“Dust: A Tale of the Wired West” is a game produced by Cyberflix in 1995. It was a game where the player had lots of conversations with colorful characters, collected inventory items and solved puzzles. There are simply too many branches in the game’s logic to uncover by simply playing it. So maybe there was a way of uncovering the game logic from the game files.
Dust Game Files
The Windows installation of Dust contains several executable files and game files of interest:
| Type | Description |
|---|---|
| Executables | Dust.exe DF.exe MoviePlay.exe |
| CST | Cast files containing the 3D characters and some game logic |
| FLT | Flat files containing puzzle logic |
| MOV | Movie files containing screen animations and still renders |
| PRP | Prop files containing 3D props that are placed throughout the game |
| PUP | Puppet files containing characters dialogue, game logic and images of the characters |
| SET | Set files containing game locations and game logic. |
The game files are composed of a header and a set of blocks. I will go into greater detail about the file format of these game files at a later point in time, but all we need to know for now, is the following:
- All game files except for movie files can contain Script Blocks
- Puppet files also contain a list of dialogue lines that can be referenced from inside the Script Blocks
What does a Script Block look like?
All blocks in the Dust game files start with 4 bytes of block information: The block-ID and its size. Since we’re looking at the Windows version of the game files, all the data is stored in Little-Endian byte-order.
The Script Block consists of two sections:
- Logic
- Variable Lookup Table
Here’s an example of what we’ll be working with:
00000000 2f00 0000 8806 0000 0500 9805 0000 0000 |/_______________|
00000016 0500 9805 0000 0000 0300 9605 0000 0000 |________________|
00000032 0500 9405 0000 0000 0400 0100 0000 0000 |________________|
00000048 0600 0000 0000 0000 0600 0000 0000 0000 |________________|
00000064 a10f 0000 0000 0000 0500 7005 0000 0000 |__________p_____|
00000080 b20f 0000 0000 0000 b30f 0000 0000 0000 |________________|
00000096 0600 0100 0000 0000 a30f 0000 0000 0000 |________________|
00000112 0500 5205 0000 0000 0600 0100 0000 0000 |__R_____________|
00000128 a20f 0000 0000 0000 0500 3e05 0000 0000 |__________>_____|
00000144 b40f 0000 0000 0000 0500 3905 0000 0000 |__________9_____|
00000160 0600 0000 0000 0000 0600 0100 0000 0000 |________________|
00000176 092f 0000 0000 0000 b20f 0000 0000 0000 |_/______________|
00000192 b30f 0000 0000 0000 0600 0100 0000 0000 |________________|
00000208 a60f 0000 0000 0000 0500 f904 0000 0000 |________________|
00000224 481f 0000 0000 0000 0400 0000 0000 0000 |H_______________|
00000240 0600 0200 0000 0000 0500 d904 0000 0000 |________________|
00000256 481f 0000 0000 0000 0400 0100 0000 0000 |H_______________|
00000272 0600 0200 0000 0000 0b2f 0000 0000 0000 |_________/______|
00000288 b20f 0000 0000 0000 0300 b404 0000 0000 |________________|
00000304 b30f 0000 0000 0000 0600 0200 0000 0000 |________________|
00000320 0b2f 0000 0000 0000 b20f 0000 0000 0000 |_/______________|
00000336 0300 9404 0000 0000 b30f 0000 0000 0000 |________________|
00000352 0600 0200 0000 0000 0b2f 0000 0000 0000 |_________/______|
00000368 b20f 0000 0000 0000 0300 7404 0000 0000 |__________t_____|
00000384 b30f 0000 0000 0000 0600 0200 0000 0000 |________________|
00000400 0b2f 0000 0000 0000 b20f 0000 0000 0000 |_/______________|
00000416 0300 5404 0000 0000 b30f 0000 0000 0000 |__T_____________|
00000432 0600 0100 0000 0000 a70f 0000 0000 0000 |________________|
00000448 0600 0100 0000 0000 0b2f 0000 0000 0000 |_________/______|
00000464 b20f 0000 0000 0000 0300 2404 0000 0000 |__________$_____|
00000480 b30f 0000 0000 0000 0600 0100 0000 0000 |________________|
00000496 092f 0000 0000 0000 b20f 0000 0000 0000 |_/______________|
00000512 b30f 0000 0000 0000 0600 0100 0000 0000 |________________|
00000528 a60f 0000 0000 0000 0500 ae03 0000 0000 |________________|
00000544 4b1f 0000 0000 0000 0400 0500 0000 0000 |K_______________|
00000560 0600 0200 0000 0000 0c2f 0000 0000 0000 |_________/______|
00000576 b20f 0000 0000 0000 0300 bc03 0000 0000 |________________|
00000592 b40f 0000 0000 0000 0400 6600 0000 0000 |__________f_____|
00000608 b30f 0000 0000 0000 0600 0100 0000 0000 |________________|
00000624 a80f 0000 0000 0000 0600 0200 0000 0000 |________________|
00000640 0c2f 0000 0000 0000 b20f 0000 0000 0000 |_/______________|
00000656 0300 9003 0000 0000 b40f 0000 0000 0000 |________________|
00000672 0400 6600 0000 0000 b30f 0000 0000 0000 |__f_____________|
00000688 0600 0200 0000 0000 0c2f 0000 0000 0000 |_________/______|
00000704 b20f 0000 0000 0000 0300 7803 0000 0000 |__________x_____|
00000720 b40f 0000 0000 0000 0400 6500 0000 0000 |__________e_____|
00000736 b30f 0000 0000 0000 0600 0200 0000 0000 |________________|
00000752 0500 d602 0000 0000 481f 0000 0000 0000 |________H_______|
00000768 0500 c602 0000 0000 421f 0000 0000 0000 |________B_______|
00000784 0400 0500 0000 0000 0600 0100 0000 0000 |________________|
00000800 a70f 0000 0000 0000 0600 0100 0000 0000 |________________|
00000816 0c2f 0000 0000 0000 b20f 0000 0000 0000 |_/______________|
00000832 0300 1803 0000 0000 b40f 0000 0000 0000 |________________|
00000848 0400 6600 0000 0000 b30f 0000 0000 0000 |__f_____________|
00000864 0600 0100 0000 0000 0500 5a02 0000 0000 |__________Z_____|
00000880 481f 0000 0000 0000 3c4e 0000 0000 0000 |H_______<N______|
00000896 b20f 0000 0000 0000 421f 0000 0000 0000 |________B_______|
00000912 0400 0100 0000 0000 b30f 0000 0000 0000 |________________|
00000928 0600 0100 0000 0000 a90f 0000 0000 0000 |________________|
00000944 0500 1202 0000 0000 0600 0100 0000 0000 |________________|
00000960 ab0f 0000 0000 0000 421f 0000 0000 0000 |________B_______|
00000976 0400 0100 0000 0000 0600 0200 0000 0000 |________________|
00000992 a50f 0000 0000 0000 0600 0100 0000 0000 |________________|
00001008 ab0f 0000 0000 0000 0400 6500 0000 0000 |__________e_____|
00001024 0600 0200 0000 0000 0b2f 0000 0000 0000 |_________/______|
00001040 b20f 0000 0000 0000 0300 4402 0000 0000 |__________D_____|
00001056 b30f 0000 0000 0000 0600 0200 0000 0000 |________________|
00001072 f02e 0000 0000 0000 b20f 0000 0000 0000 |________________|
00001088 0300 6e01 0000 0000 b40f 0000 0000 0000 |__n_____________|
00001104 0500 1502 0000 0000 b20f 0000 0000 0000 |________________|
00001120 b30f 0000 0000 0000 b30f 0000 0000 0000 |________________|
00001136 0600 0200 0000 0000 ab3e 0000 0000 0000 |_________>______|
00001152 b20f 0000 0000 0000 0300 2601 0000 0000 |__________&_____|
00001168 b40f 0000 0000 0000 0300 da01 0000 0000 |________________|
00001184 b30f 0000 0000 0000 0600 0100 0000 0000 |________________|
00001200 ab0f 0000 0000 0000 0400 6600 0000 0000 |__________f_____|
00001216 0600 0200 0000 0000 0b2f 0000 0000 0000 |_________/______|
00001232 b20f 0000 0000 0000 0300 a401 0000 0000 |________________|
00001248 b30f 0000 0000 0000 0600 0200 0000 0000 |________________|
00001264 863e 0000 0000 0000 b20f 0000 0000 0000 |_>______________|
00001280 0300 ae00 0000 0000 b40f 0000 0000 0000 |________________|
00001296 0300 7501 0000 0000 471f 0000 0000 0000 |__u_____G_______|
00001312 3a4e 0000 0000 0000 b20f 0000 0000 0000 |:N______________|
00001328 214e 0000 0000 0000 b20f 0000 0000 0000 |!N______________|
00001344 0400 0300 0000 0000 b30f 0000 0000 0000 |________________|
00001360 b30f 0000 0000 0000 b30f 0000 0000 0000 |________________|
00001376 0600 0100 0000 0000 aa0f 0000 0000 0000 |________________|
00001392 0600 0000 0000 0000 a40f 0000 0000 0000 |________________|
00001408 0600 0000 0000 0000 0600 0000 0000 0000 |________________|
00001424 0600 0000 0000 0000 0000 0000 0000 0000 |________________|
00001440 0750 5241 4952 4945 0557 4f4d 414e 054a |_PRAIRIE_WOMAN_J|
00001456 454e 4958 0344 4159 0972 756e 796f 7365 |ENIX_DAY_runyose|
00001472 6c66 0361 7267 0a70 6c61 7965 7263 6173 |lf_arg_playercas|
00001488 680a 6a65 6e69 7870 6861 7365 076a 656e |h_jenixphase_jen|
00001504 6978 2e35 076a 656e 6978 2e36 076a 656e |ix_5_jenix_6_jen|
00001520 6978 2e37 076a 656e 6978 2e38 076a 656e |ix_7_jenix_8_jen|
00001536 6978 2e39 1b4e 6f2c 2049 2064 6f6e 2774 |ix_9_No, I don’t|
00001552 2068 6176 6520 616e 7920 6d6f 6e65 792e | have any money.|
00001568 1f4e 6f2c 2049 2077 6f6e 2774 2067 6976 |_No, I won’t giv|
00001584 6520 796f 7520 616e 7920 6d6f 6e65 792e |e you any money.|
00001600 1759 6573 2c20 6865 7265 2069 7320 7468 |_Yes, here is th|
00001616 6520 6d6f 6e65 792e 034e 6f2e 086a 656e |e money._No._jen|
00001632 6978 2e31 300c 7075 7464 6f77 6e61 6374 |ix_10_putdownact|
00001648 6f72 0967 6176 656d 6f6e 6579 086a 656e |or_gavemoney_jen|
00001664 6978 2e31 310a 746f 776e 2e65 7874 7261 |ix_11_town_extra|
What does it all mean?
If we look at the Script Block, we can see that there is a pattern in the “Logic” part of the block.
The pattern consists of 8 bytes, where the first 4 bytes contain information and the second 4 bytes are always 00’s. This means that we can disregard the second half of the pattern.
If we look closer at the information bytes we can see that some values in the first two bytes of the information bytes are recurring more often than others. They are also the only ones that are followed by another 2 bytes of information, so they probably determine the meaning of the information that follows it. We’ll call the first two bytes the flag-bytes and the second two bytes the value-bytes:
| Name | Flag bytes | Value bytes |
|---|---|---|
| A | 0x0300 | 0x???? |
| B | 0x0400 | 0x???? |
| C | 0x0500 | 0x???? |
| D | 0x0600 | 0x0000 to 0x0200 |
| X | 0x???? | 0x0000 |
So we’ve defined flag bytes A-D for the most recurring and then X for the remaining flag bytes. Note that X does have recurring flag bytes as well.
Variables
At this point we have no idea what this all means. What we do know, is that we have a lot of variable names at the end of our script block. First let’s have a look at how these variables are constructed.
00001440 0750 5241 4952 4945 0557 4f4d 414e 054a |_PRAIRIE_WOMAN_J|
Each variable starts with a byte that defines the length of the string and is followed by a set of bytes in the ASCII range. In other words, they’re all Pascal Strings. For example, “PRAIRY” starts at offset 1440. The first byte 0x07 defines that the string is 7 characters long, followed by [‘P’,’R’,’A’,’I’,’R’,’I’,’E’]
Next, let’s see if we can find where these variables are referenced.
There aren’t any absolute references to the offsets of the strings. For example, “WOMAN” starts at offset 1448, but there is no value 0xA805 anywhere in the Script Block. So if it isn’t an absolute offset, maybe it’s a relative offset.
00000000 2f00 0000 8806 0000 0500 9805 0000 0000 |/_______________|
00000016 0500 9805 0000 0000 0300 9605 0000 0000 |________________|
If we take a look at the start of our Script Block, we have the following information in the first 32 bytes:
- Block ID: 47 (0x2f00)
- Block Size: 1672 bytes (0x8806)
- Flag 5: 1432 (0x9805) at offset 8
- Flag 5: 1432 (0x9805) at offset 16
- Flag 3: 1430 (0x9605) at offset 24
If we assume the values of the flags are relative offsets, where do they land us?
8 + 1432 = 1440
16 + 1432 = 1448
24 + 1430 = 1454
Those are the offsets of our variables: PRAIRIE, WOMAN, JENIX
So we’ve figured out that flags A (3) and C (5) reference the variables at the bottom of the script.
Flag X
At this point we’ve uncovered part of the script, but it really doesn’t give us enough information to figure out what all the other flags mean. We can observe that some flags occur more often than others, such as 0xb20f and 0xb30f, but apart from that there really isn’t anything else to find.
When I was trying to decipher this, I got stuck for quite some time. The reason for this, is that I was missing a very important piece of the puzzle. This piece was locked away in one of the executables and I came across it while looking for human-readable strings.
While I was analyzing DF.exe in Ghidra, I came across a lookup table that contained what seemed like function names and programming operators.

