Oddworld Forums

Oddworld Forums (http://www.oddworldforums.net/index.php)
-   Oddworld Mods & Hacks (http://www.oddworldforums.net/forumdisplay.php?f=24)
-   -   Save file format (http://www.oddworldforums.net/showthread.php?t=19745)

LIJI 10-16-2010 08:30 AM

Save file format
 
Hey! I've been researching a bit, and I've found out a lot of details about the .sav format.
A screenshot is included to demonstrate what I can do so far. Do not hope for a .sav based level editor, but a really advanced .sav editor might come. :)

So far I have documented:
The flashing landmine object. I get it to activate, deactivate, trigger an explosion, and change its pattern to an all-red one.
Mudokuns are partially documented, and I can change their position, direction, scaling, coloring etc.
Abe is partially documented, it's in a slightly different format than other muds.
"Blockers" (Like on the screenshot) are partially documented too. I haven't managed find their object sizes.

Limitations:
Some objects, like landmines and blockers, can't be moved to any position, but to pre-set positions defined by the levels.

NovaMan 10-16-2010 08:35 AM

Can i replace a mudokon with a scrab in the save file???


Can i replace a paramite with mine car???

LIJI 10-16-2010 08:38 AM

It might be possible. It should be even possible to add objects. Or delete them.

lismati 10-16-2010 09:29 AM

Why one mudokon in the background is bigger than the second one?

LIJI 10-16-2010 09:36 AM

For the same reason there are two blue-colored Muds, testing. :)

Chubfish 10-16-2010 11:33 AM

Very, very nice work LIJI! Good to see you back too, unless you came back earlier, which I didn't notice...

Crashpunk 10-16-2010 02:05 PM

Ohh snap thats amazing!

Phylum 10-16-2010 02:22 PM

:

()
Can i replace a mudokon with a scrab in the save file???

Yes, but it would crash or be unusable. Scrabs would probably be unresponsive to the objects they aren't programmed to respond to (duh). Alternatley, the 2 objects on the same screen would cause the game to crash.

Also, I don't believe that there are scrab sprites in the mines .lvl file (although in other areas it may be possible).

mlg man 10-16-2010 06:28 PM

I recently made a program to Open the save files and show the Exact background the save was on, what level, abes position, how many mudokons on screen. there was much more, but it was too glitchy for a release. It was pretty cool though.

Chubfish 10-17-2010 02:25 AM

:

()
Also, I don't believe that there are scrab sprites in the mines .lvl file (although in other areas it may be possible).

I was thinking about that. At least we have ways of putting those files into a .lvl file.

LIJI 10-17-2010 10:12 AM

Here's the format of the area I'm researching of the .sav file:
All addresses are in decimal, values are in hex.
The area starts at 1372.
It is a list of objects, each having a different size, terminated by 0000 0000. There are 3 types of objects; dynamic, static, and semi-static. The list only covers dynamic and semi static. Some objects can be deleted by simply deleting them! (Despite changing the file size!) It might be possible to add objects by inserting them.

Some of the objects I've documented:

All values are 16 bits unless otherwise specified.

:

Flashing Landmine (24 bytes)
Type: Semi-static
ID (8F00 0000)
POSITION ID (32bit)
TIME (32 bit; the offset in the pattern, in frames)
State (0-1 is on, 2 is trigger explosion, 3 is inactive, 4+ is all red)

Mudokun (136 bytes)
Type: Dynamic
ID (5100 0000)
unknown
unknown
X
unknown
Y
//+24 bytes
Scaling (32bit)
Red
Green
Blue
Direction
Animation
//+96 bytes
GivesRedRing
//+118 bytes
Mood

Abe (216 bytes)
Always the first object. The first 136 are the same as a normal Mudokun, with the exception of the ID being 4500 0000.

Path Blocker (8 bytes)
Type: Semi-Static
ID (7A00 0000)
PositionID (32Bit)

Wheel (16 Bytes)
Type: Semi-Static
ID (9400 0000)
PositionID (32Bit)

Platform (28 Bytes)
Type: Dynamic
ID (4E00 0000)
unknown
X
unknown
Y

Flying Slig (184 Bytes)
Type: Dynamic
ID (3600 0000)
Unknown
X
Unknown
Y
//+56 bytes
Speed? (8 bits, change to 1 if possessing)
//+144 bytes
Possession (8 bits, change to 0C to possess)

Paramite (128 bytes)
Type: Dynamic
ID (6000 0000)
Unknown
X
Unknown
Y
//+26 bytes
Scale (32 bits)
Red
Green
Blue
//+118 bytes
Possession (Change to 41 to possess)

Bird Portal (8 bytes)
Type: Semi-Static
ID (6300 0000)
PositionID (32Bit)

Edit: found out PositionID also includes the scaling.
Edit 2: Added Bird Portal object

Paul 10-17-2010 10:21 AM

This is really cool, I hope you manage to figure out lots more :)

