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. |
Can i replace a mudokon with a scrab in the save file???
Can i replace a paramite with mine car??? |
It might be possible. It should be even possible to add objects. Or delete them.
|
Why one mudokon in the background is bigger than the second one?
|
For the same reason there are two blue-colored Muds, testing. :)
|
Very, very nice work LIJI! Good to see you back too, unless you came back earlier, which I didn't notice...
|
Ohh snap thats amazing!
|
:
Also, I don't believe that there are scrab sprites in the mines .lvl file (although in other areas it may be possible). |
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.
|
:
|
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) Edit 2: Added Bird Portal object |
This is really cool, I hope you manage to figure out lots more :)
|
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) |
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 |
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 ...) |
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. |
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? |
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) |
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! :) |
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 |
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!
|
:
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 :) |
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). |
"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. |
That made my brain and my eyes hurt :D
|
:
|
So this means a custom path editor? Finaly?!!
|