The XL Engine is and will remain free, donations are purely optional but greatly appreciated.

Recent Comments

Author Archive

I have been asked for a time frame for the Beta 1 release and to gauge how far along the build is, as a percentage. I have decided to answer the questions, as best I can, while providing a progress update.

As for when I will say relatively soon, though I’m afraid that I won’t be able to give any dates until I know for sure.

As for % of completeness, that too is hard to say. The engine is basically where it needs to be, with the exception of the sound system – though that already reads Voc files, plays 2D sounds and could easily play 3D and looping sounds – its mainly a matter of resource tracking at this point. The UI needs to be finished but that, honestly, will only take a few nights of work. There are additional niceties I would like to add but most of them can wait (like controller support). So engine/UI wise I would say its at 90% or so but the other 10% isn’t very time consuming.

Where the majority of the remaining time will go is finishing the game support itself. The good news is that the current approach basically allows me to work towards all of the games in parallel (exactly how is a discussion for another day). So overall I would say I’m around 50% of the way there but the pace is accelerating due to improving methods.

This probably doesn’t sound great until I mention that I basically started from scratch a few months ago, largely due to the long hiatus and change in design – though I move and refactor old code as needed. So it is going well and the pace is very promising for meeting my goals of getting a build out near the beginning of this year. And to be clear that 50% is overall, meaning that all the games are made good progress towards completion (Daggerfall, Dark Forces and Blood), though it obviously, and intentionally, doesn’t indicate which are further along.

However as soon as more concrete promises are made something will happen so… I’m going to avoid making said promises until I know for sure. :)-

Estimated Progress Towards Beta 1

Engine – 90%
Game Support – 50%



Recent Engine Features

* Vsync now works correctly, though causes noticeable “input lag” at low refresh rates – such as 60. However this “lag” has been decreased and should be small enough for many people.

* Frame rate limiting, as an alternative to vsync, now works correctly and accurately – and has almost no perceivable “input lag” on Windows. This doesn’t completely fix tearing but greatly improves it, especially during explosions and rapid screen movement. This can be set to multiples of the refresh rate – such as 120, 240, etc. to further limit any input issues – and tends to improve the overall experience even at higher multiples. Though, obviously, your system should be able to reliably hit the requested framerate. If the system can’t keep up, the framerate limiter has no affect on performance (unlike traditional vsync). In addition it saves on CPU load which can be useful for weaker systems.

The XL Engine will probably default to a frame limiter of 120 Hz, though obviously you can change it to use vsync instead or have no frame limit (this last option is not recommended though).

* Adaptive Vsync – if available this will allow for synchronization if the system performs well enough but does not if the system under performs.

* Proper dynamic vertex buffer support so that performance is good for all Graphics “levels.”

* Render targets and improved UI aesthetic.


State of the Game Support

* All games have working libraries and entry points. They can all be selected, run and shut down from the UI.

* All games are debuggable and run, though only one is currently playable.

* All games are fully disassembled and partially re-written. Obviously some of the code is easier to understand then other parts.

* Dead code has been identified and removed from all games.


I have started filling out “Issues” on GitHub so you guys can see what I’m planning on working on in the near future.

There are currently 5 milestones: Beta 1, Beta 2, Beta 3, Beta 4 and Beta 5.


Beta 1: the initial release, all the games previously stated should be working at 100% with the exception of multi-player. As stated before only software rendering will be available but higher resolutions will be supported.

Beta 2: the OpenGL 3.2 device should be implemented and true 3D hardware rendering support added – basically Daggerfall will surpass previous builds visually in addition to being gameplay complete.

Beta 3: the “Unified Sector Theory” release – perspective correct hardware rendering for 2.5D games. At this point Dark Forces should surpass the current build visually in addition to being gameplay complete.

Beta 4: will add support for additional platforms – Linux and OS X.

Beta 5: the “Multiplayer” release – add multiplayer framework and support for certain games (such as Blood).


The GitHub page now has the most up to date version of the roadmap.

Here is the GitHub link:


Please read the XL Engine Source Code post is you have questions about pull requests, the source code, etc. before asking. Thanks. :)

After some thought, I have decided to release the full source code for the XL Engine and game implementations with the Beta build. In order to start this process I have created a Git Hub public repository and have started the process of pushing up the code.


That said, however, there are some surprises in store for the release so I will not be uploading all of the code until the Beta is published. The repository will be incomplete for now but that will be rectified for the release. Also this version of the XL Engine was rebuilt recently and so it does not contain all of the functionality past builds have had. However I am moving over the code and refactoring it to fit in the new engine as I need it but until then you will see holes. No game specific functionality is pushed and the code will not build as-is. Again this will be corrected for the release – this is just getting the ball rolling.


Finally some of the code is currently in a very rough state, this is a work in progress. I will not be taking pull requests at this time and this will be a quickly moving target, any part of the code can be radically restructured at any time. So if you have any ideas, like porting to other platforms, please wait until the Beta release and the repository is complete.


I have not upload a license yet, there are still some details to work out.


Here is the GitHub link:


This post will discuss some of the recent progress made towards the Beta. For now I will focus on the UI and Engine and talk about the games themselves in a future post.

I have been using one of the games to ensure that the engine provides all of the necessary services and to implement the core features of the engine. When you start the application, by default, a UI comes up where you can select the game to play, change video settings, setup controls and so forth. While most of the UI is only semi-functioning and placeholder – the settings are stored in text based ini files which can also be edited outside of the application. Of course everything will be editable by the UI for the release. It is possible to launch a game directly and exit immediately but even when this is the case it is always possible to bring up the UI, change settings and switch games. This is useful if you wish to play a single game or to create shortcuts and bypass the UI.

Each supported game is implemented as a dynamic library (DLL on Windows) which can be loaded and unloaded dynamically. You can even pause the game at any time to bring back the UI and change settings as desired or switch games. The game is rendered into a virtual screen which can run at a different resolution then the window – right now a multiple of 320×200 (16:10 in 4:3) but normal 4:3 and widescreen modes will be supported. Proper aspect ratio is automatically maintained with letter/pillar boxing, though stretch to fit will be an option.

I am currently re-working the sound support in order to provide all the necessary features for the variety of games but otherwise all of the engine services needed to support the games is complete – input, graphics, file manipulation, etc.. The UI still needs work but is functional enough for testing.

First Look at the UI

Below is a first look at the UI, as you can see you can click on a game to start playing or click on various icons to bring up settings menus (video, controls, engine settings, game settings and so forth). The aforementioned menus are incomplete, placeholder and not shown but the main view works as intended. When pausing a game a thumbnail view of the game is shown in the UI.

Even the versioning has been retrofitted – it is now structured as MajorVersion.MinorVersion.Build and given a name though only major changes will warrant new names. The initial releases will simply be “Beta.” The build version is now incremented automatically, so even if there are two releases made in quick succession, the version order is always clear. Obviously I started the automatic build number very recently (and even now its out of date, the current version is 0.2.161).


Final Words

I still have a lot of work to do before the release but I now have a strong foundation to build on with a fully featured engine and a solid build process – no more copying around files and manually updating build numbers, etc.. So now I can focus on finishing the game support for the Beta. :)

I have not updated this site and blog in quite some time. However, despite appearances, the project is not dead. For a variety of reasons the project had to be put on hold until the beginning of next year. Despite this, there has been some progress made – which I will discuss in detail in the next few months.

