Saving gfx memory with MAME Debugger - "Unable to find CPU ':gfx'

Started by Hop, February 13, 2016, 12:11:58 PM

Previous topic - Next topic

Hop

I'm trying to save memory from sf2hfj from the "gfx" region of memory using the MAME debugger. I can save memory from the ':maincpu' region easily e.g.

save mem.bin,0,20000

This saves the memory as show in the Memory Window with Region: ':maincpu' selected.

If I change the dropdown from "Region ':maincpu' " to "Region: ':gfx' " then I see the memory I want to save, but I don't know how to specify that I want to save that memory in the command line. I've tried

save gfx.bin,0,20000,:gfx

but this fails with:

Unable to find CPU ':gfx'

Does anybody know how to perform this task?

mz

The fourth parameter seems to be for CPU, not memory region. Unless this game uses multiple CPUs, you shouldn't put anything there.

Now, I would look into the sf2hfj driver source code and see how's the memory map of this game. So, if the "gfx" region of memory is at 60000h or something, you would then use "save mem.bin, 60000, 20000".

Maybe... I'm just speculating here, since I've never used this feature. :D
There has to be a better life.

Hop

Thanks for the response. I agree that the fourth parameter would appear to be a CPU id of some sort. The reason I thought that the save command might accept a "memory region"(?) id is because of the this:

>saved test,0,10
No matching memory space found for CPU ':maincpu'

This made me think that ':gfx' might also be a "CPU" name.


I have been looking at mame\src\mame\drivers\cps1.cpp  but I can't see how the gfx roms map into address space. This is what I have figured out (with my added comments)

