backup memory structure

Hey there...

I already posted this in another topic but it fits better in a dedicated one.

PLEASE DON'T HESITATE TO POST IF YOU FIND ANY ERRORS OR MISSING INFOS.

I will update it if something we don't know yet is posted (LAST EDIT : DECEMBER 14TH 2010).

For now, here are the infos we gathered (Thx again for contributions.) :

*****

Location : 0x00180000 - 0x0018FFFF (odd bytes only) (mirrored at 0x00190000-0x001FFFFF)

Size : 32768 bytes.

The whole area is divided into 512 blocks of 64 bytes each.

Blocks are labeled in order from Block#0000 to Block01FF.

Block#0000 is reserved for the backup memory header and must fit the following ascii string :

"BackUpRam FormatBackUpRam FormatBackUpRam FormatBackUpRam Format"

Block#0001 obviously acts as a separator block.

It cannot be used to store data and all its bytes must be set to 0x00.

Block#0002 - Block#01FF can be used to store game saves data.

The first double word of all these blocks is reserved for the block header and must fit one of the following values :

- 0x00000000 > if the block is unused or used but not the first of a game save

- 0x80000000 > if the block is the first of a game save

Each game save data may not always be stored to contiguous blocks, due to preceeding game saves writings.

Anyway, it must fit the following scheme (in addtion to all the previous rules), starting at the first available block :

block header :

- 0x80000000 > specifies this block is the 1st of a game save

game save header :

- 11 bytes > game save name

- 1 byte > game save language (mainly for multi-language games : 0x00=jap / 0x01=eng / 0x02=fra / 0x03=deu / 0x04=esp / 0x05=ita)

- 10 bytes > game save description

- 1 dword > game save time stamp (minutes elapsed since 1/1/1980 00:00:00), big endian

- 1 dword > game save data size, in bytes, big endian

- (word array) > only for game saves using more than 1 block : listing of blocks used by the game save in order, starting from the second one, in Block#, big endian (variable length)

- 0x0000 > separator

game save data :

- byte array > game save data (variable length)

*****

For example, the deza 2 default save looks like this in memory (even bytes included) :

block start

FF 80 FF 00 FF 00 FF 00 > block header

FF 44 FF 45 FF 5A FF 41 FF 32 FF 5F FF 5F FF 5F FF 53 FF 59 FF 53 > save name

FF 00 > save language

FF C3 FF DE FF BB FF DE FF 32 FF 5F FF BC FF BD FF C3 FF D1 > save description

FF 00 FF F7 FF 95 FF A4 > save timestamp

FF 00 FF 00 FF 00 FF 11 > save data size

> blocks listing skipped because only 1 used block

FF 00 FF 00 > separator

FF 1E FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 01 FF 02 FF 03 FF 05 FF 04 FF 01 FF 02 FF 03 FF 05 FF 04 > save data

FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 > unused block bytes

block end
 

ExCyber

Staff member
Is the checksum present on every block or just on the first block of each file?

I think it's worth noting that "F7" is "FF" with one bit clear (1111 0111). It could be a flag word or something along those lines.

SGM probably doesn't calculate the checksum itself, but rather creates the file through the backup library, which is stored in the BIOS and which licensed games use to read/write saves. However, that library is compressed and the game/app must decompress it to RAM in order to use it, so you can't just go look at the code in the ROM. It should be possible to get at it with the Yabause debugger (have a look at the address in 06000358 after the library's been initialized -- it's a pointer to an array of function pointers), but I'm a bit too occupied right now to give that a shot.
 

cafe-alpha

Established Member
Thank you for detailing the backup data format
smile.gif


Your unknown data seems to be a time stamp.

For more informations, I recommend you to read SBL backup library documentation : ST-162-R1-092994.

Also, the backup header format is detailled in SBL's SEGA_BUP.H, and it fits your description :