Next I would like to clarify some things. Interkarma has made large strides in his own Daggerfall work (see which begs the question – what about DaggerXL? Is there any point? And what about Dark Forces or Blood – have they been forgotten?


I will start by being as clear as possible:

*The XL Engine is not dead and will resume with regular work, updates and builds within the next few months – by the start of the new year.

*DaggerXL will continue, in no way reduced or affected negatively by Interkarma’s excellent work. There are still several reasons for DaggerXL to exist – improved Modding support, including support for existing mods; source-port accurate to the original game, with optional extended features while properly supporting modern systems – including support for software rendering; custom engine designed for high performance even on low-end systems; enhanced features such as extreme view distances (hundreds of km), enhanced texture filtering, enhanced lighting and shadows, etc.

*I have been building tools and other work that will allow me to release source-port accurate support for Dark Forces and Blood at the same time as Daggerfall early in the new year. They too will support software rendering as well as enhanced features. Outlaws has not been forgotten but has been put on hold until a later time. The XL Engine will even allow you to play with your existing save games.

*The XL Engine will be easier to use and setup, automatically setting up the supported games that you have on your system in most cases – easily supporting Steam and GoG versions of the games. It will feature a streamlined interface for adjusting features that the original games did not support (such as adjusting resolution, rendering quality, etc.), rather then implementing game specific interfaces as seen in previous releases of DarkXL or DaggerXL.

*There will be a single entry point – xlengine.exe – with the supported games being implemented as dynamic libraries, allowing for easy addition of new projects in the future. Of course you will be able to make shortcuts to specific games if you want. No launcher, no separate executables.

*Linux and OS X support is still planned but will not be available for the first Beta release.


The first Beta build, coming early in the new year, will feature support for Daggerfall, Dark Forces and Blood. However the initial release will be limited to the software renderer with only limited enhanced features (although basic stuff like higher resolutions will be supported). The XL Engine will still require a GPU with at least OpenGL 1.5 support for compositing and UI (any cards released within the last decade should work, assuming it supports any other games). GPU rendering support will follow shortly, with all the basic features you would expect. However the hardware renderer will require a GPU that supports at least OpenGL 2.0 , though higher end GPUs will be required for some extended features such as extreme view distance support (which will not be available in the initial releases). If you do not have a good enough GPU, you will always be able to fallback to the software renderer – assuming you have the aforementioned OpenGL 1.5 or better GPU.

What about source code access? I plan on releasing a few builds before I release the source code – unfortunately I’m too picky about the code for my own good – but I do plan on opening up read-only access to the repository (with the option to submit patches of course, if you are so inclined) within the first quarter of the year.


It has been a long time since my last update but my life has been undergoing a series of changes – for the better – that have occupied my time. I have started to settle in and have resumed working on the DaggerXL Beta. In this post I will talk a little about the Daggerfall Renderer, starting with some simple basics.

Below is the Object structure – the structure itself (i.e. size of variables and such) is fully known though a few members still do not have appropriate names yet, u1, u2, u3. As you can see the world space position is stored in (xPosition, yPosition, zPosition) in inches and  the rotation angles are stored in angles[3]. Positions are stored in absolute world space – this position is not relative but the actual position on the world map. The angles range from 0 – 2047 – which maps to 360 degrees. In other words 512 = 90 degrees, 2048 = 360 degrees.

struct Object
   byte type;
   word angles[3];
   int xPosition;
   int yPosition;
   int zPosition;
   word index2;
   word flags;
   word dataSize;
   word index;
   word arrayIndex;
   word model;
   int ID;
   byte u1;
   word u2;
   byte u3;
   int curLocID;
   dword time;
   Object *target2;
   Object *target;
   Object *siblingNext;
   Object *siblingPrev;
   Object *child;
   Object *parent;

To generate a rotation transform for an object or for the camera Daggerfall uses the following matrix:

Given angles: x=xAngle, y=yAngle, z=zAngle – the Daggerfall rotation matrix =

[ cos(z)*cos(y) + sin(x)*sin(z)*sin(y)  -sin(z)*cos(x)     sin(z)*sin(x)*cos(y) ]
[-sin(x)*sin(y)*cos(z) + sin(z)*cos(y)   cos(x)*cos(z)    -cos(z)*sin(x)*cos(y) ]
[ sin(y)*cos(x)                          sin(x)            cos(x)*cos(y)	]

The 3×3 rotation matrix is stored in 1.3.28 fixed point.

Here is the function used to generate the 3×3 rotation matrix from the angles:

void Build3x3Matrix(int xAngle, int yAngle, int zAngle, int *matrix)
    int x = xAngle&0x07ff;
    int y = yAngle&0x07ff;
    int z = zAngle&0x07ff;

    const int64 half = 134217728LL;
    int64 c = (int64)sinTable[z] * (int64)sinTable[y] + half;
    camAngle0 = (int)( c >> 28LL );

    c = (int64)sinTable[y] * (int64)sinTable[512+z] + half;
    camAngle1 = (int)( c >> 28LL );

    c = (int64)sinTable[x] * (int64)sinTable[512+y] + half;
    camAngle2 = (int)( c >> 28LL );

    c = (int64)sinTable[512+z] * (int64)sinTable[512+y] + 
        (int64)sinTable[x] * (int64)camAngle0 + half;
    matrix[0] = (int)( c >> 28LL );

    c = -(int64)sinTable[z] * (int64)sinTable[x+512] + half;
    matrix[1] = (int)( c >> 28LL );

    c = (int64)sinTable[z] * (int64)camAngle2 + half;
    matrix[2] = (int)( c >> 28LL ) - camAngle1;

    c = -(int64)sinTable[x] * (int64)camAngle1 + 
         (int64)sinTable[z] * (int64)sinTable[y+512] + half;
    matrix[3] = (int)( c >> 28LL );

    c = (int64)sinTable[x+512] * (int64)sinTable[z+512] + half;
    matrix[4] = (int)( c >> 28LL );

    c = -(int64)sinTable[z+512] * camAngle2;
    matrix[5] = (int)( c >> 28LL ) - camAngle0;

    c = (int64)sinTable[y] * (int64)sinTable[x+512] + half;
    matrix[6] = (int)( c >> 28LL );

    matrix[7] = sinTable[x];

    c = (int64)sinTable[x+512] * (int64)sinTable[y+512] + half;
    matrix[8] = (int)( c >> 28LL );

A few things to note – the fixed point format is 1.3.28 so 64 bit math is required to avoid overflows, fortunately x86 assembly makes this fairly easy to do quickly. Also note that Daggerfall has a sine table that stores the sin values for all angles ranging from 0 to 2047 (remember that this maps to 0 to 360 degrees). As you can see the cosine values are computed as sinTable[angle+512] – since sine and cosine are out of phase by 90 degrees, we can compute cosine as cos(angle) = sin(angle+90degrees) which Daggerfall does to limit the size of the table.

The camera uses the same position and angles to generate the view and projection matrices. However the way positions are transformed into screenspace are a little different from most modern engines. The projection matrix is build directly from the view matrix by scaling by the x relative and y relative screen aspect ratios, built as follows:

void BuildAspectScaledMatrix(int *rotMatrix, int *outScaledMatrix)
    int64 c = (int64)rotMatrix[0] * (int64)screenAspectX;
    outScaledMatrix[0] = (int)( c >> 14LL );

    c = (int64)rotMatrix[1] * (int64)screenAspectX;
    outScaledMatrix[1] = (int)( c >> 14LL );

    c = (int64)rotMatrix[2] * (int64)screenAspectX;
    outScaledMatrix[2] = (int)( c >> 14LL );

    c = (int64)rotMatrix[3] * (int64)screenAspectY;
    outScaledMatrix[3] = (int)( c >> 14LL );

    c = (int64)rotMatrix[4] * (int64)screenAspectY;
    outScaledMatrix[4] = (int)( c >> 14LL );

    c = (int64)rotMatrix[5] * (int64)screenAspectY;
    outScaledMatrix[5] = (int)( c >> 14LL );

    outScaledMatrix[6] = rotMatrix[6];
    outScaledMatrix[7] = rotMatrix[7];
    outScaledMatrix[8] = rotMatrix[8];

Note that the screenAspectX and screenAspectY are stored in 1.17.14 fixed point but the resulting projection matrix is still stored in 1.3.28 fixed point.

Positions in view space are stored as 1.23.8 fixed point so a position can be transformed as follows:
Given (x,y,z) in absolute world space:
(Note s_xPosition, s_yPosition and s_zPosition are the camera positions extracted from the camera objects and used by the renderer).

//Convert to camera relative coordinates, stored in 1.23.8 fixed point), still in inches.
int viewX = (x - s_xPosition)*256;
int viewY = (y - s_yPosition)*256;
int viewZ = (z - s_zPosition)*256;
TransformPoint(&viewX, &viewY, &viewZ, projMatrix);

void TransformPoint(int *x, int *y, int *z, int *matrix)
    int64 xp = (int64)(*x)*16LL;
    int64 yp = (int64)(*y)*16LL;
    int64 zp = (int64)(*z)*16LL;

    *x = (int)( ( xp*(int64)matrix[0] + yp*(int64)matrix[1] + zp*(int64)matrix[2] ) >> 32LL );
    *y = (int)( ( xp*(int64)matrix[3] + yp*(int64)matrix[4] + zp*(int64)matrix[5] ) >> 32LL );
    *z = (int)( ( xp*(int64)matrix[6] + yp*(int64)matrix[7] + zp*(int64)matrix[8] ) >> 32LL );

Finally the following functions are used to cull the bounds, stored as spheres – center in relative camera coordinates and radius in inches.

enum ClipPlanes_e
    PLANE_NEGX =  1,
    PLANE_POSX =  2,
    PLANE_POSY =  4,
    PLANE_NEGY =  8,
    PLANE_NEAR = 16,
    PLANE_FAR  = 32,
static int nearPlane=2560;	//[1E9CC0] :the near plane is 10 inches.
static int farPlane =393216;	//[1E9CC4] :the far plane is 128 feet 
                                //(at normal maximum settings).

int CullPlaneDistX(int xScaled, int yScaled, int radius)
    int r = (zRadius*xScaled)>>16 + (xRadius*yScaled)>>16;
    if ( r < 0 ) 
        r = -r; 
    return r+radius;

int CullPlaneDistY(int xScaled, int yScaled, int radius)
    int r = (zRadius*xScaled)>>16 + (yRadius*yScaled)>>16;
    if ( r < 0 ) 
        r = -r; 
    return r+radius;
//Is the sphere: center = (x,y,z), radius = r at least partially visible?
//Returns 0 if visible else 1.
//Note that the sphere position must be relative to the camera and be in 1.23.8 fixed point format.
int IsSphereVisible(int x, int y, int z, int r)
    //transform into projection space.
    TransformPoint(&x, &y, &z, projMatrix);

    //figure out which planes the sphere needs to be tested against.
    int clipFlags = PLANES_ALL;	//6 planes = 111111 binary = 63
    if ( z >= nearPlane )
        clipFlags ^= PLANE_NEAR;
    if ( z <= farPlane ) 	
        clipFlags ^= PLANE_FAR; 	
    if ( x >= -z )
        clipFlags ^= PLANE_NEGX;
    if ( x <= z )
        clipFlags ^= PLANE_POSX;

    if ( y <= z ) 	
        clipFlags ^= PLANE_POSY; 	
    if ( y >= -z )
        clipFlags ^= PLANE_NEGY;

    //compute plane data.
    xRadius = (int)( ((int64)x*(int64)x*(int64)ScreenAspectA   )>>32LL );
    yRadius = (int)( ((int64)y*(int64)y*(int64)ScreenAspectA_SC)>>32LL );
    zRadius = z;

    //test against each plane based on the clipflags set (see above).
    if ( clipFlags ) 
        if ( s_clipFlags&PLANE_NEAR ) 
            if ( z+r <= nearPlane ) 				
                return 1; 		
        if ( s_clipFlags&PLANE_FAR ) 		
            if ( z-r >= farPlane )
                return 1;

        if ( s_clipFlags&PLANE_NEGX )
            if ( CullPlaneDistX(ScreenX_Scaled, ScreenH_Scaled, r) <= 0 )
                return 1;

        if ( s_clipFlags&PLANE_POSX )
            if ( CullPlaneDistX(ScreenX_Scaled, -ScreenH_Scaled, r) <= 0 )
                return 1;

        if ( s_clipFlags&PLANE_POSY )
            if ( CullPlaneDistY(varD34, -varD30, r) <= 0 )
                return 1;

        if ( s_clipFlags&PLANE_NEGY )
            if ( CullPlaneDistY(varD34, varD30, r) <= 0 )
                return 1;

    return 0;

Anyway that is enough about the renderer for this post but I will talk about the lighting system, more about culling objects and other topics in future blog posts. If you want to see the original assembly for these functions, visit the Renderer Part I – Original Functions in Assembly page. As you will see I have rearranged the code a little to make some things more clear.

I have resumed work on the DaggerXL Beta after taking a break for Christmas. I don’t have much new to show yet but I will talk about a few topics.


On the XL Engine forums there has been mod work towards using the WebUI system that will be present in the Beta release. I talked about this system before in a previous blog post but now Lazaroth has been busy working on a UI mod, using existing web technologies to prototype. I dropped the files into the WebUI folder under the XL Engine and modified a bit of code (that will be externalized for the release) and was able to view the new UI in the current version of the XL Engine. There are a few issues to work out but its a great start and will allow me to work out kinks in the WebUI system. It also shows that using HTML5/CSS/Javascript and a browser is a great way of prototyping new UI ideas and the results can be nearly “drop-in” for use in the XL Engine.




If you want more information on the mod or to help out, check out Lazaroth’s thread on the forums.


Anatomy of a For-Loop in Daggerfall

So what does a for-loop look like in Daggerfall? It turns out that recognizing for-loops generated by the compiler is rather simple as you’ll see shortly. I have copied some of the actual assembly code with comments.

mov dword [ebp-0018],00000000
[19A283]				;for (int i=0; i<27; i++) {
cmp dword [ebp-0018],001B               ;//comparison block
jl 0019A293
jmp 0019A2D1
[19A28B]			        ;//for-loop counter block
mov eax,[ebp-0018]
inc dword [ebp-0018]
jmp 0019A283
[19A293]			        ;//code block
jmp 0019A28B
[19A2D1]				;} //for (int i=0; i<27; i++)

The actual assembly code is on the left, with code addresses shown in brackets [ ]. As you can see the local variable, ‘i’ as I named it, is at ebp-0x18. So the first thing that happens is to fill the value with 0, basically the i=0; part of the for-loop. Next it enters the comparison block where the local variable is compared to a value – in this case 0x1B = 27, and if the comparison is successful the execution jumps to the code block, otherwise it jumps to the end of the loop (the last line shown above).

When you see the C/C++ code

for (int i=0; i<27; i++)
    //code inside the loop

The “code inside the loop” is the code block above. Once the code is executed or a continue is hit – then the execution jumps to the for-loop counter block. Here the counter is incremented, decremented or otherwise modified before jumping back to the comparison block. Obviously if a break is encountered in the code block, the jump will lead directly to the end of the loop or to a another address which will have a jump to the end of the loop if the difference in address is too big for a “short” jump (usually). Remember that for-loops look different with modern compilers and sometimes the format is tweaked a bit even in Daggerfall depending on what the optimizer does. But this is essentially what a for-loop looks like, even nested loops have a similar, though recursive, structure.

The Beta

I have recently continued work towards the Beta release and will start posting updates again as additional progress is made.

Unfortunately I have a lot on my plate for this Christmas season, so I will be taking a break from XL Engine work until after Christmas. This has been happening for the last week or so, which is why I haven’t been posting updates. Anyway this is not caused by roadblocks or even stressful issues but rather by obligations I have to meet by the holiday.

Once Christmas is over I should have some extra time for a while, allowing me to make up for lost time a little. :)

Happy Holidays.


Unfortunately time was tight this past week, reducing the number of updates I could write and the amount of work being done. I have loaded the character data, faction data, setup the current region and loaded the location data for my save game.

For the moment I’m working on getting dungeons fully loaded from the save games, which has had me revisiting the dungeon loading code again. It turns out this process has allowed me to clean up the code a bit, with better understanding gained by approaching the code from another angle.

So I’ll describe a bit of the way Daggerfall interprets dungeon data, which is a little different then DaggerXL did before this point. I’ll also explain a little about how objects are allocated and used.


Textures Revisited

First, you’ll recall the texture assignment code I showed before for dungeons when explaining how random numbers were involved. In that function there were calls to srand() and rand(), which were valid, and a call to RandByte(). I hadn’t fully fleshed out this function, thinking I’d revisit the random number generation stuff later. Well it turns out its both simpler and more complex then I originally thought.

It turns out that function is specific to dungeon textures, so I now call it DungeonTex_RandByte(). But basically another section of code needs to be called first to generate a “key,” which is derived from the map coordinates which happens during cell loading or save game loading depending on whether you’re loading from a save game, by starting a new game or by clicking on a door. It is generated like this:

dungeonLocTexKey = GetDungeonTexKey( mapX, mapZ, dungeonKeyTable )

