#include #include #include #include #include #include #include "common.h" #include "identifier.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; } using metatile_id = identifier::key_type; struct TileUse { metatile_id id; bool tflipx = false; bool tflipy = false; }; Magick::Image renderTile(unsigned short ch, bool tflipx, bool tflipy, const std::vector& palettes, const std::vector& mapTiles, const std::vector>& tilesets) { Magick::Image result("16x16", "transparent"); unsigned short tile16 = ch & 0x3FF; if ((tile16 >> 6) >= 12) return result; result.modifyImage(); Magick::Pixels view(result); int tpal = (ch >> 10) & 0xF; const Palette& palette = palettes[tpal]; // bool tflipx = tu.tflipx;//(ch & 0x4000) != 0; // bool tflipy = tu.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(); } } return result; } unsigned short stripFlipInfo(unsigned short ch) { return ch & ~(0x4000 | 0x8000); } 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"); 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()); identifier metatiles; std::vector> itemised; for (int layer=0; layer& ml = mapLayers[layer]; if (ml.empty()) continue; std::vector newLayer; for (int mapy = 0; mapy < height; mapy++) { for (int mapx = 0; mapx < width; mapx++) { unsigned short ch = BufferView(ml).ReadTwoBytes((mapx + (mapy * width)) * 2); TileUse tu; tu.id = metatiles.add(stripFlipInfo(ch)); tu.tflipx = (ch & 0x4000) != 0; tu.tflipy = (ch & 0x8000) != 0; newLayer.push_back(std::move(tu)); } } itemised.push_back(std::move(newLayer)); } std::ofstream mapfile("out.tmx"); mapfile << R"()" << std::endl; mapfile << R"( )" << std::endl; for (int layer=itemised.size()-1; layer>=0; layer--) { mapfile << R"( )" << std::endl; mapfile << R"( )"; bool first = true; for (const TileUse& tu : itemised[layer]) { if (first) { first = false; } else { mapfile << ","; } unsigned int outChar = tu.id; if (tu.tflipx) outChar |= 0x80000000; if (tu.tflipy) outChar |= 0x40000000; mapfile << outChar; } mapfile << R"()" << std::endl; mapfile << R"( )" << std::endl; } mapfile << R"()" << std::endl; for (int layer=itemised.size()-1; layer>=0; layer--) { for (int mapy = 0; mapy < height; mapy++) { for (int mapx = 0; mapx < width; mapx++) { const TileUse& tu = itemised[layer][mapx+mapy*width]; Magick::Image tileRender = renderTile(metatiles.get(tu.id), tu.tflipx, tu.tflipy, palettes, mapTiles, tilesets); image.composite(tileRender, mapx << 4, mapy << 4, Magick::OverCompositeOp); } } } image.magick("png"); image.write("out.png"); constexpr int TILES_PER_ROW = 10; int sheetWidth; int sheetHeight; if (metatiles.size() < TILES_PER_ROW) { sheetWidth = metatiles.size() * 16; sheetHeight = 16; } else { sheetWidth = TILES_PER_ROW * 16; sheetHeight = (metatiles.size() / TILES_PER_ROW + 1) * 16; } std::ofstream tilesetfile("out.tsx"); tilesetfile << R"()" << std::endl; tilesetfile << R"( )" << std::endl; tilesetfile << R"()" << std::endl; Magick::Image tilesetImage(Magick::Geometry(sheetWidth, sheetHeight), "transparent"); for (int i=0; i