Code:
typedef struct BupDir{

	Uint8	filename[12];	/* ファイル名 */

	Uint8	comment[11];	/* コメント */

	Uint8	language;	/* コメントの言語種類 */

	Uint32	date;		/* タイムスタンプ */

	Uint32	datasize;	/* データサイズ(Byte) */

	Uint16	blocksize;	/* データサイズ(ブロック数) */

} BupDir;

About the time stamp: you can specify a date to a backup in a way like this:

Code:
    #include <SEGA_BUP.H>

    BupDir bupDir;

    BupDate datetb;

    (fill bupDir contents)

    datetb.month = ...

    datetb.day   = ...

    (etc)

    bupDir.date = BUP_SetDate(&datetb);

    BUP_Write(0/*Build-in memory*/, &bupDir, (unsigned char *)data, OFF/*overwrite OK*/);

BUP_SetDate converts a date structure to a 32bits value, so the separator above the "checksum" is actually used.

I tried with a dummy date value like this: bupDir.date = 0x12345678;

And I obtained the following result:

FF 80 FF 00 FF 00 FF 00 > block header

FF 54 FF 45 FF 53 FF 54 FF 31 FF 20 FF 20 FF 20 FF 20 FF 20 FF 20 > save name

FF 02 > backup language value (*)

FF 43 FF 4F FF 4D FF 4D FF 45 FF 4E FF 54 FF 20 FF 20 FF 20

FF 12 FF 34 FF 56 FF 78 > dummy time stamp

FF 00 FF 00 > separator

FF 00 FF 05 > save data size

FF 00 FF 00 > separator

FF 48 FF 45 FF 4C FF 4C FF 4F > SAVE DATA

(*) The backup language values are defined as below in SBL's SEGA_BUP.H :

/* 言語種類 */

#define BUP_JAPANESE (0)

#define BUP_ENGLISH (1)

#define BUP_FRANCAIS (2)

#define BUP_DEUTSCH (3)

#define BUP_ESPANOL (4)

#define BUP_ITALIANO (5)

Unfortunately, the BUP_SetDate function is a bios function, so I don't know how the date is converted.

However, by seeing some date/value couples, it may be easy to guess BUP_SetDate's algorithm.
 

cafe-alpha

Established Member
Here are some infos about the date format (still from SBL's SEGA_BUP.H):

BupDate structure:

Code:
typedef struct BupDate {

	Uint8	year;		/* タイムスタンプ(年 -1980) */ <- Year - 1980

	Uint8	month;		/* タイムスタンプ(月 1~12) */ <- Month (1~12)

	Uint8	day;		/* タイムスタンプ(日 1~31) */ <- Day (1~31)

	Uint8	time;		/* タイムスタンプ(時 0~23) */ <- Hour (0~23)

	Uint8	min;		/* タイムスタンプ(分 0~59) */ <- Minute (0~59)

	Uint8	week;		/* タイムスタンプ (曜日 日0~土6)*/ <- Day of the week (Sunday:0 ~ Saturday:6)

} BupDate;

Some date/time stamp values (taken from real hardware):

Display Format : YYYY/MM/DD HH:mm (day of week) => return value, hexadecimal (return value, decimal)

1980/ 0/ 0 0: 0 (0) => 0x008246A0 (8537760) <- All the date data set to zero

2010/11/10 0:47 (3) => 0x00F7A78F (16230287)

2010/11/10 1:47 (3) => 0x00F7A7CB (16230347)

2010/11/10 1:46 (3) => 0x00F7A7CA (16230346)

2010/11/10 1:46 (4) => 0x00F7A7CA (16230346)

2010/11/11 1:46 (4) => 0x00F7AD6A (16231786)

2010/12/11 1:46 (4) => 0x00F8562A (16274986)

2011/12/11 1:46 (4) => 0x01005B4A (16800586)

Have fun with reverse-engineering this ^^

If you want other date/values, don't hesitate to ask
smile.gif
 

antime

Extra Hard Mid Boss
The time stamps seem to be a minute count.

16231786 - 16230346 = 1440, 1440 = 60 * 24.

16800586 - 16274986 = 525600, 525600 = 1440 * 365.
 
Nice finds =]