In this case mapX and mapZ are not the exact map coordinates but offset slightly from normal:

mapX = worldX/32768 + 2
mapZ = 499 - (worldZ/32768)

if ( mapZ < 1 )     
    mapZ = 1;   
else if (mapZ > 499 )
    mapZ = 499;


Here is the code to build the texture key for the current location. I don’t show the key table because it is pretty big – 500 entries.

struct TexKey_XOffsetEntry
   word xOffset;
   byte key;

byte GetDungeonTexKey(int mapX, int mapZ, TexKey_XOffsetEntry *keyTable)
   TexKey_XOffsetEntry *keyEntry = &keyTable[mapZ];
   mapX -= keyEntry->xOffset;
   while (mapX > 0)
      mapX -= keyEntry->xOffset;
   return keyEntry->key;


Then the key is used DungeonTex_RandByte, as follows:

byte DungeonTex_RandByte()
    return dungeonTexTable[ dungeonLocTexKey ];


Which is used to actually generate the texture table (you’ve seen this code before):

word textureTableSrc[5]=;   //0x28617C
{ 119, 120, 122, 123, 124 };
word textureTableCur[5];   //0x286186

void InitializeTextureTable()
   int r0 = rand();
   srand( curLocationRec->locationID>>16 );
   byte r = DungeonTex_RandByte();
   int r2 = _texRandTable[ r ];

   memcpy(textureTableCur, textureTableSrc, 10);
   for (int i=0; i<5; i++)
      byte n = random_range(0, 4);
      if ( n == 2 )
         n += 2;
      n += textureTableSrc[r2];
      textureTableCur[i] = n;