/* B-Board 91634B-2 */
ROM_START( sf2hfj )
   // 3 x 512KiB code ROM files
   // 0x80000 = 524288 = 512KiB
   // 3 x 0x80000 = 0x180000 bytes of code from address 0 in "maincpu" address space
   ROM_REGION( CODE_SIZE, "maincpu", 0 )      /* 68000 code */
   ROM_LOAD16_WORD_SWAP( "s2tj_23.8f", 0x000000, 0x80000, CRC(ea73b4dc) SHA1(efbc73277d00bac86505755db35225e14ea25a36) )
   ROM_LOAD16_WORD_SWAP( "s2tj_22.7f", 0x080000, 0x80000, CRC(aea6e035) SHA1(ce5fe961b2c1c95d231d1235bfc03b47de489f2a) )   // == s2te_22.7f
   ROM_LOAD16_WORD_SWAP( "s2tj_21.6f", 0x100000, 0x80000, CRC(fd200288) SHA1(3817b67ab77c7b3d4a573a63f18671bea6905e26) )   // == s2te_21.6f
   
   // gfx ROMS
   // ========
   // length = 0x600000 = 6.00MB (12x512KB) of graphics ROM in "gfx" address space
   // The gfx roms are loaded into memory in sets of 4, interleaved by word (2 bytes). NOT word swapped.
   // This can be seen by looking at the offsets which are in groups of 4:
   //      {0x000000, 0x000002, 0x000004, 0x000006}
   //      {0x200000, 0x200002, 0x200004, 0x200006}
   //      {0x400000, 0x400002, 0x400004, 0x400006}
   // So if for example
   //   s92_01.3a is full of words 1122
   //   s92_02.4a is full of words 3344
   //   s92_03.5a is full of words 5566
   //   s92_04.6a is full of words 7788
   // then gfx memory will look like: 11 22 33 44 55 66 77 88 11 22 33 44 55 66 77 88 all the way up to 0x1fffff at which point the next 4 files
   // will start off in the same interleaved pattern
   ROM_REGION( 0x600000, "gfx", 0 )
   ROMX_LOAD( "s92_01.3a",  0x000000, 0x80000, CRC(03b0d852) SHA1(f370f25c96ad2b94f8c53d6b7139100285a25bef) , ROM_GROUPWORD | ROM_SKIP(6) )    // == s92-1m.3a
   ROMX_LOAD( "s92_02.4a",  0x000002, 0x80000, CRC(840289ec) SHA1(2fb42a242f60ba7e74009b5a90eb26e035ba1e82) , ROM_GROUPWORD | ROM_SKIP(6) )    // == s92-3m.5a
   ROMX_LOAD( "s92_03.5a",  0x000004, 0x80000, CRC(cdb5f027) SHA1(4c7d944fef200fdfcaf57758b901b5511188ed2e) , ROM_GROUPWORD | ROM_SKIP(6) )    // == s92-2m.4a
   ROMX_LOAD( "s92_04.6a",  0x000006, 0x80000, CRC(e2799472) SHA1(27d3796429338d82a8de246a0ea06dd487a87768) , ROM_GROUPWORD | ROM_SKIP(6) )    // == s92-4m.6a

   ROMX_LOAD( "s92_05.7a",  0x200000, 0x80000, CRC(ba8a2761) SHA1(4b696d66c51611e43522bed752654314e76d33b6) , ROM_GROUPWORD | ROM_SKIP(6) )    // == s92-5m.7a
   ROMX_LOAD( "s92_06.8a",  0x200002, 0x80000, CRC(e584bfb5) SHA1(ebdf1f5e2638eed3a65dda82b1ed9151a355f4c9) , ROM_GROUPWORD | ROM_SKIP(6) )    // == s92-7m.9a
   ROMX_LOAD( "s92_07.9a",  0x200004, 0x80000, CRC(21e3f87d) SHA1(4a4961bb68c3a1ce15f9d393d9c03ecb2466cc29) , ROM_GROUPWORD | ROM_SKIP(6) )    // == s92-6m.8a
   ROMX_LOAD( "s92_08.10a", 0x200006, 0x80000, CRC(befc47df) SHA1(520390420da3a0271ba90b0a933e65143265e5cf) , ROM_GROUPWORD | ROM_SKIP(6) )    // == s92-8m.10a

   ROMX_LOAD( "s2t_10.3c",  0x400000, 0x80000, CRC(3c042686) SHA1(307e1ca8ad0b11f3265b7e5467ba4c90f90ec97f) , ROM_GROUPWORD | ROM_SKIP(6) )
   ROMX_LOAD( "s2t_11.4c",  0x400002, 0x80000, CRC(8b7e7183) SHA1(c8eaedfbddbf0b83311d2dbb9e19a1efef0dffa9) , ROM_GROUPWORD | ROM_SKIP(6) )
   ROMX_LOAD( "s2t_12.5c",  0x400004, 0x80000, CRC(293c888c) SHA1(5992ea9aa90fdd8b9dacca9d2a1fdaf25ac2cb65) , ROM_GROUPWORD | ROM_SKIP(6) )
   ROMX_LOAD( "s2t_13.6c",  0x400006, 0x80000, CRC(842b35a4) SHA1(35864a140a0c8d76501e69b2e01bc4ad76f27909) , ROM_GROUPWORD | ROM_SKIP(6) )
   
   ROM_REGION( 0x18000, "audiocpu", 0 ) /* 64k for the audio CPU (+banks) */
   ROM_LOAD( "s92_09.12a",  0x00000, 0x08000, CRC(08f6b60e) SHA1(8258fcaca4ac419312531eec67079b97f471179c) )   // == s92_09.11a
   ROM_CONTINUE(            0x10000, 0x08000 )

   ROM_REGION( 0x40000, "oki", 0 ) /* Samples */
   ROM_LOAD( "s92_18.11c",  0x00000, 0x20000, CRC(7f162009) SHA1(346bf42992b4c36c593e21901e22c87ae4a7d86d) )
   ROM_LOAD( "s92_19.12c",  0x20000, 0x20000, CRC(beade53f) SHA1(277c397dc12752719ec6b47d2224750bd1c07f79) )

   ROM_REGION( 0x0200, "aboardplds", 0 )
   ROM_LOAD( "buf1",         0x0000, 0x0117, CRC(eb122de7) SHA1(b26b5bfe258e3e184f069719f9fd008d6b8f6b9b) )
   ROM_LOAD( "ioa1",         0x0000, 0x0117, CRC(59c7ee3b) SHA1(fbb887c5b4f5cb8df77cec710eaac2985bc482a6) )
   ROM_LOAD( "prg1",         0x0000, 0x0117, CRC(f1129744) SHA1(a5300f301c1a08a7da768f0773fa0fe3f683b237) )
   ROM_LOAD( "rom1",         0x0000, 0x0117, CRC(41dc73b9) SHA1(7d4c9f1693c821fbf84e32dd6ef62ddf14967845) )
   ROM_LOAD( "sou1",         0x0000, 0x0117, CRC(84f4b2fe) SHA1(dcc9e86cc36316fe42eace02d6df75d08bc8bb6d) )

   ROM_REGION( 0x0200, "bboardplds", 0 )
   ROM_LOAD( "s9263b.1a",    0x0000, 0x0117, CRC(0a7ecfe0) SHA1(f75e7eed4604fcf68273197fe3dd7f0d7a313ada) )
   ROM_LOAD( "iob1.12d",     0x0000, 0x0117, CRC(3abc0700) SHA1(973043aa46ec6d5d1db20dc9d5937005a0f9f6ae) )
   ROM_LOAD( "bprg1.11d",    0x0000, 0x0117, CRC(31793da7) SHA1(400fa7ac517421c978c1ee7773c30b9ed0c5d3f3) )

   ROM_REGION( 0x0200, "cboardplds", 0 )
   ROM_LOAD( "ioc1.ic7",     0x0000, 0x0117, CRC(0d182081) SHA1(475b3d417785da4bc512cce2b274bb00d4cc6792) )
   ROM_LOAD( "c632.ic1",     0x0000, 0x0117, CRC(0fbd9270) SHA1(d7e737b20c44d41e29ca94be56114b31934dde81) )