A time stamp of course...

We still have to know how it's precisely calculated but a minute count seems to be a nice lead.

However, i'm then suprised that 1980/0/0 0:0 (0) doesn't return 0x00000000.

Good point about the language byte too (didn't notice that one).

It may be used by games which support multi-language saves.

The first post was edited and now includes up2date discoveries (thx guys).
 

cafe-alpha

Established Member
the_mad_joob said:
However, i'm then suprised that 1980/0/0 0:0 (0) doesn't return 0x00000000.

The first time stamp in my previous post was actually not valid, because month and day need to be counted from 1, and I set them to zero.

By testing with the valid "1980/1/1 0:0" date, BUP_SetDate returned zero.

It seems you can convert the date with the following formula (However, I don't know if it correctly works for leap year or so) :

u = BUP_SetDate(date) * 60 + 315529200 (*)

Where u is the unix time.

(*) 315529200 is the unix time for "1980/1/1 0:0"

Example with your save data :

u = 0xF795A4 * 60 + 315529200 = 1289071200

By converting the result above (for example, in this page), it seems to be dated from "2010/11/06 20:20".
 
First post updated.

Thx again for your time cafe-alpha.

You're the man once again...

So the backup mem is almost naked now =]

Why almost ?

Because i'm afraid there's one more thing to discover.

I found some strange incremental word arrays that don't belong to data areas.

I'll tell you more later cause right now my brain is too upside down because of hex editing (a bit of smoking too i must confess =]).

P.S. : cafe-alpha, are you french by any chance ?
 
the_mad_joob said:
So the backup mem is almost naked now =]

Why almost ?

Because i'm afraid there's one more thing to discover.

I found some strange incremental word arrays that don't belong to data areas.

When you create a file that's large enough to span multiple blocks, the block numbers must be stored. This is important in case the blocks are noncontiguous.
 

ExCyber

Staff member
TascoDLX said:
When you create a file that's large enough to span multiple blocks, the block numbers must be stored. This is important in case the blocks are noncontiguous.
It seems that there isn't a lot of free space in the header. Is it stored like a linked list (i.e. there's a "next block" field)?
 

cafe-alpha

Established Member
ExCyber said:
It seems that there isn't a lot of free space in the header. Is it stored like a linked list (i.e. there's a "next block" field)?

I tried to write/erase some long dummy data on backup RAM, and I could verify that the 2nd, 3rd~ block's header are all set to zero. (and the first block's header is set to 0x80000000)

Maybe the "strange incremental word arrays" are remains from deleted backup data ?

When deleting backup data, the data themselves are not set to zero, only the first block's header is set to zero.
 
ExCyber said:
It seems that there isn't a lot of free space in the header. Is it stored like a linked list (i.e. there's a "next block" field)?

It's just a ordinary zero-terminated list. There's no fixed header size -- the header will expand as necessary.

Example: a 4-block file starting at block 2 (note, blocks 0 and 1 are the reserved blocks).

Immediately following the data size in the file header:

0003 0005 0006 0000

Note, the first block (0002, in this case) is not listed.

So, the file data is contained in blocks 0002, 0003, 0005, and 0006, in that order.

BTW, the 4-byte block headers do not seem to count towards the data size stored in the file header.

cafe-alpha said:
I tried to write/erase some long dummy data on backup RAM, and I could verify that the 2nd, 3rd~ block's header are all set to zero. (and the first block's header is set to 0x80000000)

Maybe the "strange incremental word arrays" are remains from deleted backup data ?

When deleting backup data, the data themselves are not set to zero, only the first block's header is set to zero.

Apparently, no directory data is stored. In order to account for all blocks, the system has to scan for marked block headers to locate each file and scan each file's block list to determine which blocks (and how many) are in use.
 
Well done guys.

This seems to be clearer and clearer...