… the process looks convoluted and well… it is.


Dungeon Blocks

As you already know dungeons are loaded as “blocks” – each of which is an RDB and RDI file that identifies all the objects, models, quest locations, monsters, loot piles and so on for the dungeon block. For the purposes of culling and rendering, each block is sub-divided into 2×2 sub-blocks. RDB files contain a grid of object root offsets – each of which maps to one of these 2×2 sub-blocks. As it turns out, the game always uses 2×2 sub-blocks, even though RDB files can (and usually do) specify more. Even though these blocks must be referenced based on grid width/height, only the upper left 2×2 block is actually used (the rest of the data is -1).

Daggerfall creates a sub-block game object for each sub-block, which is used as the parent object for the sub-block objects. For rendering, the game loops through each visible sub-block, then traverses the link list of child objects to render, collide with, etc.. Links between objects, for things like switches, actually occur within these sub-blocks, though the height difference can be immense, and the distance can seem quite large due to the windy nature of the corridors.

Each dungeon block is 2048 x ? x 2048 game units, each sub-block being 1024 x ? x 1024 units. Note that the height value is a question mark since there aren’t any hard-coded limits, as far as I’ve seen so far.


Game Objects

When Daggerfall starts up, a 3584000 byte (almost 3.42MB) object pool is allocated. Each object is 71 bytes is size, with extra data that can be allocated based on type. In addition 18 bytes of addition overhead is used for things like previous and next pointers.