Each line is a pointer to a string, followed by two bytes. These are the values of Flag X.
If we extract this lookup table from the binary, we can use it to replace these flags with the corresponding strings.
00001136 0600 0200 0000 0000 ab3e 0000 0000 0000 |_________>______|
00001152 b20f 0000 0000 0000 0300 2601 0000 0000 |__________&_____|
00001168 b40f 0000 0000 0000 0300 da01 0000 0000 |________________|
00001184 b30f 0000 0000 0000 0600 0100 0000 0000 |________________|
If we replace flags A, C and X with what we now know, we get the following:
0600 0200: ?
ab3e 0000: actorowner
b20f 0000: (
0300 2601: JENIX
b40f 0000: ,
0300 da01: gavemoney
b30f 0000: )
0600 0100: ?
actorowner(JENIX, gavemoney)
Integer Values and Formatting
Now that we have uncovered the three most important flags of the script, maybe we can figure out what the other flags mean by filling in the blanks.
00000112 0500 5205 0000 0000 0600 0100 0000 0000 |__R_____________|
00000128 a20f 0000 0000 0000 0500 3e05 0000 0000 |__________>_____|
00000144 b40f 0000 0000 0000 0500 3905 0000 0000 |__________9_____|
00000160 0600 0000 0000 0000 0600 0100 0000 0000 |________________|
00000176 092f 0000 0000 0000 b20f 0000 0000 0000 |_/______________|
00000192 b30f 0000 0000 0000 0600 0100 0000 0000 |________________|
00000208 a60f 0000 0000 0000 0500 f904 0000 0000 |________________|
00000224 481f 0000 0000 0000 0400 0000 0000 0000 |H_______________|
00000240 0600 0200 0000 0000 0500 d904 0000 0000 |________________|
00000256 481f 0000 0000 0000 0400 0100 0000 0000 |H_______________|
We convert this to the following by filling in flags A,C and X:
arg 0600 0001 global playercash , jenixphase 0600 0001 puppetclear ( ) 0600 0000 0600 0001 if jenixphase = 0400 0000 0600 0002 jenixphase = 0400 0001
If we look at flag B (0x0400), we quickly see that it’s an integer flag. The best example is the value 0 after “if jenixphase =“
arg 0600 0001 global playercash , jenixphase 0600 0001 puppetclear ( ) 0600 0000 0600 0001 if jenixphase = 0 0600 0002 jenixphase = 1
Now all we have left is flag D (0x0600). The flag value seems to increment and decrement as the script goes along. This becomes most apparent when looking at the larger script files in the game.
This flag is used to denote where the line breaks are, but also the amount of indentation (amount of tabs) that line should start with:
arg
global playercash , jenixphase
puppetclear ( )
if jenixphase = 0
jenixphase = 1
Parameters
Now we can completely print the script. However, there’s still one question unanswered:
What’s the difference between flag A (0x0300) and flag C (0x0500)?
I don’t have a definitive answer for this, but the main difference appears to be that flag A is used for variables that are function parameters and flag B is used for the rest. My best guess is that they used this for code coloring or bold/italic in their game engine to improve readability.
The Result
| Name | Flag bytes | Type | Description |
|---|---|---|---|
| A | 0x0300 | Parameter Variable | References variable using a relative offset |
| B | 0x0400 | Integer | The flag value should be interpreted as an integer |
| C | 0x0500 | Variable | References variable using a relative offset |
| D | 0x0600 | Indentation | Defines a new line and indents the following code by the flag value. |
| X | 0x???? | Script | Key of the Script Lookup Table. |
PRAIRIE WOMAN JENIX DAY 1
code runyoself ( )
local arg
global playercash , jenixphase
puppetclear ( )
if jenixphase = 0
jenixphase = 1
puppetspeak ( jenix.5 )
puppetspeak ( jenix.6 )
puppetspeak ( jenix.7 )
puppetspeak ( jenix.8 )
endif
puppetspeak ( jenix.9 )
puppetclear ( )
if playercash < 5
puppetbevel ( No, I don't have any money. , 102 )
else
puppetbevel ( No, I won't give you any money. , 102 )
puppetbevel ( Yes, here is the money. , 101 )
playercash = playercash - 5
endif
puppetbevel ( No. , 102 )
arg = puppetevent ( - 1 )
switch arg
case - 1
exitcode
case 101
puppetspeak ( jenix.10 )
sendtoactor ( JENIX , putdownactor ( ) )
actorowner ( JENIX , gavemoney )
case 102
puppetspeak ( jenix.11 )
actorstar ( JENIX , town.extra @ numtostring ( random ( 3 ) ) )
endswitch
endcode
Dialogue Lines
If we look at the Puppet file this script was taken from, we can find all the dialogue of that character in Block 0:
jenix.2 : They're yours!
jenix.3 : No, thank you.
jenix.4 : I couldn't...
jenix.5 : Excuse me, stranger, excuse me.
jenix.6 : I was just seeing if there were some old vittles and such.
jenix.8 : Since Karl died, I don't know how I'm going to feed them.
Some of the variables that are referenced in our script by flag A are the identifiers of the dialogue lines from Block 0. We can use this to make the script even more readable:
PRAIRIE WOMAN JENIX DAY 1
code runyoself ( )
local arg
global playercash , jenixphase
puppetclear ( )
if jenixphase = 0
jenixphase = 1
puppetspeak ( jenix.5: "Excuse me, stranger, excuse me." )
puppetspeak ( jenix.6: "I was just seeing if there were some old vittles and such." )
puppetspeak ( jenix.7: "In the slop pail...for my boys." )
puppetspeak ( jenix.8: "Since Karl died, I don't know how I'm going to feed them." )
endif
puppetspeak ( jenix.9: "Stranger, you got any money? Just five dollars, maybe?" )
puppetclear ( )
if playercash < 5
puppetbevel ( No, I don't have any money. , 102 )
else
puppetbevel ( No, I won't give you any money. , 102 )
puppetbevel ( Yes, here is the money. , 101 )
playercash = playercash - 5
endif
puppetbevel ( No. , 102 )
arg = puppetevent ( - 1 )
switch arg
case - 1
exitcode
case 101
puppetspeak ( jenix.10: "Thank you, stranger. My boys, they thank you, too." )
sendtoactor ( JENIX , putdownactor ( ) )
actorowner ( JENIX , gavemoney )
case 102
puppetspeak ( jenix.11: "Of course. I am sorry to trouble you." )
actorstar ( JENIX , town.extra @ numtostring ( random ( 3 ) ) )
endswitch
endcode
Final Thoughts
The moment that I put two and two together and figured out that the lookup table in the executable was connected to these script files, I knew I’d found something very important. I had uncovered all the game logic of Dust, which means that rebuilding the game had become viable.
After letting it sink in, I realized that not only was I now able to read the game logic, I could change it as well. Maybe I could uncover things that weren’t meant to be seen…
…
or maybe
…
just maybe
…
I could set my inner-child free and make a windmill-sized Leroy!

Leave a comment