/* Copyright (c) 2005 Ryan Rubley */ #include #include #include using namespace std; #define FREEIMAGE_LIB #include "FreeImage.h" #pragma comment(lib, "FreeImage.lib") #define NAMETABLE_WIDTH 32 #define NAMETABLE_HEIGHT 30 #define PALETTE_SIZE 16 #define ROM_SIZE 131072 #define NUM_AREAS 5 #define PATTERN_SIZE 8 #define PATTERNTABLE_SIZE 256 #define ROOM_WIDTH 256 #define ROOM_HEIGHT 240 #define MAP_SIZE 32 #define NUM_MAP_PIECES 23 #define SHOWSPRITES_WIDTH (PATTERN_SIZE * 5) #define SHOWSPRITES_HEIGHT (PATTERN_SIZE * 9) #define SHOWSPRIETS_XOFF PATTERN_SIZE #define SHOWSPRIETS_YOFF (PATTERN_SIZE * 3) #define SHOWSPRITES_NUM 150 #define PATTERN_FLIPHORZ 0x40 #define PATTERN_FLIPVERT 0x80 #define PATTERN_SPRITE 0x08 #define MAP_PTR(pg,ptr) (g_rom + ((pg) | ((ptr) & 0x3FFF))) struct EnemyMap { int type, sprite, xoffset, yoffset; }; struct AreaInfo { char *name; int *patterns; EnemyMap *enemy_sprites; int page_ofs, sprite_ptr, spritepos_ptr, palette_data, room_ptr, structure_ptr, item_data, tile_data; }; struct MapPiece { int x1, y1, x2, y2, area, alt_palette; }; FIBITMAP *allocate_nes_buffer(int width, int height, int transcolor); FIBITMAP *allocate_pattern_table(void); void load_pattern_tables(int *patterns); void load_pattern(FIBITMAP *fi, int x, int y, unsigned char *pattern); void load_room(FIBITMAP *fi, int x, int y, int roomx, int roomy, AreaInfo *info); void load_structure(int x, int y, int structure, int palette, int defpal, AreaInfo *info); void load_tile(int x, int y, int tile, int palette, int defpal, AreaInfo *info); void copy_pattern(FIBITMAP *fi, int x, int y, int clipleft, int cliptop, int clipright, int clipbottom, int pattern, int palette, int flags); void draw_background(FIBITMAP *fi, int roomleft, int roomtop); void draw_sprite(FIBITMAP *fi, int x, int y, int clipleft, int cliptop, int clipright, int clipbottom, int sprite, int paladj, int flagadj, AreaInfo *info); void draw_enemy(FIBITMAP *fi, int x, int y, int roomleft, int roomtop, int enemy, int paladj, AreaInfo *info); void draw_powerup(FIBITMAP *fi, int x, int y, int roomleft, int roomtop, int powerup); void draw_elevator(FIBITMAP *fi, int roomleft, int roomtop); void draw_door(FIBITMAP *fi, int roomleft, int roomtop, int onright, int palette); void draw_statues(FIBITMAP *fi, int roomleft, int roomtop); void draw_number(FIBITMAP *fi, int x, int y, int num, int color); void draw_digit(FIBITMAP *fi, int x, int y, int digit, int color); //pattern tables FIBITMAP *g_sprites, *g_background; //one name table unsigned char g_nametable[NAMETABLE_HEIGHT][NAMETABLE_WIDTH]; //a little more unpacked then how the NES actually would store an attribute table unsigned char g_attributes[NAMETABLE_HEIGHT / 2][NAMETABLE_WIDTH / 2]; //image & sprite palettes unsigned char g_palette[PALETTE_SIZE * 2]; //rom data unsigned char g_rom[ROM_SIZE]; //reveal lots of secrets int g_showsecrets; int main(int argc, char **argv) { //for setting up the pattern tables, these are in the metroid rom as several calls to a subroutine, not stored as actual data. int common_patterns[] = {0x15, 0x00, /*0x1B,*//* this pattern overwrites the suit with the suitless girl ones */ 0x14, 0x17, 0x18, 0x19, 0x16, -1}; int brinstar_patterns[] = {0x03, 0x04, 0x05, 0x06, 0x19, 0x16, -1}; int norfair_patterns[] = {0x04, 0x05, 0x07, 0x08, 0x09, 0x19, 0x16, -1}; int tourian_patterns[] = {0x05, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x1A, 0x1C, 0x19, 0x16, -1}; int kraid_patterns[] = {0x04, 0x05, 0x0A, 0x0F, 0x10, 0x11, 0x19, 0x16, -1}; int ridley_patterns[] = {0x04, 0x05, 0x0A, 0x12, 0x13, 0x19, 0x16, -1}; EnemyMap brinstar_enemies[] = {{2, 0x03, 0, 2}, {3, 0x1F, 0, 2}, {4, 0x27, 4, -4}, {5, 0x58}, {6, 0x63, 4}, {7, 0x66, -8, -8}, {100, 0x8F}, {-1, -1}}; EnemyMap norfair_enemies[] = {{0, 0x18, 4}, {2, 0x1D, 0, 4}, {6, 0x58}, {7, 0x66, -8, -8}, {11, 0x15, 0, -24}, {12, 0x27}, {13, 0x30, 0, -20}, {14, 0x42, 4}, {100, 0x21, 0, 4}, {-1, -1}}; EnemyMap tourian_enemies[] = {{0, 0x03, 0, -4}, {1, 0x04, 0, -4}, {101, 0x1D}, {200, 0x09, 0, -8}, {201, 0x0B, -8, -8}, {202, 0x0A, 0, -8}, {203, 0x0A, 0, -8}, {-1, -1}}; EnemyMap kraid_enemies[] = {{0, 0x1A, 2}, {1, 0x1D, 2, -8}, {3, 0x1F, 0, 2}, {4, 0x27, 4, -4}, {5, 0x58}, {7, 0x66, -8, -8}, {8, 0x91, 0, -8}, {100, 0x8F}, {-1, -1}}; EnemyMap ridley_enemies[] = {{0, 0x18, 4}, {2, 0x34, 2, -8}, {3, 0x31, 2}, {6, 0x58}, {7, 0x66, -8, -8}, {9, 0x03, -8, -11}, {12, 0x27}, {-1, -1}}; //most of these offsets are pulled from the tables at page+0x01560 AreaInfo area_info[NUM_AREAS] = { {"Brinstar", brinstar_patterns, brinstar_enemies, 0x04000, 0x05DE0, 0x05F0E, 0x06271, 0x06314, 0x06372, 0x063D6, 0x06EF0}, {"Norfair", norfair_patterns, norfair_enemies, 0x08000, 0x09C64, 0x09D78, 0x0A178, 0x0A21B, 0x0A277, 0x0A2D9, 0x0AEEC}, {"Tourian", tourian_patterns, tourian_enemies, 0x0C000, 0x0E42C, 0x0E540, 0x0E718, 0x0E7D1, 0x0E7FB, 0x0E83B, 0x0EE49}, {"Kraid", kraid_patterns, kraid_enemies, 0x10000, 0x11CF7, 0x11E25, 0x12155, 0x121D5, 0x1221F, 0x1226D, 0x12C32}, {"Ridley", ridley_patterns, ridley_enemies, 0x14000, 0x15BF0, 0x15D04, 0x160EB, 0x1617F, 0x161D3, 0x1620D, 0x16B23} }; //normally you must ride elevators or walk through special rooms with a palette-switch item to toggle the world state, //but this hard-coded table will cover the entire room table with the proper layout. MapPiece map_piece[NUM_MAP_PIECES] = { {3, 0, 12, 3, 0, 0}, {4, 4, 12, 6, 0, 0}, {11, 7, 12, 12, 0, 0}, {0, 13, 12, 15, 0, 0}, {0, 16, 7, 19, 0, 0}, {13, 0, 31, 8, 0, 1}, {13, 9, 23, 12, 0, 1}, {24, 9, 31, 12, 1, 0}, {13, 13, 31, 15, 1, 0}, {22, 16, 31, 17, 1, 0}, {30, 18, 31, 20, 1, 0}, {23, 21, 31, 22, 1, 0}, {24, 23, 31, 23, 1, 0}, {13, 16, 21, 17, 1, 1}, {13, 18, 29, 20, 1, 1}, {13, 21, 22, 23, 1, 1}, {0, 0, 2, 3, 2, 0}, {0, 4, 3, 6, 2, 0}, {0, 7, 10, 12, 2, 0}, {8, 16, 12, 19, 3, 0}, {0, 20, 12, 31, 3, 0}, {23, 23, 23, 23, 4, 0}, {13, 24, 31, 31, 4, 0} }; FILE *f = fopen("METROID.NES", "rb"); fseek(f, 16, SEEK_SET); fread(g_rom, ROM_SIZE, 1, f); fclose(f); FreeImage_Initialise(); for( g_showsecrets = 0; g_showsecrets <= 1; g_showsecrets++ ) { FIBITMAP *world = allocate_nes_buffer(MAP_SIZE * ROOM_WIDTH, MAP_SIZE * ROOM_HEIGHT, 0xFF); for( int m = 0; m < NUM_MAP_PIECES; m++ ) { g_sprites = allocate_pattern_table(); g_background = allocate_pattern_table(); AreaInfo *info = &area_info[map_piece[m].area]; load_pattern_tables(common_patterns); load_pattern_tables(info->patterns); memcpy(g_palette, g_rom + info->palette_data + 3, PALETTE_SIZE * 2); if( map_piece[m].alt_palette ) { memcpy(g_palette, g_rom + info->palette_data + 63, PALETTE_SIZE); memcpy(g_palette + PALETTE_SIZE + 4, g_rom + info->palette_data + 82, PALETTE_SIZE - 4); } for( int y = map_piece[m].y1; y <= map_piece[m].y2; y++ ) { for( int x = map_piece[m].x1; x <= map_piece[m].x2; x++ ) { load_room(world, x * ROOM_WIDTH, y * ROOM_HEIGHT, x, y, info); } } FreeImage_Unload(g_sprites); FreeImage_Unload(g_background); } FreeImage_Save(FIF_PNG, world, g_showsecrets ? "Metroid-Map-Secrets.png" : "Metroid-Map.png"); FreeImage_Unload(world); } g_showsecrets = 0; //draw each area to show all possible "hidden zones" for( int a = 0; a < NUM_AREAS; a++ ) { FIBITMAP *area = allocate_nes_buffer(MAP_SIZE * ROOM_WIDTH, MAP_SIZE * ROOM_HEIGHT, 0xFF); g_sprites = allocate_pattern_table(); g_background = allocate_pattern_table(); load_pattern_tables(common_patterns); load_pattern_tables(area_info[a].patterns); memcpy(g_palette, g_rom + area_info[a].palette_data + 3, PALETTE_SIZE * 2); for( int y = 0; y < MAP_SIZE; y++ ) { for( int x = 0; x < MAP_SIZE; x++ ) { load_room(area, x * ROOM_WIDTH, y * ROOM_HEIGHT, x, y, &area_info[a]); } } FreeImage_Save(FIF_PNG, area, (string(area_info[a].name) + "-Hidden-Zones.png").c_str()); FreeImage_Unload(g_sprites); FreeImage_Unload(g_background); FreeImage_Unload(area); } //show each area's sprites for( int aa = 0; aa < NUM_AREAS; aa++ ) { FIBITMAP *sprites = allocate_nes_buffer(SHOWSPRITES_WIDTH * SHOWSPRITES_NUM, SHOWSPRITES_HEIGHT + PATTERN_SIZE, 0); g_sprites = allocate_pattern_table(); g_background = allocate_pattern_table(); load_pattern_tables(common_patterns); load_pattern_tables(area_info[aa].patterns); memcpy(g_palette, g_rom + area_info[aa].palette_data + 3, PALETTE_SIZE * 2); for( int s = 0; s < SHOWSPRITES_NUM; s++ ) { draw_number(sprites, s * SHOWSPRITES_WIDTH, 0, s, 48); draw_sprite(sprites, s * SHOWSPRITES_WIDTH + SHOWSPRIETS_XOFF, PATTERN_SIZE + SHOWSPRIETS_YOFF, s * SHOWSPRITES_WIDTH, PATTERN_SIZE, (s + 1) * SHOWSPRITES_WIDTH, SHOWSPRITES_HEIGHT + PATTERN_SIZE, s, 0, 0, &area_info[aa]); } FreeImage_Save(FIF_PNG, sprites, (string(area_info[aa].name) + "-Sprites.png").c_str()); if( aa == 00 ) { memset(FreeImage_GetBits(sprites), 0xFF, FreeImage_GetLine(sprites) * FreeImage_GetHeight(sprites)); for( int s = 0; s < SHOWSPRITES_NUM; s++ ) { draw_number(sprites, s * SHOWSPRITES_WIDTH, 0, s, 48); draw_sprite(sprites, s * SHOWSPRITES_WIDTH + SHOWSPRIETS_XOFF, PATTERN_SIZE + SHOWSPRIETS_YOFF, s * SHOWSPRITES_WIDTH, PATTERN_SIZE, (s + 1) * SHOWSPRITES_WIDTH, SHOWSPRITES_HEIGHT + PATTERN_SIZE, s, 0, 0, NULL); } FreeImage_Save(FIF_PNG, sprites, "Metroid-Common-Sprites.png"); } //show each area's pattern tables FreeImage_Save(FIF_PNG, g_sprites, (string(area_info[aa].name) + "-Patterns-Sprites.png").c_str()); FreeImage_Save(FIF_PNG, g_background, (string(area_info[aa].name) + "-Patterns-Backgrounds.png").c_str()); FreeImage_Unload(g_sprites); FreeImage_Unload(g_background); FreeImage_Unload(sprites); } FreeImage_DeInitialise(); } FIBITMAP *allocate_nes_buffer(int width, int height, int transcolor) { RGBQUAD nes_palette[64] = { //From zsnes.com's AspiringSquire, which is "A Revision of BMF's bmfpal40.pal [http://bmf.rustedmagick.com/]" {0x60, 0x60, 0x60}, {0x70, 0x2C, 0x00}, {0x9C, 0x00, 0x00}, {0x80, 0x00, 0x3C}, {0x64, 0x00, 0x64}, {0x28, 0x08, 0x68}, {0x00, 0x00, 0x64}, {0x00, 0x18, 0x51}, {0x00, 0x24, 0x24}, {0x00, 0x34, 0x1C}, {0x00, 0x44, 0x00}, {0x2C, 0x44, 0x00}, {0x54, 0x34, 0x00}, {0x00, 0x00, 0x00}, {0x10, 0x10, 0x10}, {0x10, 0x10, 0x10}, {0xAE, 0xAE, 0xAE}, {0xB8, 0x58, 0x24}, {0xF4, 0x34, 0x34}, {0xD4, 0x24, 0x74}, {0xB4, 0x00, 0xB4}, {0x59, 0x1E, 0xBD}, {0x1C, 0x1C, 0xB4}, {0x18, 0x3C, 0x88}, {0x00, 0x5C, 0x5C}, {0x00, 0x6C, 0x38}, {0x00, 0x7C, 0x00}, {0x48, 0x7C, 0x00}, {0x89, 0x6F, 0x00}, {0x30, 0x30, 0x30}, {0x10, 0x10, 0x10}, {0x10, 0x10, 0x10}, {0xFF, 0xFF, 0xFF}, {0xE8, 0xA0, 0x58}, {0xFF, 0x84, 0x84}, {0xFF, 0x74, 0xB8}, {0xEC, 0x64, 0xEC}, {0xAF, 0x6A, 0xFB}, {0x74, 0x74, 0xFF}, {0x44, 0x96, 0xDE}, {0x00, 0xB7, 0xB7}, {0x28, 0xC6, 0x7A}, {0x3C, 0xD4, 0x3C}, {0x7C, 0xC8, 0x34}, {0xCC, 0xBC, 0x00}, {0x4C, 0x4C, 0x4C}, {0x10, 0x10, 0x10}, {0x10, 0x10, 0x10}, {0xFF, 0xFF, 0xFF}, {0xFC, 0xD8, 0xC0}, {0xFF, 0xCC, 0xCC}, {0xFF, 0xC8, 0xE4}, {0xFC, 0xC4, 0xFC}, {0xE4, 0xC8, 0xFF}, {0xCC, 0xCC, 0xFF}, {0xB8, 0xD8, 0xF4}, {0xA4, 0xE4, 0xE4}, {0xAC, 0xEC, 0xCC}, {0xB4, 0xF4, 0xB4}, {0xCC, 0xEC, 0xB4}, {0xE4, 0xE4, 0xB4}, {0xB6, 0xB6, 0xB6}, {0x10, 0x10, 0x10}, {0x10, 0x10, 0x10} }; FIBITMAP *fi = FreeImage_Allocate(width, height, 8); memcpy(FreeImage_GetPalette(fi), nes_palette, sizeof(nes_palette)); memset(FreeImage_GetPalette(fi) + 255, transcolor, 3); BYTE trans[256]; memset(trans, 0xFF, 255); trans[255] = 0; FreeImage_SetTransparencyTable(fi, trans, 256); memset(FreeImage_GetBits(fi), 0xFF, FreeImage_GetLine(fi) * FreeImage_GetHeight(fi)); return fi; } FIBITMAP *allocate_pattern_table(void) { RGBQUAD bw_palette[4] = {{0x00, 0x00, 0x00}, {0x60, 0x60, 0x60}, {0xAE, 0xAE, 0xAE}, {0xFF, 0xFF, 0xFF}}; FIBITMAP *fi = FreeImage_Allocate(PATTERNTABLE_SIZE * PATTERN_SIZE, PATTERN_SIZE, 8); memcpy(FreeImage_GetPalette(fi), bw_palette, sizeof(bw_palette)); return fi; } void load_pattern_tables(int *patterns) { for( int p = 0; patterns[p] != -1; p++ ) { /* there is a table at 0x1C6E0 for pattern data which is formatted as such: * byte 0 - source ROM bank * byte 1-2 - source address * byte 3-4 - destination address (0x0??? for sprites, 0x1??? for background) * byte 5-6 - length of data (there are 16 bytes per pattern) */ unsigned char *pattern_data = g_rom + 0x1C6E0 + (patterns[p] * 7); unsigned char *patbuf = MAP_PTR(pattern_data[0] * 0x04000, *(unsigned short *)(pattern_data + 1)); int x = *(unsigned short *)(pattern_data + 3); int bgr = (x & 0x1000); x = ((x & 0x0FF0) >> 4) * 8; //we aren't copying the raw data, we are decoding it int num = (*(unsigned short *)(pattern_data + 5)) >> 4; //the number of patterns is the number of bytes / 16 for( int n = 0; n < num; n++ ) { load_pattern(bgr ? g_background : g_sprites, x, 0, patbuf); x += 8; patbuf += 16; } } } void load_pattern(FIBITMAP *fi, int x, int y, unsigned char *pattern) { for( int yy = 0; yy < 8; yy++ ) { BYTE *img = FreeImage_GetScanLine(fi, FreeImage_GetHeight(fi) - 1 - (y + yy)) + x; for( int xx = 0; xx < 8; xx++ ) { *img++ = ((pattern[yy] & (0x80 >> xx)) ? 1 : 0) | ((pattern[yy + 8] & (0x80 >> xx)) ? 2 : 0); } } } void load_room(FIBITMAP *fi, int x, int y, int roomx, int roomy, AreaInfo *info) { unsigned char *room_table = g_rom + 0x0253E; //32x32 grid of room numbers for the world int room = room_table[(roomy * MAP_SIZE) + roomx]; //don't draw the offlimits room if( room == 0xFF ) return; //blank out the area with pattern 0xFF, which should just be the background color int yy; for( yy = 0; yy < ROOM_HEIGHT; yy++ ) { memset(FreeImage_GetScanLine(fi, FreeImage_GetHeight(fi) - 1 - (y + yy)) + x, g_palette[0], ROOM_WIDTH); } memset(g_nametable, 0xFF, sizeof(g_nametable)); //build room background data unsigned short room_ptr = *(unsigned short *)(g_rom + info->room_ptr + (room * 2)); unsigned char *room_data = MAP_PTR(info->page_ofs, room_ptr); int defpal = *room_data++; memset(g_attributes, defpal, sizeof(g_attributes)); while( *room_data != 0xFD && *room_data != 0xFF ) { load_structure((room_data[0] & 0x0F) * 2, ((room_data[0] & 0xF0) >> 4) * 2, room_data[1], room_data[2] & 0x03, defpal, info); room_data += 3; } //room number common items if( *room_data == 0xFD ) { room_data++; while( *room_data != 0xFF ) { int type = room_data[0] & 0x0F; if( type == 1 ) { //enemy: [0]: F0=id [1]: ?F=type 80=hard flag 40=miniboss [2]: F?=y ?F=x draw_enemy(fi, x + ((room_data[2] & 0x0F) << 4), y + (room_data[2] & 0xF0), x, y, room_data[1] & 0x0F, room_data[1] >> 7, info); room_data += 3; } else if( type == 2 ) { //door: [1]: B?=left A?=right ?0=5missiles ?1=normal ?2=10missiles draw_door(fi, x, y, (room_data[1] & 0xF0) == 0xA0, room_data[1] & 0x03); room_data += 2; } else if( type == 6 ) { //kraid/ridley statues draw_statues(fi, x, y); room_data += 1; } else if( type == 7 ) { //enemy generator: [0]: F0=id [1]: ?F=type 80=hard flag [2]: F?=y ?F=x draw_enemy(fi, x + ((room_data[2] & 0x0F) << 4), y + (room_data[2] & 0xF0), x, y, room_data[1] & 0x0F, room_data[1] >> 7, info); room_data += 3; } else { //should never happen room_data++; } } } //coordinate specific items unsigned char *item_data = g_rom + info->item_data; yy = item_data[0]; while( yy < roomy ) { unsigned short item_ptr = *(unsigned short *)(item_data + 1); if( item_ptr == 0xFFFF ) { yy = -1; break; } item_data = MAP_PTR(info->page_ofs, item_ptr); yy = item_data[0]; } if( yy == roomy ) { item_data += 3; int xx = item_data[0]; while( xx < roomx ) { if( item_data[1] == 0xFF ) { xx = -1; break; } item_data += item_data[1]; xx = item_data[0]; } if( xx == roomx ) { item_data += 2; while( *item_data != 0 ) { int type = item_data[0] & 0x0F; if( type == 1 ) { //extra enemy: [0]: F0=id [1]: ?F=type 80=hard flag [2]: F?=y ?F=x draw_enemy(fi, x + ((item_data[2] & 0x0F) << 4), y + (item_data[2] & 0xF0), x, y, item_data[1] & 0x0F, item_data[1] >> 7, info); item_data += 3; } else if( type == 2 ) { //powerup: [1]=type [2]: F?=y ?F=x draw_powerup(fi, x + ((item_data[2] & 0x0F) << 4), y + (item_data[2] & 0xF0), x, y, item_data[1]); item_data += 3; } else if( type == 3 ) { //enemy swarm (mellow, memu, melia) draw_enemy(fi, x + 32, y + 64, x, y, 100, 0, info); draw_enemy(fi, x + 96, y + 64, x, y, 100, 0, info); draw_enemy(fi, x + 144, y + 64, x, y, 100, 0, info); draw_enemy(fi, x + 208, y + 64, x, y, 100, 0, info); item_data++; } else if( type == 4 ) { //elevator: [1]: 7F=link id 80=goes up instead of down draw_elevator(fi, x, y); item_data += 2; } else if( type == 5 ) { //cannon turret: [0]: F?=direction (0=downleft, 1=downright, 2=down, 3=down but rotates 90 degrees instead of 180) [1]: F?=y ?F=x draw_enemy(fi, x + ((item_data[1] & 0x0F) << 4), y + (item_data[1] & 0xF0), x, y, 200 + (item_data[0] >> 4), 0, info); item_data += 2; } else if( type == 6 ) { //mother brain item_data++; } else if( type == 7 ) { //zeebetite: [0]: F?=id item_data++; } else if( type == 8 ) { //rinkas: [0]: F?=id draw_enemy(fi, x + 120, y + 32, x, y, 101, 0, info); draw_enemy(fi, x + 32, y + 192, x, y, 101, 0, info); draw_enemy(fi, x + 208, y + 192, x, y, 101, 0, info); item_data++; } else if( type == 9 ) { //extra door: [1]: B?=left A?=right ?0=unknown/nothing ?2=10missiles ?3=blue, except can't be opened after motherbrain is killed type = item_data[1] & 0x0F; if( type > 0 ) { draw_door(fi, x, y, (item_data[1] & 0xF0) == 0xA0, type == 0x02 ? 2 : 1); } item_data += 2; } else if( type == 10 ) { //palette switch item_data++; } else { //should never happen item_data++; } } } } //draw samus standing at the start point if( roomx == g_rom[0x055D7] && roomy == g_rom[0x055D8] ) { draw_sprite(fi, x + 124, y + 161, x, y, x + ROOM_WIDTH, y + ROOM_HEIGHT, 0x07, 0, 0, NULL); } //draw the background tiles, set up from all the load_structure calls above, on top of all the sprites draw_background(fi, x, y); } void load_structure(int x, int y, int structure, int palette, int defpal, AreaInfo *info) { if( x >= NAMETABLE_WIDTH || y >= NAMETABLE_HEIGHT ) return; unsigned short structure_ptr = *(unsigned short *)(g_rom + info->structure_ptr + (structure * 2)); unsigned char *structure_data = MAP_PTR(info->page_ofs, structure_ptr); while( *structure_data != 0xFF ) { int max = *structure_data++; int xx = (max & 0xF0) >> 4; max = (max & 0x0F) + xx; while( xx < max ) { if( x + (xx * 2) < NAMETABLE_WIDTH ) { load_tile(x + (xx * 2), y, *structure_data, palette, defpal, info); } structure_data++; xx++; } y += 2; if( y >= NAMETABLE_HEIGHT ) return; } } void load_tile(int x, int y, int tile, int palette, int defpal, AreaInfo *info) { unsigned char *tile_data = g_rom + info->tile_data + (tile * 4); for( int yy = 0; yy < 2; yy++ ) { for( int xx = 0; xx < 2; xx++ ) { g_nametable[y + yy][x + xx] = *tile_data++; } } if( palette != defpal ) { g_attributes[y / 2][x / 2] = palette; } } void copy_pattern(FIBITMAP *fi, int x, int y, int clipleft, int cliptop, int clipright, int clipbottom, int pattern, int palette, int flags) { //reveal walkable and shootable blocks if( g_showsecrets && !(flags & PATTERN_SPRITE) ) { if( pattern >= 0xA0 ) pattern = 0xFF; if( pattern >= 0x70 && pattern < 0xA0 ) pattern = 0xFE; } unsigned char *palette_data = g_palette + ((flags & PATTERN_SPRITE) ? PALETTE_SIZE : 0) + (palette * 4); for( int yy = 0; yy < PATTERN_SIZE; yy++ ) { if( y + yy < cliptop || y + yy >= clipbottom ) continue; BYTE *src = FreeImage_GetScanLine((flags & PATTERN_SPRITE) ? g_sprites : g_background, (flags & PATTERN_FLIPVERT) ? yy : PATTERN_SIZE - 1 - yy) + (pattern * PATTERN_SIZE); BYTE *dst = FreeImage_GetScanLine(fi, FreeImage_GetHeight(fi) - 1 - (y + yy)) + x; for( int xx = 0; xx < PATTERN_SIZE; xx++ ) { if( x + xx < clipleft || x + xx >= clipright ) continue; BYTE p = src[(flags & PATTERN_FLIPHORZ) ? PATTERN_SIZE - 1 - xx : xx]; if( p & 0x03 ) { dst[xx] = palette_data[p]; } } } } void draw_background(FIBITMAP *fi, int roomleft, int roomtop) { for( int y = 0; y < NAMETABLE_HEIGHT; y++ ) { for( int x = 0; x < NAMETABLE_WIDTH; x++ ) { copy_pattern(fi, roomleft + (x * PATTERN_SIZE), roomtop + (y * PATTERN_SIZE), roomleft, roomtop, roomleft + ROOM_WIDTH, roomtop + ROOM_HEIGHT, g_nametable[y][x], g_attributes[y / 2][x / 2], 0); } } } void draw_sprite(FIBITMAP *fi, int x, int y, int clipleft, int cliptop, int clipright, int clipbottom, int sprite, int paladj, int flagadj, AreaInfo *info) { unsigned short sprite_ptr = *(unsigned short *)(g_rom + (info ? info->sprite_ptr : 0x0460B) + (sprite * 2)); unsigned char *sprite_data = MAP_PTR(info ? info->page_ofs : 0x04000, sprite_ptr); unsigned short spritepos_ptr = *(unsigned short *)(g_rom + (info ? info->spritepos_ptr : 0x046DF) + ((sprite_data[0] & 0x0F) * 2)); unsigned char *spritepos_data = MAP_PTR(info ? info->page_ofs : 0x04000, spritepos_ptr); int palette = ((sprite_data[0] & 0x30) >> 4) + paladj; if( palette > 3 ) palette = 3; int flags = ((sprite_data[0] & 0xC0) | PATTERN_SPRITE) ^ flagadj; int xdir = (flags & PATTERN_FLIPHORZ) ? -1 : 1; int ydir = (flags & PATTERN_FLIPVERT) ? -1 : 1; y += (signed char)sprite_data[1]; x += (signed char)sprite_data[2]; sprite_data += 3; while( *sprite_data != 0xFF ) { if( *sprite_data == 0xFE ) { //skip a position spritepos_data += 2; } else if( *sprite_data == 0xFD ) { //update palette and flip sprite_data++; palette = (*sprite_data & 0x03) + paladj; if( palette > 3 ) palette = 3; flags = ((*sprite_data & 0xC0) | PATTERN_SPRITE) ^ flagadj; } else if( *sprite_data == 0xFC ) { //move cursor sprite_data++; y += ydir * (signed char)*sprite_data; sprite_data++; x += xdir * (signed char)*sprite_data; } else { copy_pattern(fi, x + (xdir * (signed char)spritepos_data[1]), y + (ydir * (signed char)spritepos_data[0]), clipleft, cliptop, clipright, clipbottom, *sprite_data, palette, flags); spritepos_data += 2; } sprite_data++; } } void draw_enemy(FIBITMAP *fi, int x, int y, int roomleft, int roomtop, int enemy, int paladj, AreaInfo *info) { for( int i = 0; info->enemy_sprites[i].type != -1; i++ ) { if( info->enemy_sprites[i].type == enemy ) { draw_sprite(fi, x + info->enemy_sprites[i].xoffset, y + info->enemy_sprites[i].yoffset, roomleft, roomtop, roomleft + ROOM_WIDTH, roomtop + ROOM_HEIGHT, info->enemy_sprites[i].sprite, paladj, 0, info); return; } } } void draw_powerup(FIBITMAP *fi, int x, int y, int roomleft, int roomtop, int powerup) { int nx = (x - roomleft) / 8, ny = (y - roomtop) / 8; for( int yy = 0; yy < 2; yy++ ) { for( int xx = 0; xx < 2; xx++ ) { if( g_showsecrets ) { //reveal hidden items g_nametable[ny + yy][nx + xx] = 0xFF; } else { if( g_nametable[ny + yy][nx + xx] != 0xFF ) { //don't draw this powerup because it is hidden return; } } } } //use blue palette for ice beam, otherwise default draw_sprite(fi, x + 5, y + 5, roomleft, roomtop, roomleft + ROOM_WIDTH, roomtop + ROOM_HEIGHT, powerup + 0x50, powerup == 7 ? 1 : 0, 0, NULL); } void draw_elevator(FIBITMAP *fi, int roomleft, int roomtop) { draw_sprite(fi, roomleft + 112, roomtop + 127, roomleft, roomtop, roomleft + ROOM_WIDTH, roomtop + ROOM_HEIGHT, 0x23, 0, 0, NULL); } void draw_door(FIBITMAP *fi, int roomleft, int roomtop, int onright, int palette) { int x = 8; int flagadj = 0; if( onright ) { flagadj = PATTERN_FLIPHORZ; x = 224; } draw_sprite(fi, roomleft + x, roomtop + 80, roomleft, roomtop, roomleft + ROOM_WIDTH, roomtop + ROOM_HEIGHT, 0x31, palette - 3, flagadj, NULL); } void draw_statues(FIBITMAP *fi, int roomleft, int roomtop) { draw_sprite(fi, roomleft + 128, roomtop + 96, roomleft, roomtop, roomleft + ROOM_WIDTH, roomtop + ROOM_HEIGHT, 0x65, 0, 0, NULL); draw_sprite(fi, roomleft + 96, roomtop + 64, roomleft, roomtop, roomleft + ROOM_WIDTH, roomtop + ROOM_HEIGHT, 0x66, 0, 0, NULL); } void draw_number(FIBITMAP *fi, int x, int y, int num, int color) { draw_digit(fi, x, y, (num >> 4) & 0x0F, color); draw_digit(fi, x + 4, y, num & 0x0F, color); } void draw_digit(FIBITMAP *fi, int x, int y, int digit, int color) { int map[16] = { 0x25552, 0x23227, 0x34217, 0x34243, 0x55744, 0x71343, 0x61757, 0x74444, 0x25252, 0x75743, 0x25755, 0x35353, 0x61116, 0x35553, 0x71317, 0x71311 }; for( int yy = 0; yy < 5; yy++ ) { BYTE *p = FreeImage_GetScanLine(fi, FreeImage_GetHeight(fi) - 1 - (y + yy)) + x; for( int xx = 0; xx < 3; xx++ ) { if( (map[digit] >> (16 - (yy * 4))) & (1 << xx) ) { p[xx] = color; } } } }