Each game object in Daggerfall – things ranging from 3D objects, lights, flats, sub-block objects, the player, NPCs, enemies, etc. – use this pool, with the type specific data being allocated after the 71 bytes. This data contains information such as the world position (in “units” – basically inches), flags, unique identifier, link list data, type and so forth.

So, while the code is pure C, this type of structure allows for some object oriented style behavior – you can think of the Object as being the base class. And they are actually called objects in the original code, as evidenced by the following error message if no object can be found:

if ( !obj )
    printf( "Unable to allocate OBJECT memory." );

Anyway you can use the type parameter to determine which structure to cast the type-specific data to, at offset 71.

I’m hoping to be able to show screenshots soon, fortunately by coding support for save games now I can make sure that all locations of a given type work – so my first dungeon shots will not be of Privateer’s Hold. :D

The last couple of days have been slow due to work, Halloween and such, so I’m still working on loading save games. However, to keep the news flowing, I’ll talk about a specific element – factions.

When loading a save game or starting a new game the first thing that needs to happen is to load the base faction data from FACTION.TXT. The parser first frees all the previous faction data and then counts the number of factions – by counting the number of times ‘#’ shows up in the text file. At this point, the parser looks for certain symbols: ‘#’ for faction ID and ‘:’ for tags. Tags themselves are hashed, with a callback function being called for any matching table entry using the following table:

