i know c# and .net and dabbled in c, but not that experienced in c. i might have thought someone would have done this, or perhaps i need to think about it myself.. If i knew the information about the position of graphics within the ram and the subpallet information then i could rip the sprites, 16x16 does make it very easy, i assume spirtes are built from multiple 16x16 sprites to build up background graphics etc?
Well background graphics use 8x8 cells. Completely different from the sprite format too, not just size. In your case, that's a good thing (because you can visually identify what section of vram are for sprites and what are for tiles). But yes, all sprites are made from 16x16 cells. An entry in the SAT is defined in size by Xn and Yn cells. I.e. A 32x16 sprite is 2x1 cell in the SAT(B).
Do a straight conversion of all 64k vram to bg tile sheet, sprite cell sheet, then parse the SAT/SATB (both) and build a sheet from that. On the tile and sprite bitmaps of vram, maybe even build one set per subpalette entry. That way the first two layers (in all 32 subpalettes, but only 16 for tiles and a separate 16 for sprites), you can visually identify any sprites cells of animation that stay in vram. Sometimes it's easier to rip just those with them showing on the correct subpalette. Other times it's best to uses the parse SAT bitmap layer. Hell, maybe even provide X/Y coords for each SAT entry on another layer. Because some sprite animations are a conjunction of layered sprites and will still need manual cut/pasting - if you want to capture them as a single frame of pseudo sprite animation. Parsing the SAT also allows to you turn off any sprite at any time, or change their X/Y coords if you have overlapping sprites messin up your clean rip. Anyway, whatever's easiest for you to work with.
As for the parsing the save states, mednafen is compressed with 7z - so they need to be decompressed. ME has an option for compression or not (or last I checked it did). And for hardware, pce's graphics layer and structure is very simplistic in design. Nothing convoluted or tricky (unless you've never dealt with planar pixels before). all sprites have a special bounding address they must be at and same with tiles. I.e. the BAT (tilemap layer) can only point to vram in tile length segments. You can't point to segments less than 32 bytes - because you take the BAT entry, shift the 16bit value to the left 4 places. This gives you all 0's in the lower end. A la every 32bytes. SAT is similar, but wider than 32bytes. The BAT is always at $0000 of vram and the SATB is always pointed by the SATB pointer reg. Find that reg in the save state, and you'll find the location of the SATB in vram ($0000-7ffff but these are WORDs addresses (unsigned short)). The SAT itself is somewhere in the save state too. Make sure to build out both though (SAT and SATB), because one can be out of sync with the other depending on the point in which the save state is made and any given game. For technical information of what the SAT(B) and BAT entries look like/do - take a look at the Magickit assembler documents (sprites.txt and vdc.txt are the files you're looking for).
There's no file for BAT and tiles, so I'll explain that here. The BAT is the tilemap. It *always* starts at address $0000. You can't change it. That's easy enough. How you look at the tilemap depends on how the MAWR register is set. This tells you how many BAT entries to read in a row, before skipping down to the next row. That's important because if you assume 32 entries when it's set to 64 wide entries by the MAWR reg, it will look wrong. You could do a build/bitmap of the whole BG layer like this. You can also apply X/Y coords from the BXR and BYR regs of the VDC, but most cases this isn't practical because games are usually changing it mid screen - and depending on where the save state stops at, could anyway and give a 'false' location (like a menu bar at the top or bottom of the screen). The BAT entry is one WORD (unsigned short). The lower 12bits are the tile number. That number * 32 gives the byte offset (or num *16 for WORD offset). Remember to AND off the top 4 bits before doing that <_<; The upper 4bits is the palette number for the tile. So SubPalette=BAT_Entry>>12. When you get a pixel from a tile, and the value is *not* zero, then multiply the subpalette by 16, then add the tile color number: if (tile_color !=0) { index=(SubPalette*16)+tile_color; } else { index=0; }. Since this is a tile, you use that index to point into the array of WORDs in palette ram. There are 512 WORD entries in palette ram, but only the first 256 entries are for tiles. So _RGB=PaletteRam[index*2]. The 16bit entry is as follows: xxxxxxxGGGRRRBBB. X= don't care. To get the correct color on a PC display, take each three bits and multiply them by 36. Don't forget to shift right on RRR and GGG (all the way to the right) before multiplying. This'll get you the 8bit for each R/G/B for PC compatible display. And lastly, the tileformat itself. A tile is 8x8 pixels. Each pixel is 4bits. So colors 0 to 15 (remember color 0 in any subpalette is index 0). The 4bits are stored in planar format. And to make things complicated, it's called composite planar format (i.e. two 2bit tiles make a 4bit complete tile).
If a tile is at memory address 0x800 (WORD offset):
First two planes: (0x800 offset)
(D0->D15)
row 0: 0000000011111111
row 1: 0000000011111111
row 2: 0000000011111111
etc
(+0x10 WORD offset from base tile address 0x800 = 0x810)
last two planes:
(D0->D15)
row 0: 2222222233333333
row 1: 2222222233333333
row 2: 2222222233333333
etc
0=plane 0. 1=plane 1. 2= plane 2. 3=plane 3. The plane is the pixel in a 1bit field. Like all binary, bits in higher places(to the left) hold larger values than bits in smaller places (to the right).
4bit pixel = D3:D2:D1:D0. Or better put: 4bit pixel = P3:P2:P1:P0. (P=plane bit)
Eight rows tall makes 8x8 pixels. Your job is to collect all 4 bits and shift them into the correct places. So a 4bit tile color for pixel 0 of row 0 is... D3=row0:D8 2nd offset,D2=row0:D0, D1=row0:D8 1st offset,D0=row0:D0 1st offset. So for row 1, pixel 7:
base offset:
row 0: 0000000011111111
row 1: 000000(D0)0111111(D1)1
row 2: 0000000011111111
base offset+0x10 WORDs:
row 0: 2222222233333333
row 1: 222222(D2)2333333(D3)3
row 2: 2222222233333333
As you already know, x86 is little endian. So is the VDC. So that's the good news. But that doesn't mean it's necessarily stored in little endian or even in WORDs. Might be completely in unsigned INT format. Really depends on the emulator. Also remember, you're not randomly access pixels here. You're reading them out in sequential fashion for conversion. Which makes it easier, so you should store each plane of each row into four variables. Then shift out each variable left once for each sequential pixel for that row. Code would look similar to this:
int plane0;
int plane1;
int plane2;
int plane3;
// at some point ZERO out all four planes before reading in a byte
// Also remember - little endian. So read the WORD as two reversed bytes
fread(&plane1,1,1,infile);
fread(&plane0,1,1,infile);
fseek(TileAddress+0x20,SEEK_SET);
fread(&plane3,1,1,infile);
fread(&plane2,1,1,infile);
for(int i;i<8;i++)
{
TileIndex[n+i]=0;
plane0<<=1;
plane1<<=1;
plane2<<=1;
plane3<<=1;
TileIndex[n+i] |= (plane0 & 0x0100) ? 0x01 | 0x00;
TileIndex[n+i] |= (plane0 & 0x0100) ? 0x02 | 0x00;
TileIndex[n+i] |= (plane0 & 0x0100) ? 0x04 | 0x00;
TileIndex[n+i] |= (plane0 & 0x0100) ? 0x08 | 0x00;
}
The logic for
var3 = (var0) ? var1 | var2 is short hand C for:
If(plane0 && 0x0100)
{ TileIndex[n+i] = TileIndex[n+i] | 0x01; }
else { TileIndex[n+i] = TileIndex[n+i] | 0x00; }
Of course my example above only reads/converts 1 row of 8pixels. You'd need another outer loop and file stream reposition logic to read in the rest of the rows are you go down the 8x8 tiles. So
n=_row*8 in TileIndex[n+i].
Thankfully, sprites aren't stored in this 'composite' format. But they still planar. Stacked planes just like above, just not interleaved. And in rows of 16 pixels, not 8. So you'll have to read a WORD (short) in at a time, not a byte. If vram *is* stored in INT format in the save state, then I recommend just reading in the 64k to a 32k SHORT array (be sure to convert it to proper SHORT format first). That'll get rid of those pesky I/O handlers and directly use indexes.
I know you said sprites, but sometimes the BG layer is used as a large sprite in games. So it's good to know. Hope this helps. It's good to have a customer build like this for ripping, because it gives you more control. But, some people are happy just to grab
every frame with layers disabled and
manually cut/paste.