#๐Ÿ”’ Binary editor for DS game saves

70 messages ยท Page 1 of 1 (latest)

minor mirageBOT
#

@mystic juniper

Python help channel opened

Remember to:

  • Ask your Python question, not if you can ask or if there's an expert who can help.
  • Show a code sample as text (rather than a screenshot) and the error message, if you've got one.
  • Explain what you expect to happen and what actually happens.

:warning: Do not pip install anything that isn't related to your question, especially if asked to over DMs.

mystic juniper
#

whoops, .sav isn't allowed here, sorry. here's the original message:

so, i've been looking at making a save editor for ds games for a while now. i'm not really sure if python is the best candidate for this, but it's all i have any experience with.
as far as i know, most ds games save their player data in binary format, completely up to the developers of each game (see file for an example, Need for Speed ProStreet, i have this one mostly mapped out in a spreadsheet).
i've messed with it some time ago, wrote a terminal save viewer specifically for ProStreet, but the menu and navigation system was hard for me to wrap my head around, and i abandoned it. all i have that's relevant are a script that updates the save's checksum (so i can modify things for testing), and a script that converts the game's textures to a standard format, like png (that one isn't related to saves though).
i'd love some guidance on how to properly start a foundation for an editor that can support more than one game (a plugin system? maybe). i know it'll be a lot of manual work to add support for each game, and it's all very niche... i just think it's neat

sick stream
#

"binary format" can mean anything (technically everything is binary data, "binary format" is used for anything that can't be read as text - even .png is "binary format"), so unless you actually know the specs of the save file, there's nothing we can do

#

It may be just some kind of memory dump of the objects, it may be a more structured form but encoded in some way...

mystic juniper
sick stream
#

Even for such simple thing as numbers, could be big endian or little endian... And that changes a lot

>>> int.from_bytes(b'\x01\x02')
258
>>> int.from_bytes(b'\x01\x02',byteorder='little')
513

And that's for 2-byte test, numbers are usually stored as 4-byte

mystic juniper
mystic juniper
#

and seems to be signed numbers even where it doesn't make any sense, for prostreet at least

robust vapor
#

The main way to figure something like this out would be to change some values in the save file, load it, and see what changed

sick stream
robust vapor
#

and rinse and repeat

#

every ds save game is different

#

it depends on the game

mystic juniper
#

that is what i've done so far

foggy tapir
robust vapor
#

pokemon saves are pretty much solved for example with pkhex but if you have something that nobody else has done before, i wish you luck

mystic juniper
#

i'm looking at how to make a "foundation", upon which support for individual games can be added

robust vapor
#

flip some bits/values and see what happens

mystic juniper
#

universal i guess

mystic juniper
robust vapor
#

try to be deterministic

#

as in leaving everything the same except one change

#

and see what changes in the save file after re-saving in game

#

run a diff

foggy tapir
sick stream
# foggy tapir That might just be because C ints are signed by default.

With unsigned, you might also end up with negative overflow which is not as easily fixed as with "if <0, then make it 0" ๐Ÿ˜‰

I've once played a game in alpha where a weak mob would one-shot high-geared players because after the calculations of all defense etc damage would end up negative... only for it to overflow

mystic juniper
foggy tapir
#

It helps with the common binary types.

mystic juniper
#

some of the values (mainly car config) is not aligned to the byte

foggy tapir
#

Awkward. You'll have to do some special stuff yourself there.

#

But that might be: grab a binary chunk, turn it into a big int, shift bits off it as needed. very use case dependent.

mystic juniper
#

there are five levels of parts for each part, between two bytes, the first part could be 00011111 00000000 and the next part 11100000 00000011 , and so on (i think i formatted that properly)

#

where each 1 represents whether a certain level for a certain part is owned

foggy tapir
#

Right. A bitmap.

mystic juniper
#

ah

#

that name makes sense

#

support for each game on its own is one thing, but what about the platform to build this support onto? or should they all be their own independent scripts?

#

i don't even know if i want a gui or not

foggy tapir
#

Personally I'd probably have a module (.py file) per game, to hold the binary format and the tools to decode 9and maybe encode) it.

#

I can't advise on GUIs, I find them very cumbersome.

#

Maybe make a dsgame package, with a module per game format, and modules for the front end (GUI of command line) etc.

#

I don't suppose each save file has some identifier for the game in some nice standard way? So that you can pick one up and know which decoder to use?

mystic juniper
#

the few i've looked at so far have some data that never seems to change

#

prostreet's seems to be a 4 byte magic at the beginning

#

E7 B1 10 4B, not sure if it's supposed to represent anything in particular

foggy tapir
halcyon pewter
mystic juniper
#

my script for updating the checksum, checks the file length and magic before touching anything else

foggy tapir
#

I was just imagining having a bunch of classes, one per game-specific format, and a factory function which could open a file, look at the header/magic, and pick the right class for the rest of the decode/

mystic juniper
#

as opposed to altering anything else in the file, which makes the checksum not match anymore, and the game warns about corruption instead

foggy tapir
#

You definitely want the magic header. Typically there will be a fixed part which essentially denotes "this is a DS game save" and then an additional variable part which says something like "this is for game X", or even "game X, version Y". And so forth.

#

Of course, there might be nothing very formal. I have in the back of my mind that once upon a tim the "binary save" of a M$ Word document was just a dump of Word's memory. Ghastly and unportable.

mystic juniper
#

they don't seem to have a standard header for all ds games

foggy tapir
#

Not even the very beginning? Alas.

#

I know nothing about these files, BTW. Just binary encode/decode in general.

halcyon pewter
#

Save files on most consoles are usually proprietary and company-dependent iirc.

mystic juniper
#

yeah, the beginning is completely different between prostreet and, say, indy 500 legends

#

they use different checksum methods

#

prostreet's is per-save slot so you can lose one to corruption but keep the other, indy's is across the entire save, so you'd lose all of it

#

prostreet's just counts all the bytes in a fancy way, indy's is crc32

#

super mario 64 ds seems to keep two copies of all data, from what i remember

#

no consistency

mystic juniper
#

i'm sure there are some consoles where all the games' saves follow a standard

#

the ds is not one of them

halcyon pewter
#

I'd imagine that even if they do the standards aren't public so it's pretty much up to reverse engineering to do its magic.

minor mirageBOT
#
Python help channel closed

This help channel has been closed and it's no longer possible to send messages here. If your question wasn't answered, feel free to create a new post in #1035199133436354600. To maximize your chances of getting a response, check out this guide on asking good questions.