struct FactionTxtTag
   word tagID;
   factionTagCB func;

FactionTxtTag factionTags[19]=   //2865BC
   { 0x06C9, Faction_ProcessTypeTag   },
   { 0x0633, Faction_ProcessNameTag   },
   { 0x0302, Faction_ProcessRepTag    },
   { 0x1C18, Faction_ProcessSummonTag },
   { 0x1AB8, Faction_ProcessRegionTag },
   { 0x0D90, Faction_ProcessPowerTag  },
   { 0x0C85, Faction_ProcessFlagsTag  },
   { 0x0609, Faction_ProcessAllyTag   },
   { 0x0CA7, Faction_ProcessEnemyTag  },
   { 0x0DB4, Faction_ProcessRulerTag  },
   { 0x05DF, Faction_ProcessFaceTag   },
   { 0x0616, Faction_ProcessFlatTag   },
   { 0x063F, Faction_ProcessRaceTag   },
   { 0x1B76, Faction_ProcessSGroupTag },
   { 0x19F6, Faction_ProcessGGroupTag },
   { 0x064E, Faction_ProcessMinFTag   },
   { 0x0642, Faction_ProcessMaxFTag   },
   { 0x065B, Faction_ProcessRankTag   },
   { 0x0307, Faction_ProcessVamTag    },

A few of the entries are ignored – the callback functions actually just skip past the data without recording it, but most fill out the Faction structure. The following tags are ignored: Summon (the game determines this using other methods), MinFMaxF and Rank. Some tags can occur more then once – up to 2 flats, 3 allies and 3 enemies for example.

Then once all the factions are processed, enemy and ally IDs are converted to faction pointers. Once all this is complete, faction data is read from the save games and overwrites the data (but leaves the pointers as-is). These then have their pointers fixed up again.

So the factions in the text file determine the default faction data, including reputation (which doesn’t always start at 0). Only those factions that have been modified or that the player are directly a part of are actually tracked in the save game files. The rest stay at their default values – except for one thing. Each time the faction data is loaded, two random numbers are generated which are used to help determine certain behaviors (more on this later).

Anyway, here is the final Faction structure – I keep track of the actual hex offsets for each variable to make it easier for me to convert from offsets in the assembly code to the variable in the structure.

//sizeof(Faction) = 92
struct Faction
   byte type;                //0x00
   char region;              //0x01
   byte ruler;               //0x02
   char name[26];            //0x03
   short rep;                //0x1D
   short power;              //0x1F
   short id;                 //0x21
   short vam;                //0x23
   word flags;               //0x25
   dword rndValue1;          //0x27
   dword rndValue2;          //0x2B
   short flats[2];           //0x2F
   word face;                //0x33
   char race;                //0x35
   byte sgroup;              //0x36
   byte ggroup;              //0x37
   Faction *allies[3];       //0x38
   Faction *enemies[3];      //0x44
   //pointers used for traversing the list, these are not saved or loaded.
   Faction *child;           //0x50
   Faction *parent;          //0x54
   Faction *prev;            //0x58

Obviously I already know how Bethesda hashed the tag names, since I reverse engineered the code that does it, but as a fun puzzle let’s see if any of you guys can figure it out – given the data below. This is one kind of puzzle that is often involved with this work since I oftentimes get data that is referenced but understanding the code requires understanding what the data actually means – which is very often not explicit in the code (though it was this time). Unfortunately they are not always as simple as this one. :P


 INPUT          OUTPUT (hex)     OUTPUT (base-10 "normal")
   type           0x06C9               1737
   name           0x0633               1587
   rep            0x0302                770
(see the table above for more examples)


Important clues:
*case matters
*you’ll want to consult an ASCII table (search ASCII table on google)
*the number of characters has a fairly large impact on the result
*the transform is simple

*Convert from the tag name, such as type, to the number used in the lookup table using a mathematical transform on the characters.

Post here if you think you’ve figured it out. :)

The XL Engine is and will remain free, donations are purely optional but greatly appreciated.