#include #include #include #include #include #include "common.h" constexpr int NUM_ROOMS = 1000; constexpr int NUM_TILESETS = 12; struct RoomInfo { int width; int height; static std::map ReadFromRom(BufferView m3) { const int BASE_ADDR = 0xD2E1D8 + 12; const int ENTRY_LEN = 28; std::map output; for (int i=0; i> 3) + 1) << 4; } return output; } }; struct RoomGfxPal { int paletteId; int tilesetId[NUM_TILESETS]; static std::map ReadFromRom(BufferView m3) { const int BASE_ADDR = 0xD34F44 + 12; const int ENTRY_LEN = 26; std::map output; for (int i=0; i(m3.ReadTwoBytes(BASE_ADDR + i*ENTRY_LEN + 24)); for (int tid=0; tid> GetTilesets(BufferView m3) const { const int BASE_ADDR = 0xD3B4E0; std::vector> output(NUM_TILESETS); for (int i=0; i GetPalettes(BufferView m3) const { const int BASE_ADDR = 0xF3C344; std::vector output; int offset = m3.ReadFourBytes(BASE_ADDR + 4 + (paletteId << 2)); for (int i=0; i<16; i++) { unsigned long palAddr = BASE_ADDR + offset + (i << 5); output.emplace_back(m3, palAddr); } return output; } }; std::vector GetMapTiles(BufferView m3, int roomId) { const int BASE_ADDR = 0x104D9CC; int tilesAddr = m3.ReadFourBytes(BASE_ADDR + 4 + (roomId << 2)) + BASE_ADDR; return m3.Decompress(tilesAddr); } std::vector> GetMapLayers(BufferView m3, int roomId) { const int BASE_ADDR = 0xF9003C; std::vector> layers; for (int i=0; i<3; i++) { try { int lookAddr = BASE_ADDR + 4 + (((roomId * 3) + i) << 2); int layerAddr = m3.ReadFourBytes(lookAddr) + BASE_ADDR; layers.push_back(m3.Decompress(layerAddr)); } catch (const std::domain_error&) { // ignore } } return layers; } int main(int argc, char** argv) { if (argc < 3) { std::cout << "Usage: ./tileset_dumper [path to rom] {map ID}" << std::endl; return -1; } Magick::InitializeMagick(nullptr); Rom m3(argv[1]); auto roomInfos = RoomInfo::ReadFromRom(m3.buffer()); auto roomGfxPals = RoomGfxPal::ReadFromRom(m3.buffer()); int roomNum = std::stoi(argv[2]); std::cout << roomInfos[roomNum].width << "," << roomInfos[roomNum].height << std::endl; std::cout << roomGfxPals[roomNum].paletteId; for (int i=0;i<12;i++) std::cout << "," << roomGfxPals[roomNum].tilesetId[i]; std::cout << std::endl; int width = roomInfos[roomNum].width; int height = roomInfos[roomNum].height; Magick::Image image(Magick::Geometry(width*16, height*16), "transparent"); image.modifyImage(); Magick::Pixels view(image); auto mapTiles = GetMapTiles(m3.buffer(), roomNum); auto mapLayers = GetMapLayers(m3.buffer(), roomNum); const auto& gfxPal = roomGfxPals[roomNum]; auto palettes = gfxPal.GetPalettes(m3.buffer()); auto tilesets = gfxPal.GetTilesets(m3.buffer()); for (int layer=mapLayers.size()-1; layer>=0; layer--) { const std::vector& ml = mapLayers[layer]; if (ml.empty()) continue; for (int mapy = 0; mapy < height; mapy++) { for (int mapx = 0; mapx < width; mapx++) { unsigned short ch = BufferView(ml).ReadTwoBytes((mapx + (mapy * width)) * 2); unsigned short tile16 = ch & 0x3FF; if ((tile16 >> 6) >= 12) continue; int tpal = (ch >> 10) & 0xF; const Palette& palette = palettes[tpal]; bool tflipx = (ch & 0x4000) != 0; bool tflipy = (ch & 0x8000) != 0; int tile8[2][2]; bool sflipx[2][2]; bool sflipy[2][2]; unsigned int magic = BufferView(mapTiles).ReadFourBytes(tile16 * 8); tile8[0][0] = mapTiles[(tile16 * 8) + 4]; tile8[0][1] = mapTiles[(tile16 * 8) + 5]; tile8[1][0] = mapTiles[(tile16 * 8) + 6]; tile8[1][1] = mapTiles[(tile16 * 8) + 7]; for (int i=0; i<2; i++) { for (int j=0; j<2; j++) { sflipx[i][j] = (tile8[i][j] & 0x40) != 0; sflipy[i][j] = (tile8[i][j] & 0x80) != 0; tile8[i][j] &= 0x3f; tile8[i][j] |= (ch & 0x3c0); } } unsigned int mask = (magic >> 16) & 0xf; if ((mask & 0x1) == 0) tile8[0][0] = -1; if ((mask & 0x2) == 0) tile8[0][1] = -1; if ((mask & 0x4) == 0) tile8[1][0] = -1; if ((mask & 0x8) == 0) tile8[1][1] = -1; for (int tiley=0; tiley<2; tiley++) { for (int tilex=0; tilex<2; tilex++) { if (tile8[tiley][tilex] < 0) continue; int tileset = tile8[tiley][tilex] >> 6; int subtile = tile8[tiley][tilex] & 0x3f; int tileData[8][8]; BufferView tilesetData(tilesets[tileset]); tilesetData.Seek(subtile << 5); for (int ty=0; ty<8; ty++) { for (int tx=0; tx<4; tx++) { unsigned char vvvv = tilesetData.ReadNextByte(); tileData[tx*2][ty] = static_cast(vvvv & 0xF); tileData[tx*2+1][ty] = static_cast((vvvv >> 4) & 0xF); } } int stx = tflipx ? 1 - tilex : tilex; int sty = tflipy ? 1 - tiley : tiley; int destX = (mapx << 4) + (stx << 3); int destY = (mapy << 4) + (sty << 3); bool reallyFlipX = (sflipx[tiley][tilex] ^ tflipx); bool reallyFlipY = (sflipy[tiley][tilex] ^ tflipy); Magick::PixelPacket* pixels = view.get(destX,destY,8,8); for (int ty=0; ty<8; ty++) { int actualTy = reallyFlipY ? (7-ty) : ty; for (int tx=0; tx<8; tx++) { int actualTx = reallyFlipX ? (7-tx) : tx; if (tileData[actualTx][actualTy] != 0) { //auto& c = palette.Colors().at(tileData[actualTx][actualTy]); //std::cout << c.redQuantum() << "," << c.greenQuantum() << "," << c.blueQuantum() << std::endl; *pixels = palette.Colors().at(tileData[actualTx][actualTy]); //std::cout << tileData[actualTx][actualTy] << std::endl; } pixels++; } } view.sync(); } } } } } image.magick("png"); image.write("out.png"); }