ROM_END


This file doesn't seem to give the addresses that the gfx roms load into. I've wrote some C code to load the 3 x 4 sets of graphics ROM files and build the final buffer in memory, which matches the contents of the MAME debugger Memory: Region ':gfx' exactly. (Ref http://www.romhacking.net/forum/index.php?topic=13172.0), but I think there simply must be a way to save/dump the memory from :gfx from within the debugger.


I've also looked in the filemame\src\mame\video\cps1.cpp which shows:

#define mapper_S9263B   { 0x8000, 0x8000, 0x8000, 0 }, mapper_S9263B_table
static const struct gfx_range mapper_S9263B_table[] =
{
   // verified from PAL dump:
   // FIXME there is some problem with this dump since pin 14 is never enabled
   // instead of being the same as pin 15 as expected
   // bank0 = pin 19 (ROMs 1,3) & pin 18 (ROMs 2,4)
   // bank1 = pin 17 (ROMs 5,7) & pin 16 (ROMs 6,8)
   // bank2 = pin 15 (ROMs 10,12) & pin 14 (ROMs 11,13)
   // pins 12 and 13 are the same as 14 and 15

   /* type            start    end      bank */
   { GFXTYPE_SPRITES, 0x00000, 0x07fff, 0 },

   { GFXTYPE_SPRITES, 0x08000, 0x0ffff, 1 },

   { GFXTYPE_SPRITES, 0x10000, 0x11fff, 2 },
   { GFXTYPE_SCROLL3, 0x02000, 0x03fff, 2 },
   { GFXTYPE_SCROLL1, 0x04000, 0x04fff, 2 },
   { GFXTYPE_SCROLL2, 0x05000, 0x07fff, 2 },
   { 0 }
};


static const struct CPS1config cps1_config_table[]={
   ...
   {"sf2hfj",      CPS_B_21_DEF, mapper_S9263B, 0x36 },
}


But I'm not sure how to interpret this.


mz

Quote from: Hop on February 13, 2016, 06:23:43 PMThis file doesn't seem to give the addresses that the gfx roms load into.
Have you tried this one already?
Quote from: Hop on February 13, 2016, 06:23:43 PMROM_REGION( 0x600000, "gfx", 0 )
That looks like it... So I would try with "save mem.bin, 0x600000, 20000", if you haven't already.

(It's also surprisingly similar to what I randomly said in my previous post...)
There has to be a better life.

Hop

Sorry for not answering directly before. The command "save mem.bin, 600000, 200000"  completes but the output file is completely full of zeroes. This is what I'd expect because the contents of "M68000 ':maincpu' program space memory" in the Memory Window is zero from 0x600000 to to 0x800000. From the definitions


#define ROM_START(name)                             static const rom_entry ROM_NAME(name)[] = {
#define ROM_REGION(length,tag,flags)                { tag, NULL, 0, length, ROMENTRYTYPE_REGION | (flags) },
#define ROM_END                                     { NULL, NULL, 0, 0, ROMENTRYTYPE_END } };


it would look like ROM_REGION( 0x600000, "gfx", 0 ) defines a region with length 0x600000 rather than address 0x600000.

mz

Quote from: Hop on February 14, 2016, 03:24:12 AMit would look like ROM_REGION( 0x600000, "gfx", 0 ) defines a region with length 0x600000 rather than address 0x600000.
It does look like that.

In that case, the line "ROM_REGION( CODE_SIZE, "maincpu", 0 )" should give you the "gfx" address. Just find what that CODE_SIZE constant is, and replace it in "save mem.bin, CODE_SIZE, 20000".

(If this is still wrong, maybe I should just set up MAME and get that game, but I'm kind of busy right now.  :()
There has to be a better life.

Hop

Quote from: mz on February 14, 2016, 03:35:30 AM
In that case, the line "ROM_REGION( CODE_SIZE, "maincpu", 0 )" should give you the "gfx" address. Just find what that CODE_SIZE constant is, and replace it in "save mem.bin, CODE_SIZE, 20000".

It would make sense if the regions are mapped contiguously in address space.

#define CODE_SIZE 0x400000

But looking at or saving memory memory at this address it is all zeroes. It would seem that the "gfx" region ( and all of the other regions: e.g. "audiocpu", "oki") have their own address space.

mz

Last suggestion: copy the first few bytes of the gfx region, for example 0xabcdef0123456789 or something, then use the cheat search functionality to find those bytes in memory. That should give you the address you're looking for...

(If the cheat search functionality doesn't let you search for enough bytes or within that region, dump all the memory and search with a hex editor.)
There has to be a better life.

Hop

I think I've tried this by running a "find". Is this equivalent?

The gfx region starts with a load of FF FF FF.. but at address 0x160 we have something searchable: 03 63 63 03 ...  but

f 0,800000,d.03636303            ---> Not Found
f 900000,700000,d.03636303  ---> Not Found

Note that running a find or save in the region from 800000 crashes MAME. It looks like this is because it is trying to read from a memory mapped IO write only address.

-----------------------------------------------------
Exception at EIP=000000000251F0A3 (ioport_port::read()+0x0023): ACCESS VIOLATION
While attempting to read memory at 0000000000000008
-----------------------------------------------------
RAX=0000000000000000 RBX=0000000000000008 RCX=0000000000000000 RDX=0000000000000006
RSI=000000001275AEA0 RDI=0000000000000000 RBP=00000000002267E0 RSP=0000000000226760
R8=1F96B0E5106A9E57  R9=C6A4A7935BD1E995 R10=C6A4A7935BD1E995 R11=00000000002267B0
R12=0000000000000000 R13=0000000000226960 R14=0000000000000001 R15=0000000000800140
-----------------------------------------------------
Stack crawl:
  0000000000226780: 000000000251F0A3 (ioport_port::read()+0x0023)
...


February 14, 2016, 04:36:58 AM - (Auto Merged - Double Posts are not allowed before 7 days.)

As an experiment I tried changing the first 2 words of Region: ':maincpu' in the memory window to 1234 5678 then running f 0,100000,d.12345678 gives "Found at 000000"

However changing the first 4 bytes of Region: ':gfx' in the memory window to 12 34 56 78 then running f 0,800000,d.12345678 or f 900000,700000,d.12345678 both return"Not found"

mz

Quote from: Hop on February 14, 2016, 04:29:11 AMAs an experiment I tried changing the first 2 words of Region: ':maincpu' in the memory window to 1234 5678 then running f 0,100000,d.12345678 gives "Found at 000000"

However changing the first 4 bytes of Region: ':gfx' in the memory window to 12 34 56 78 then running f 0,800000,d.12345678 or f 900000,700000,d.12345678 both return"Not found"
I see. Well, I don't know much about MAME and not a lot of people use it here, since you can't run hacked ROMs on it...

I'd now recommend the obvious stuff, such as asking in some official MAME or MESS forums.

Also, the people at http://www.mamecheat.co.uk/forums/ should have some experience with finding stuff in memory.

If you ever find an answer, please share it with us!
There has to be a better life.

Hop

Thanks for your help. I'll report back when I've figured this out.

AWJ

MAME developer here. Unfortunately, what you're trying to do isn't supported by MAME. There's no debugger command to dump an arbitrary ROM region to a file. You can only dump CPU address spaces, and the graphics ROMs on CPS1 (like most arcade hardware) aren't mapped into any CPU's address space. Graphics ROMs in arcade machines are like CHR ROM on the NES: the CPU doesn't need to (and usually can't) read them, they're directly connected to the graphics hardware.

If you want the graphics ROMs concatenated into a single file so you can load it up in a tile editor, just write a python script or something to do it. Actually, I see you've already figured that out :)

Hop

Thanks for the information. I'm slowly beginning to understand how it all works.

Am I correct in thinking that after the game is up and running in MAME, editing the gfx region of memory in the debugger will have no effect on the actual in-game graphics because at that point the data has already been decoded into another (MAME) buffer? If so, is there a way to ask MAME to re-decode?

AWJ

Quote from: Hop on February 17, 2016, 04:36:46 AM
Thanks for the information. I'm slowly beginning to understand how it all works.

Am I correct in thinking that after the game is up and running in MAME, editing the gfx region of memory in the debugger will have no effect on the actual in-game graphics because at that point the data has already been decoded into another (MAME) buffer? If so, is there a way to ask MAME to re-decode?

Yes, that's correct for drivers that use MAME's built-in sprite and tilemap primitives to emulate the graphics (which includes cps1, and every driver that has a block of GFXDECODE macros in the source and that lets you see the game's graphics by pressing F4) No, there isn't a debugger command to force the graphics to be re-decoded. And before you ask, there isn't any way to save changes you make to the ROMs using the debugger hex editor either. The MAME debugger is generally more targetted toward developing new drivers and fixing emulation bugs than at facilitating ROM hacking.

Hop

I'm more interested in finding out how the data is stored and decoded that hacking the ROM. I'll step through the code that uses the data defined in the GFXDECODE macros to figure out how the data is stored/coded.  It would be helpful to be able to see any changes I make in realtime to confirm that my understanding is correct.

Can I ask: What defines where the 8x8, 16x16, 32x32 gfx data is stored?

AWJ

Quote from: Hop on February 17, 2016, 05:09:01 AM
I'm more interested in finding out how the data is stored and decoded that hacking the ROM. I'll step through the code that uses the data defined in the GFXDECODE macros to figure out how the data is stored/coded.

Oh, I can just explain that to you. I'll use CPS1's 16x16 layout (the sprites and one of the three scrolling layers) as an example and break down what each line in the gfx_layout struct means:

static const gfx_layout cps1_layout16x16 =
{
        16,16,
        RGN_FRAC(1,1),
        4,
        { 24, 16, 8, 0 },
        { STEP8(0, 1), STEP8(32, 1) },
        { STEP16(0, 4*16) },
        4*16*16
};


16,16, <- this is the size in pixels, i.e. 16x16

RGN_FRAC(1,1), <- this determines the total number of available tiles as a function of the ROM region size... don't worry about it

4 <- this is the bit depth, 4bpp (16 colors)

{ 24, 16, 8, 0 }, <- this is the bit position, relative to the start of a tile, where each bit plane starts. In this case the bit planes are 8 bits apart, meaning the graphics are planar like NES or SNES graphics. Packed pixels like Megadrive or GBA would be { 0, 1, 2, 3 }.

{ STEP8(0, 1), STEP8(32, 1) }, <- this is the bit position where each column of pixels starts. STEP8(x, y) is a macro that means "8 values starting at x and going up by y each time". So the first (leftmost) 8 columns are packed into the first byte (1 bit apart), and the second 8 columns come four bytes (32 bits) after and are likewise packed into a byte.

{ STEP16(0, 4*16) }, <- this is the bit position where each row starts. Unlike the columns, all the rows are evenly spaced, each eight bytes (4*16 bits) apart.

4*16*16 <- this is the distance in bits between consecutive tiles. Most often it's simply the size of a tile, in this case 4*16*16 bits.

Hop

Wow. That's great. Many thanks. What defines where the memory for each of the different sets is starts? So for example knowing the 16x16 layout, where does the actual 16x16 data start in ROM/memory?

AWJ

Quote from: Hop on February 17, 2016, 06:08:02 AM
Wow. That's great. Many thanks. What defines where the memory for each of the different sets is starts? So for example knowing the 16x16 layout, where does the actual 16x16 data start in ROM/memory?

With CPS1, that's... apparently rather complicated and varies per game. Have a look at cps_state::gfxrom_bank_mapper().

Hop

Thanks for the info. I've got some code up and running that loads the roms and displays the 8x8 gfx data. I'll extend it to the other sets later. I then came across MAME decode() function which does the same thing!

Quotegraphics ROMs on CPS1 (like most arcade hardware) aren't mapped into any CPU's address space. Graphics ROMs in arcade machines are like CHR ROM on the NES: the CPU doesn't need to (and usually can't) read them, they're directly connected to the graphics hardware.

This is very interesting. I'd never really thought that the CPU would not have access to the data. I guess the video hardware would have to be used to perform pixel perfect collision detection, and any tile metadata would have to be defined in the program address space.

I was also expecting to see palette data in the ROMs, but it looks like this is stored in the CODE and put in place as and when required - it seemed a little strange having the colour indices and the colours themselves in two different regions of memory, but I guess it gives allows for palette effects (like the fades I've seen in sf2).

QuoteThe MAME debugger is generally more targetted toward developing new drivers and fixing emulation bugs than at facilitating ROM hacking.

Does this imply that any pull requests made that don't move MAME in this direction would be rejected?

AWJ

Quote from: Hop on February 18, 2016, 07:21:15 AM
Thanks for the info. I've got some code up and running that loads the roms and displays the 8x8 gfx data. I'll extend it to the other sets later. I then came across MAME decode() function which does the same thing!

This is very interesting. I'd never really thought that the CPU would not have access to the data. I guess the video hardware would have to be used to perform pixel perfect collision detection, and any tile metadata would have to be defined in the program address space.

I was also expecting to see palette data in the ROMs, but it looks like this is stored in the CODE and put in place as and when required - it seemed a little strange having the colour indices and the colours themselves in two different regions of memory, but I guess it gives allows for palette effects (like the fades I've seen in sf2).

Did you notice how all the 8x8 tiles are duplicated twice? Once in the pair of ROMs that contains the left halves of the 16x16 tiles (and the first and third quarters of the 32x32 tiles) and once in the other pair of ROMs. That's why the MAME driver specifies two 8x8 layouts that are offset by 32 bits but otherwise identical. It's a quirk of the CPS1 background generator hardware--"odd 8-pixel columns come from one pair of ROMs, even 8-pixel columns come from another pair" even applies to 8x8 tiles, meaning the same tiles have to be duplicated in both pairs of ROMs, otherwise the game could only display a particular tile at odd screen positions or at even positions depending on which ROMs it was in.

CPS1 (and most arcade hardware) doesn't do hardware collision detection. Collision detection is done in software and it's done with bounding boxes, not at the pixel level. Yes, the "graphics" ROMs contain nothing but pixel patterns; the colors and the metadata for assembling the tiles into characters and backgrounds are in the "program" ROMs. Some arcade hardware actually does have the background tilemaps in ROM (e.g. 8-bit Capcom games like 1943 and Gunsmoke) but in that case it's still in a separate set of ROMs from the pixel patterns. And older arcade hardware (again, 8-bit Capcom games being an example) does often have the color palette in ROM rather than in RAM, but again it's in dedicated ROMs (typically tiny 256x4-bit BPROMs) that are separate from the graphics ROMs. Since the colors are in ROM, those older games can't do fade-ins or other dynamic color manipulation.

Are you familiar at all with 2D console hacking (NES, SNES, GBA...)? Most 2D arcade hardware (at least hardware from the Japanese makers and Atari) is similar to 2D consoles in general principles, or at least a lot more similar to 2D consoles than to modern gaming platforms that you might be tempted to compare with (PC, Flash, Unity, mobile OSes) Specifically, most 2D arcade hardware is like a NES game with CHR ROM. Everything is tile-based; the CPU says "draw character #1234 at position (125, 63)" and doesn't know or care about the individual pixels at all.

QuoteDoes this imply that any pull requests made that don't move MAME in this direction would be rejected?

Not at all! It just means that the MAME debugger has a somewhat different feature set from console emulator debuggers, most of which were developed by and for ROM hackers.

Note that the features I listed (editing decoded graphics from the debugger, and saving modified data back to the ROMs) would be extremely difficult to implement in a way that worked with all drivers. The big obstacle to saving modified ROMs is that the ROM loading macros that say where each byte from each ROM goes don't always tell the whole story; the ROM data is often postprocessed in some way by the driver (usually to decrypt encrypted ROMs) In order to support saving back to the ROMs, every single driver that has a ROM decryption function would need to define an inverse function to reconstruct the encrypted data.