That means 512 words may be reserved for block informations in the whole backup memory.

That also explains the usage of blocks itself =].

Hey TascoDLX, in your example, i suppose block 4 was skipped because it was already used by another save.

You're right, when writing, those arrays must simply be scanned in order to know which is the next available block.

I'll add those juicy finds in the first post.

Looks like we're done here or what ?

Anyway, don't hesitate to post if you know more nasty bytes or find errors in my first post.

Thx again =]

P.S. to cafe-alpha : Le choix de la vidéo est plus que judicieux =].
 

antime

Extra Hard Mid Boss
the_mad_joob said:
However, i'm then suprised that 1980/0/0 0:0 (0) doesn't return 0x00000000.

If you take the 1980/0/0 value and go backwards from 1980/1/1 you get 1963/10/8. I wonder if that's an easter egg - the programmer's birthday or something similar.
 
Correction to the order of data and the reference year (epoch) in the first post:

block header :

- 0x80000000 > specifies this block is the 1st of a game save

game save header :

- 11 bytes > game save name

- 1 byte > game save language (mainly for multi-language games : 0x00=jap / 0x01=eng / 0x02=fra / 0x03=deu / 0x04=esp / 0x05=ita)

- 10 bytes > game save description

- 1 dword > game save time stamp, big endian (minutes elapsed since 1/1/1980 00:00:00)

- 0x0000 > separator

- 1 word > game save data size (in bytes), big endian

- (word array) > only for game saves using more than 1 block : listing of blocks used by game save in order, starting from the second one, big endian, length can vary


- 0x0000 > separator

game save data :

- game save data starts from here...

Also, for clarity, I'd say:

Size : 32768 bytes

Location : 0x00180000 - 0x0018FFFF (odd bytes only) [* mirrored at 0x00190000 - 0x001FFFFF]

Something like that.

antime said:
If you take the 1980/0/0 value and go backwards from 1980/1/1 you get 1963/10/8. I wonder if that's an easter egg - the programmer's birthday or something similar.

The value in question, 0x008246A0, corresponds to 1996/3/26 0:00, which pretty much coincides with the passing of Comet Hyakutake. As far as easter eggs go, that'd be my guess.
 

cafe-alpha

Established Member
Here is a correction about the header structure:

block header :

- 0x80000000 > specifies this block is the 1st of a game save

game save header :

- 11 bytes > game save name

- 1 byte > game save language (mainly for multi-language games : 0x00=jap / 0x01=eng / 0x02=fra / 0x03=deu / 0x04=esp / 0x05=ita)

- 10 bytes > game save description

- 1 dword > game save time stamp, big endian (minutes elapsed since 1/1/1980 00:00:00)

- 1 dword > game save data size, in bytes, big endian

- (word array) > only for game saves using more than 1 block : listing of blocks used by the game save in order, starting from the second one, big endian, length can vary

- 0x0000 > separator

game save data :

- byte array > game save data starts from here...

As described in the BupDir structure, the `datasize' field is a 32 bits value.

As Saturn internal backup memory size is smaller than 64KB, the upper 2 bytes are not used, but they may be used when data is saved to a backup memory card.

TascoDLX said:
The value in question, 0x008246A0, corresponds to 1996/3/26 0:00, which pretty much coincides with the passing of Comet Hyakutake. As far as easter eggs go, that'd be my guess.

Nice !!!

I just wanted to know what may happen in the case the programmer doesn't set the date field in a backup entry.

In the case the result wasn't zero, I wasn't expecting anything other than a value resulting from an unhandled date value.

I'm so sad I couldn't even find this by myself >_<
 

antime

Extra Hard Mid Boss
TascoDLX said:
The value in question, 0x008246A0, corresponds to 1996/3/26 0:00, which pretty much coincides with the passing of Comet Hyakutake. As far as easter eggs go, that'd be my guess.

The comet wasn't discovered until January 1996. That'd be hell of an easter egg...
 
Top