LIJI 10-17-2010 10:34 AM

Forgot to include info about the second list:

Followed by the terminator on the first list, 0000 0000, comes a list of sublists that represents information on some static and semi-static objects.
The list is delimited by 0400. Sometimes a delimiter might be another number, which might mean something about the following sublist(s) .The order of screens is probably defined the the .lvl, and has nothing to do with the CAM name.

Each sublist consists of two bytes, where each two bytes match an object. (Not sure which types of objects yet, but it surely includes static objects and some/all semi-static ones). The order of objects is probably defined the the .lvl.

The first byte is usually either 0 or 2, where 0 is exists and 2 is destroyed. The second byte only affects some of the objects, and it is a parameter. For example, fart vending machines use it to represent their counter, where (1-31 (decimal) represents the number itself, 32+ represents 0, and 0 represents the default value defined for the machine in the .lvl file)

Some objects (Like animated backgrounds and levers) can not be destroyed by putting 02 on them, however the last object can ALWAYS be deleted by deleting two bytes from the sublist. (I.e. resizing the file)

Paul 10-17-2010 11:22 AM

When moving from the screen before the one in your screen shot the game script loads these:


4F = load_work_wheel
3C = load_lcd_font
21 = nop
4d = load_wdrop_splash
20 = load_slig
31 = load_mud
55 = load_slam_door
58 = ?
11 = load_switch_lever
21 = nop
06 = ?
06 ?
61 = load_STATUSLT.BAN
06 ?
02 = abe_hoist
02 = abe_hoist
02 = abe_hoist
03 = abe_hoist2
03 = abe_hoist2
03 = abe_hoist2
3C = load_lcd_font
30 = ?
06 ?
06 ?
3D = load_stone_spotlite
55 = load_slam_door
31 = load_mud
31 = load_mud
3C = load_lcd_font
19 = load_ubx_blow
19 = load_ubx_blow
31 = load_mud
3C = load_lcd_font
1C = load_portal
55 = load_slam_door
32 = nop
40 = load_lcd_font2
02 =
06 ?
06 ?
06 ?
2E = nop
21 = nop


Each number is 32bit hex. The names are the names of functions I've named based on what BND/BAN file names are used in them. It comes from the path, strange how they don't seem to match which what you have for portals etc? Maybe you can double check for these magic numbers in the save file?

