For my own amusement, I dumped the files of an old Nintendo DS video game I have (the company has long since gone bankrupt/closed) and am trying to extract the assets. I have recovered game text, videos, and sound, but one 58-MB file by the name of graph.dat, which contains the game's graphics (probably) is giving me a hard time.

Importantly, the game's text was stored in a custom format which the developers appear to have created for the game, so I strongly suspect graph.dat to contain a custom graphics format as well.

I was able to break the graphics file into segments by reading metadata records left in a separate file, graph.dti. The metadata file contained 1910 records, separated by 44 chunks of intermediate data. Each record listed off a set of contiguous addresses in graph.dat, which yielded three binary data blobs once chunked out of that file.

The first blob in each record, which I've nicknamed 'Huey', might be some sort of header. It's always either 32 bytes or 512 bytes long.

The second blob, 'Dewey', is of variable length, ranging from 704 bytes to 49 KB in size. I suspect it contains the actual image data.

The third blob, 'Louie', is always 1536 bytes in size.

I suspect 'Huey' may be color palette data, and 'Louie' is some sort of color index, but I can't be sure.

Does anyone have any ideas? Tossing 'Dewey' into GIMP's raw data import was spectacularly unsuccessful.

Link to a sample of four different images (I'm assuming) from the file. Filenames were provided by my unpacker tool and were not present in the original metadata. I have 1,906 more of these where this came from, so just ask if you need them.

  • You can try rawpixels.net, it often helped me with unknown graphic files (only works for non-indexed images).
    – Overblade
    Nov 16 '19 at 11:45

I'm not sure what the 'louie' files are for, but this python script should help reconstruct the images:

import png

# simple scale from [0,0x1f] to [0,0xff]
def scale_up(n):
  return (n<<3)|n

def make_pal(n):
  val = (n[1]<<8)|n[0]
  return [scale_up((val>>0)&0x1f), scale_up((val>>5)&0x1f), scale_up((val>>10)&0x1f)]




with open(palette_file, 'rb') as f:
  pal_bytes = f.read()

for i in range(0,256):
  m = make_pal(pal_bytes[i*2:i*2+2])

with open(pixel_data_file, 'rb') as f:
  pixel_bytes = f.read()

tile_w = 8
tile_h = 8
image_tile_h = h/tile_h
image_tile_w = w/tile_w

# if any pixel/channel is not written, the png writer will complain about -1
rows_dat = [[-1 for n in range(w*3)] for m in range(h)]
for i in range(w*h):
  tile_i = int(i / (tile_w*tile_h))
  tile_x = int(tile_i % image_tile_w)
  tile_y = int(tile_i / image_tile_w)
  small_i = int(i % (tile_w*tile_h))
  small_x = int(small_i % tile_w)
  small_y = int(small_i / tile_w)
  rows_dat[tile_y*tile_h + small_y][3*(tile_x*tile_w + small_x)] = real_pal[pixel_bytes[i]][0]
  rows_dat[tile_y*tile_h + small_y][3*(tile_x*tile_w + small_x)+1] = real_pal[pixel_bytes[i]][1]
  rows_dat[tile_y*tile_h + small_y][3*(tile_x*tile_w + small_x)+2] = real_pal[pixel_bytes[i]][2]

with open(f'{image_base}.png', 'wb') as of:
  writer = png.Writer(width=w, height=h, greyscale=False)
  writer.write(of, rows_dat)

In your example data, it looks like img_159_9_0_dewey.bin is padded at the start with 64 bytes of 0x00's. I'm not sure if this was an error in dumping it, but removing that helps.

All the images you provided seem to be 256x192 (I left them rotated, the tile logic is clearer without unrotating - you can do this after), made up of 8x8 tiles of pixels. The pixel data (stored in dewey) indexes into a palette of RGB555 values stored as u16le (stored in huey). You didn't provide any, but I guess the 32 byte huey files are just palettes of 16 colors - the corresponding pixel data may consist of tightly packed 4-bit indexes.

  • You've been a great help. Thank you.
    – SoItBegins
    Nov 17 '19 at 10:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.