From 362c332000a65acc060660dcb6bb0ec6f99cbafe Mon Sep 17 00:00:00 2001
From: Kelly Rauchenberger <fefferburbia@gmail.com>
Date: Mon, 1 Feb 2021 13:14:40 -0500
Subject: Tileset dumper can now export to Tiled

---
 tools/sprite_dumper/identifier.h       |  59 +++++++
 tools/sprite_dumper/tileset_dumper.cpp | 275 +++++++++++++++++++++++----------
 2 files changed, 254 insertions(+), 80 deletions(-)
 create mode 100644 tools/sprite_dumper/identifier.h

(limited to 'tools/sprite_dumper')

diff --git a/tools/sprite_dumper/identifier.h b/tools/sprite_dumper/identifier.h
new file mode 100644
index 0000000..74d83ce
--- /dev/null
+++ b/tools/sprite_dumper/identifier.h
@@ -0,0 +1,59 @@
+#ifndef IDENTIFIER_H_D7EE5679
+#define IDENTIFIER_H_D7EE5679
+
+#include <map>
+#include <vector>
+
+template <typename T>
+class identifier {
+public:
+
+  using value_type = T;
+
+private:
+
+  using vector_type = std::vector<value_type>;
+
+public:
+
+  using key_type = typename vector_type::size_type;
+
+  key_type add(const value_type& val)
+  {
+    auto it = ids_.find(val);
+
+    if (it == std::end(ids_))
+    {
+      key_type ret = ids_.size();
+      ids_[val] = ret;
+
+      uniq_.push_back(val);
+
+      return ret;
+    } else {
+      return it->second;
+    }
+  }
+
+  void compile()
+  {
+    ids_.clear();
+  }
+
+  inline const value_type& get(key_type i) const
+  {
+    return uniq_.at(i);
+  }
+
+  inline key_type size() const
+  {
+    return uniq_.size();
+  }
+
+private:
+
+  std::map<value_type, key_type> ids_;
+  vector_type uniq_;
+};
+
+#endif /* end of include guard: IDENTIFIER_H_D7EE5679 */
diff --git a/tools/sprite_dumper/tileset_dumper.cpp b/tools/sprite_dumper/tileset_dumper.cpp
index de6e1f4..d8997a5 100644
--- a/tools/sprite_dumper/tileset_dumper.cpp
+++ b/tools/sprite_dumper/tileset_dumper.cpp
@@ -3,7 +3,9 @@
 #include <vector>
 #include <string>
 #include <map>
+#include <fstream>
 #include "common.h"
+#include "identifier.h"
 
 constexpr int NUM_ROOMS = 1000;
 constexpr int NUM_TILESETS = 12;
@@ -102,6 +104,112 @@ std::vector<std::vector<char>> GetMapLayers(BufferView m3, int roomId) {
   return layers;
 }
 
+using metatile_id = identifier<unsigned short>::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<Palette>& palettes, const std::vector<char>& mapTiles, const std::vector<std::vector<char>>& 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<unsigned char>(vvvv & 0xF);
+          tileData[tx*2+1][ty] = static_cast<unsigned char>((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;
@@ -125,8 +233,6 @@ int main(int argc, char** argv) {
   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);
@@ -135,102 +241,111 @@ int main(int argc, char** argv) {
   auto palettes = gfxPal.GetPalettes(m3.buffer());
   auto tilesets = gfxPal.GetTilesets(m3.buffer());
 
-  for (int layer=mapLayers.size()-1; layer>=0; layer--) {
+  identifier<unsigned short> metatiles;
+  std::vector<std::vector<TileUse>> itemised;
+
+  for (int layer=0; layer<mapLayers.size(); layer++) {
     const std::vector<char>& ml = mapLayers[layer];
     if (ml.empty()) continue;
 
+    std::vector<TileUse> 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);
-        unsigned short tile16 = ch & 0x3FF;
-
-        if ((tile16 >> 6) >= 12) continue;
+        TileUse tu;
+        tu.id = metatiles.add(stripFlipInfo(ch));
+        tu.tflipx = (ch & 0x4000) != 0;
+        tu.tflipy = (ch & 0x8000) != 0;
+        newLayer.push_back(std::move(tu));
+      }
+    }
 
-        int tpal = (ch >> 10) & 0xF;
-        const Palette& palette = palettes[tpal];
+    itemised.push_back(std::move(newLayer));
+  }
 
-        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);
+  std::ofstream mapfile("out.tmx");
+  mapfile << R"(<map version="1.0" orientation="orthogonal" renderorder="right-down" width=")";
+  mapfile << width;
+  mapfile << R"(" height=")";
+  mapfile << height;
+  mapfile << R"(" tilewidth="16" tileheight="16">)" << std::endl;
+  mapfile << R"(  <tileset firstgid="0" source="out.tsx" />)" << std::endl;
+
+  for (int layer=itemised.size()-1; layer>=0; layer--) {
+    mapfile << R"(  <layer id=")";
+    mapfile << layer;
+    mapfile << R"(" name="Layer )";
+    mapfile << layer;
+    mapfile << R"(" width=")";
+    mapfile << width;
+    mapfile << R"(" height=")";
+    mapfile << height;
+    mapfile << R"(">)" << std::endl;
+    mapfile << R"(    <data encoding="csv">)";
+
+    bool first = true;
+    for (const TileUse& tu : itemised[layer]) {
+      if (first) {
+        first = false;
+      } else {
+        mapfile << ",";
+      }
 
-        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];
+      unsigned int outChar = tu.id;
+      if (tu.tflipx) outChar |= 0x80000000;
+      if (tu.tflipy) outChar |= 0x40000000;
+      mapfile << outChar;
+    }
 
-        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;
+    mapfile << R"(</data>)" << std::endl;
+    mapfile << R"(  </layer>)" << std::endl;
+  }
 
-            tile8[i][j] &= 0x3f;
-            tile8[i][j] |= (ch & 0x3c0);
-          }
-        }
+  mapfile << R"(</map>)" << std::endl;
 
-        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<unsigned char>(vvvv & 0xF);
-                tileData[tx*2+1][ty] = static_cast<unsigned char>((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();
-          }
-        }
+  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"(<tileset name="fromRom" tilewidth="16" tileheight="16" tilecount=")";
+  tilesetfile << metatiles.size();
+  tilesetfile << R"(" columns=")";
+  tilesetfile << TILES_PER_ROW;
+  tilesetfile << R"(">)" << std::endl;
+  tilesetfile << R"(  <image source="tiles.png" />)" << std::endl;
+  tilesetfile << R"(</tileset>)" << std::endl;
+
+  Magick::Image tilesetImage(Magick::Geometry(sheetWidth, sheetHeight), "transparent");
+  for (int i=0; i<metatiles.size(); i++) {
+    Magick::Image tileRender = renderTile(metatiles.get(i), false, false, palettes, mapTiles, tilesets);
+    tilesetImage.composite(tileRender, (i % TILES_PER_ROW) << 4, (i / TILES_PER_ROW) << 4, Magick::OverCompositeOp);
+  }
+
+  tilesetImage.magick("png");
+  tilesetImage.write("tiles.png");
 }
\ No newline at end of file
-- 
cgit 1.4.1