Edit: I got it, there is another function for loading save files, maybe it dosent contain part of the path file after all :( I the type Id is also an index into a list of functions that loads data, I'm just collecting al list of what each type is for a save in the screen shot screen :)

Edit again:

Here is the list and order my save gets loaded.

45 = load_state_savefile (sets tons of vars)
51 = load_mud_savefile
51 = load_mud_savefile
51 = load_mud_savefile
51 = load_mud_savefile
8F = load_bomb_savefile
8F = load_bomb_savefile
51 = load_mud_savefile
63 = load_portal_savefile
7A = load_slamdoor_savefile

LIJI 10-17-2010 11:48 AM

I checked these values in quite a lot saves and lvl files. The full list I have: (Including objects which doesn't have documented sizes yet)
8F Mine
45 Abe
51 Mud
7A Blocker
94 Wheel
2D Fart
4E Platform
36 Flying Slig
60 Paramite
7D Slig
63 Portal

Why would it load slam door? There's only one door in this path, and it can't be slammed anyway. How these numbers are stored? Are they just followed by each other? (I.e. 4F00 0000 3C00 0000 ...)

Paul 10-17-2010 11:56 AM

Purely because thats the name of those doors, it loads "SLAM.BAN"? So I assume the name is slam door? Also the game seems to add the Id as the length to get to the next item/Id? Not checked but that's what the function is seems to be doing:

.text:004C9BF1 more_save_items:
.text:004C9BF1 xor eax, eax
.text:004C9BF3 push ebp
.text:004C9BF4 mov ax, [ebp+0] ; Type, 51 = mud, and so on
.text:004C9BF8 call dword_560C34[eax*4] ; Call the function using the id an index
.text:004C9BFF add ebp, eax ; ebp is index in save file data, eax is D8 for type 51, which is added to get to the next object
.text:004C9C01 add esp, 4
.text:004C9C04 cmp dword ptr [ebp+0], 0 ; Keep looping until we get to id of 0
.text:004C9C08 jnz short more_save_items

Here is the captured sizes too:


45 size = D8
51 size = 88
8F size = 18
63 size = 08
7A size = 08


Strange that 63 and 7A are the same.

LIJI 10-17-2010 12:07 PM

From what I understand from my experiments, doors are not objects, (They're just like ledges, which are defined in the path file) but the object that blocks the door from being access is. (It's static I believe, not semi) I'm not completely sure, but that's what made me surprised about this.

The asm code is very interesting and useful, I might disassemble the exe myself too. The only thing that bothers me - 0xD8 (216) is the size for Abe, not Mudokuns, which is 0x88 (136). Are you sure about this number?

Paul 10-17-2010 12:25 PM

Ah, that must mean that:

45 size = D8 = abe
51 size = 88 = mud?

Type 45 sets tons of variables, maybe it contains lots of game state stuff too.

Edit: Doh of course it is, you've already said so in a previous post! I'm just wondering, do you know if the list of objects always starts at the same offsets? Should be pretty easy to parse the list or add new objects if thats true :D

Edit again: Must have stepped over or whatever in the code listing since its certainly wrong (the comment that is)

LIJI 10-17-2010 12:27 PM

Yes, Abe includes a lot of data. I'm pretty sure it also includes a copy for saved/killed Muds. And yes, as documented in my previous post, Abe is 45, and muds are 51. :)
I'm disassembling the code, I hope I can find something with my only recently gained asm skills. :)

Edit: 63 and 7A can have the same sizes, because each represent another object. 63 represents a bird portal, and 7A represents a path blocker.
Edit 2: Yes! It always starts at 1372! And that's what makes it VERY easy to parse once all information is gathered! :)

Paul 10-17-2010 12:49 PM

Some interesting path file info:

Search for 19000000 in the first path, you will find:

19000000730664058B067C050200C5040000000002000000

With a length of 1C, this is one of the UBX bombs. Right after this we have another UBX bomb:

19000000A5066405BD067C050200C504000000000200000000003000

.. And what after this? A mud!

310000008C066405A50678050000000000004D0100000000000001000000000000000100010000000000000000001C00

.. And now?

3C00000000067D05C406910547000000

3C is load_lcd_font..

The length of the type is before the Id, looks like a really good finding on Path format here :D

Edit: Yay, I'm 99% certain that this is it! How the hell did I never notice this before now? Its:

16bit, magic (04=end of screen)
16bit, length of object
32bit, object id
XXXX object data

LIJI 10-17-2010 01:22 PM

Paul you're awesome! Thanks you you I've figured out what PositionID is! It's a pointer to these! It has much more information than position then, it explains why I could move the mines to the Mud's position but not the path blocker!

Paul 10-17-2010 01:28 PM

:

()
Paul you're awesome! Thanks you you I've figured out what PositionID is! It's a pointer to these! It has much more information than position then, it explains why I could move the mines to the Mud's position but not the path blocker!

I'm pretty sure I can parse most of the path file now (for AE at least). It might just mean collecting some hard coded offsets. I think each screen must point somewhere in to the massive list of objects, then it loads them all until it hits 04 and it skips 03 (03 = already loaded).

Edit: If we can figure out exactly how to link these things and what the params mean then it should be fairly simple to edit objects in any level :)

LIJI 10-17-2010 01:42 PM

The first mine has the position ID 1015 0101 (0x01011015) in the save file. The second one has 2C15 0101. The difference between them is 1C, which is exactly the difference between them in bytes in the path file, which means position ID is some kind of an offset.

So far, given a Path file without any previous information, we could parse the screen array and the collision map (although we don't know where it ends). I believe the next step is to find the delimiter for the collision map.

Edit: Figured it out! The last short in the collision mask structure is mostly random, but when it's 6400, it means "end". Right after that comes your list, in your format. You can see five Muds over there (And the LCD sign, which also includes its position and other data btw, and the ledges.)! :) This is screen MIP1C36 BTW, the topmost and leftmost screen in the path.

Paul 10-17-2010 02:13 PM

:

()
The first mine has the position ID 1015 0101 (0x01011015) in the save file. The second one has 2C15 0101. The difference between them is 1C, which is exactly the difference between them in bytes in the path file, which means position ID is some kind of an offset.

So far, given a Path file without any previous information, we could parse the screen array and the collision map (although we don't know where it ends). I believe the next step is to find the delimiter for the collision map.

Edit: Figured it out! The last short in the collision mask structure is mostly random, but when it's 6400, it means "end". Right after that comes your list, in your format. You can see five Muds over there (And the LCD sign, which also includes its position and other data btw, and the ledges.)! :) This is screen MIP1C36 BTW, the topmost and leftmost screen in the path.


Seems I already wrote an app that does most of this path stuff, this is how it calcs where to start for a given screen

// S1 to S2
std::cout << "\nS1 to S2\n";
ParsePath( &data[0], 0, 3, 1, -1 );
ParsePath( &data[0], 0, 2, 1, -1 );

// S2 to S3
std::cout << "\nS2 to S3\n";
ParsePath( &data[0], 0, 4, 1, -1 );
ParsePath( &data[0], 0, 3, 1, -1 );

// S3 to S4
std::cout << "\nS3 to S4\n";
ParsePath( &data[0], 0, 5, 1, -1 );
ParsePath( &data[0], 0, 4, 1, -1 );

The 2nd arg is most important, this is used with a magic offset to get the starting pos in the path for the objects in the current screen. The game seems to always load one ahead which must be why you hear sounds for things in the next screen

Edit: the screen number seems to be the index into the array of screen names too, for 0 in R1 in AO it gets no objects, since the first screen is null

Edit: Confirmed that is certainly it :D That means I can figure out which objects are where for which screen but it requires the hard coded offset data. I guess the main thing to figure out is where does the array of screens end, unless its just a case of checking for null and then none null / none valid screen?

Edit: Boom headshot!

To know where the index table starts, subtract the cam name array size from the end of the file, to know where the end of the collision data is use the "lowest" offset from the index table :)

I can certainly write a path file parser now :D Woo!

Final edit: Well.. it looks like the collision data will be the only way to know how the screens fit together.. no idea how you know which collision data is for what screen, I think this is the final missing link (besides the params for each game object).

LIJI 10-17-2010 03:19 PM

"the screen number seems to be the index into the array of screen names too, for 0 in R1 in AO it gets no objects, since the first screen is null"
I'm not sure if I got it right, do you mean that the ID is the order in which the screen appears in the screen array? I.e. in AE mi it's "C36 -> 0, C1 -> 1, C9 -> 2 etc"?

Well, to match collision data with the screen, you should first find the position of the screen in the 2d screen array. For example, MIP1C36 is in (2,1) (0-based).
Then, multiply the x position by 375 (Screen width in units) and the y position by 260 (screen height in units) and you'll get it's starting point, (750,260) in game units. Add 375 and 260 and you'll get the ending point. So the rect for the screen is:
(750,260)-(1125,520). Then matching the collision mask should be easy. :)

Edit: Btw, I think that the parts of background which are displayed above Abe are stored as objects type 4B, can you confirm?
Edit2: Turns out that in the .sav file, the first 2 bytes are simply the offset from the beginning of the object list in the level file. No clue what the other two bytes are, but the game crashes if they change.

Crashpunk 10-18-2010 04:51 AM

That made my brain and my eyes hurt :D

Paul 10-18-2010 05:12 AM

:

()
That made my brain and my eyes hurt :D

It filled me with joy as path format is now pretty much reversed.

mlg man 10-18-2010 01:12 PM

So this means a custom path editor? Finaly?!!