Skip to content

Instantly share code, notes, and snippets.

@karmic64
Created January 27, 2022 16:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save karmic64/7c567d74c7d75e51602dba8490ed34a1 to your computer and use it in GitHub Desktop.
Save karmic64/7c567d74c7d75e51602dba8490ed34a1 to your computer and use it in GitHub Desktop.
Hydlide (NES) map ripper
/***
hydlide (nes) map ripper
coded by karmic, jan 27 2022
requires libpng (compile with "-lpng")
rom must be in the working directory and called "Hydlide (U) [!].nes"
***/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <setjmp.h>
#include <png.h>
/***
map writing subroutine is at $d4bb
map is written to a buffer row by row
screen pointers are at $ec00-$ec45
attribute writing subroutine is at $dc8d
attributes are written row by row, in steps of two columns
tile->attribute table is at $dfc4
the player starts on screen 0, the first 25 screens are the game map, arranged row by row, then the underground parts
***/
uint16_t inline get16(uint8_t *p) { return p[0] | (p[1]<<8); }
#define SCREENSIZE (0x16)
uint8_t screenbuf[SCREENSIZE][SCREENSIZE];
png_color inline getcolor(uint32_t c)
{
png_color co;
co.red = (c>>16)&0xff;
co.green = (c>>8)&0xff;
co.blue = (c>>0)&0xff;
return co;
}
const uint32_t palette[] = { 0xFF666666, 0xFF002A88, 0xFF1412A7, 0xFF3B00A4, 0xFF5C007E, 0xFF6E0040, 0xFF6C0600, 0xFF561D00, 0xFF333500, 0xFF0B4800, 0xFF005200, 0xFF004F08, 0xFF00404D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFADADAD, 0xFF155FD9, 0xFF4240FF, 0xFF7527FE, 0xFFA01ACC, 0xFFB71E7B, 0xFFB53120, 0xFF994E00, 0xFF6B6D00, 0xFF388700, 0xFF0C9300, 0xFF008F32, 0xFF007C8D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFEFF, 0xFF64B0FF, 0xFF9290FF, 0xFFC676FF, 0xFFF36AFF, 0xFFFE6ECC, 0xFFFE8170, 0xFFEA9E22, 0xFFBCBE00, 0xFF88D800, 0xFF5CE430, 0xFF45E082, 0xFF48CDDE, 0xFF4F4F4F, 0xFF000000, 0xFF000000, 0xFFFFFEFF, 0xFFC0DFFF, 0xFFD3D2FF, 0xFFE8C8FF, 0xFFFBC2FF, 0xFFFEC4EA, 0xFFFECCC5, 0xFFF7D8A5, 0xFFE4E594, 0xFFCFEF96, 0xFFBDF4AB, 0xFFB3F3CC, 0xFFB5EBF2, 0xFFB8B8B8, 0xFF000000, 0xFF000000 };
#define BGCOLOR (0x0f)
const uint8_t gamepal[] = {0x2a,0x18,0x1a, 0x14,0x28,0x30, 0x10,0x00,0x12, 0x00,0x10,0x2a};
#define PNGPALSIZE (4*3 + 1)
png_color pngpal[PNGPALSIZE];
#define BMPSIZE (SCREENSIZE*8)
png_byte bmp[BMPSIZE][BMPSIZE];
png_bytep bmprows[BMPSIZE];
uint8_t prg[0x8000];
uint8_t chr[0x2000];
int main()
{
FILE *f = fopen("Hydlide (U) [!].nes","rb");
if (!f)
{
perror("rom open error");
return EXIT_FAILURE;
}
fseek(f,0x10,SEEK_SET);
fread(prg,1,sizeof(prg),f);
fread(chr,1,sizeof(chr),f);
fclose(f);
pngpal[0] = getcolor(palette[BGCOLOR]);
for (int i = 0; i < sizeof(gamepal); i++)
pngpal[i+1] = getcolor(palette[gamepal[i]]);
for (int i = 0; i < BMPSIZE; i++)
bmprows[i] = bmp[i];
for (int screen = 0; screen < (0x46/2); screen++)
{
/* screen */
memset(screenbuf,0x40,sizeof(screenbuf));
uint8_t *p = prg+get16(prg+0x6c00+(screen*2))-0x8000;
uint8_t *sp = (uint8_t*)screenbuf;
unsigned repcnt = 0;
unsigned tile;
while (sp < (uint8_t*)screenbuf+sizeof(screenbuf))
{
if (!repcnt)
{
unsigned b = *p++;
repcnt = b & 0x0f;
if (!repcnt) repcnt = 0x10;
tile = b >> 4;
}
if (tile < 9)
*sp = tile;
else if (tile < 0x0d)
{
unsigned t = (tile-9)*4 + 9;
sp[0] = t;
sp[1] = t+1;
sp[0 + SCREENSIZE] = t+2;
sp[1 + SCREENSIZE] = t+3;
}
else if (tile < 0x0e)
{
sp[0] = 0x19;
sp[1] = 0x1a;
sp[2] = 0x1b;
sp[3] = 0x1c;
sp[SCREENSIZE + 0] = 0x1d;
sp[SCREENSIZE + 1] = 0x1e;
sp[SCREENSIZE + 2] = 0x1f;
sp[SCREENSIZE + 3] = 0x20;
sp[SCREENSIZE*2 + 0] = 0x21;
sp[SCREENSIZE*2 + 1] = 0x22;
sp[SCREENSIZE*2 + 2] = 0x23;
sp[SCREENSIZE*2 + 3] = 0x24;
sp[SCREENSIZE*3 + 0] = 0x25;
sp[SCREENSIZE*3 + 1] = 0x26;
sp[SCREENSIZE*3 + 2] = 0x27;
sp[SCREENSIZE*3 + 3] = 0x28;
}
--repcnt;
sp++;
}
/* image */
memset(bmp,0,sizeof(bmp));
for (int ytile = 0; ytile < SCREENSIZE; ytile++)
{
for (int xtile = 0; xtile < SCREENSIZE; xtile++)
{
unsigned t = screenbuf[ytile][xtile];
unsigned attr = prg[0x5fc4 + t];
uint8_t *td = &chr[0x1000+(t*0x10)];
for (int yf = 0; yf < 8; yf++)
{
for (int xf = 0; xf < 8; xf++)
{
unsigned x = (xtile*8)+xf;
unsigned y = (ytile*8)+yf;
unsigned tm = (0x80)>>xf;
unsigned tc = (td[yf] & tm ? 1 : 0) | (td[yf+8] & tm ? 2 : 0);
unsigned c = !tc ? 0 : (attr*3)+(tc-1)+1;
bmp[y][x] = c;
}
}
}
}
/* output png */
char fnam[16];
sprintf(fnam, "%02i.png",screen);
void pngerr(png_structp p, const char *e)
{
fprintf(stderr,"%s libpng error: %s\n",fnam,e);
}
void pngwarn(png_structp p, const char *e)
{
fprintf(stderr,"%s libpng warning: %s\n",fnam,e);
}
FILE *f = fopen(fnam,"wb");
if (!f)
{
fprintf(stderr, "%s open error: %s\n", fnam,strerror(errno));
continue;
}
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,pngerr,pngwarn);
if (!png_ptr)
{
fprintf(stderr,"%s png init error\n", fnam);
fclose(f);
continue;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
{
fprintf(stderr,"%s png info init error\n", fnam);
png_destroy_write_struct(&png_ptr,NULL);
fclose(f);
continue;
}
if (setjmp(png_jmpbuf(png_ptr)))
{
}
else
{
png_init_io(png_ptr,f);
png_set_IHDR(png_ptr,info_ptr, BMPSIZE,BMPSIZE, 4,PNG_COLOR_TYPE_PALETTE,
PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT);
png_set_PLTE(png_ptr,info_ptr,pngpal,PNGPALSIZE);
png_write_info(png_ptr,info_ptr);
png_set_packing(png_ptr);
png_write_image(png_ptr,bmprows);
png_write_end(png_ptr,info_ptr);
fprintf(stderr, "exported %s\n",fnam);
}
png_destroy_write_struct(&png_ptr,&info_ptr);
fclose(f);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment