From a64c85a3f5641581a19ef52a5c7898ec3cb71754 Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Sun, 31 Jan 2021 20:04:51 -0500 Subject: Abstracted some of the sprite dumper functionality out --- tools/sprite_dumper/sprite_dumper.cpp | 361 ++++++++++++++++++++++++++++++++++ 1 file changed, 361 insertions(+) create mode 100644 tools/sprite_dumper/sprite_dumper.cpp (limited to 'tools/sprite_dumper/sprite_dumper.cpp') diff --git a/tools/sprite_dumper/sprite_dumper.cpp b/tools/sprite_dumper/sprite_dumper.cpp new file mode 100644 index 0000000..9ee55cb --- /dev/null +++ b/tools/sprite_dumper/sprite_dumper.cpp @@ -0,0 +1,361 @@ +// Inspired a lot by https://github.com/jeffman/MOTHER-3-Funland + +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +class Palette { +public: + + Palette() = default; + + Palette(BufferView m3, const int addr) { + for (int i=0; i<16; i++) { + unsigned short ch = m3.ReadTwoBytes(addr + (i << 1)); + int r = (ch & 0x1F); + int g = ((ch >> 5) & 0x1F); + int b = ((ch >> 10) & 0x1F); + colors_.push_back(Magick::ColorRGB((r << 3)/256.0, (g << 3)/256.0, (b << 3)/256.0)); + } + } + + const std::vector& Colors() const { return colors_; } + +private: + std::vector colors_; +}; + +class PaletteSet { +public: + PaletteSet(BufferView m3) : m3_(m3) { + for (int i=0; i<16; i++) { + defaultPals_.emplace_back(m3, GetPointer(0) + (i << 5)); + } + } + + const Palette& GetPalette(int spriteindex, int palnum = -1) { + int a = GetPointer(spriteindex); + + if ((spriteindex > 0xFF) && (spriteindex < 0x26C)) { + specialPals_[spriteindex] = Palette(m3_, a); + return specialPals_[spriteindex]; + } else { + if (palnum == -1) { + palnum = static_cast(m3_.ReadByte(0x1433D7C + 1 + ((spriteindex + 1) * 12))) & 0xF; + } + + return defaultPals_.at(palnum); + } + } + +private: + + int GetPointer(int spriteindex) { + const int ADDR = 0x1A41548; + + int index = 0; + + if ((spriteindex > 0xFF) && (spriteindex < 0x26C)) { + // These sprites use the (spriteindex - 0xFF)th entry + index = spriteindex - 0xff; + } + + int a = m3_.ReadFourBytes(ADDR + 4 + (index << 2)); + if (a == -1) return -1; + return ADDR + a; + } + + BufferView m3_; + std::vector defaultPals_; + std::map specialPals_; +}; + +struct Subsprite { + int x; + int y; + int tile; + bool flipH; + bool flipV; + int objSize; + int objShape; + + int Width() const { return WIDTHS[objShape][objSize]; } + + int Height() const { return HEIGHTS[objShape][objSize]; } + +private: + + static constexpr int WIDTHS[3][4] = { { 8, 16, 32, 64 }, { 16, 32, 32, 64 }, { 8, 8, 16, 32 } }; + static constexpr int HEIGHTS[3][4] = { { 8, 16, 32, 64 }, { 8, 8, 16, 32 }, { 16, 32, 32, 64 } }; +}; + +struct FrameOutput { + Magick::Image image; + int width; + int height; + int centerX; + int centerY; +}; + +struct Sprite { + std::vector subsprites; + + FrameOutput render(BufferView m3, const Palette& palette, const int gfxPtr) const { + FrameOutput output; + + if (subsprites.empty()) return output; + + int minX = subsprites[0].x; + int minY = subsprites[0].y; + int maxX = minX + subsprites[0].Width(); + int maxY = minY + subsprites[0].Height(); + + for (int j=1; j maxX) maxX = o.x + o.Width(); + if ((o.y + o.Height()) > maxY) maxY = o.y + o.Height(); + } + + int width = maxX - minX; + int height = maxY - minY; + int centerX = -minX; + int centerY = -minY; + + output.width = width; + output.height = height; + output.centerX = centerX; + output.centerY = centerY; + output.image = Magick::Image(Magick::Geometry(width, height), "transparent"); + output.image.modifyImage(); + + Magick::Pixels view(output.image); + + for (const Subsprite& o : subsprites) { + int tilePointer = o.tile << 5; + for (int y=0; y(ch & 0xF); + tileData[tx*2+1][ty] = static_cast((ch >> 4) & 0xF); + } + } + + tilePointer += 0x20; + + int actualX = o.flipH ? (o.Width() - x - 8) : x; + + int destX = o.x + centerX + actualX; + int destY = o.y + centerY + actualY; + + Magick::PixelPacket* pixels = view.get(destX,destY,8,8); + + for (int ty=0; ty<8; ty++) { + int actualTy = o.flipV ? (7-ty) : ty; + + for (int tx=0; tx<8; tx++) { + int actualTx = o.flipH ? (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 output; + } +}; + +struct SpriteSheet { + int spriteindex = 0; + int gfxPtr; + std::vector sprites; + + void render(BufferView m3, PaletteSet& palettes, std::vector& frames) const { + const Palette& palette = palettes.GetPalette(spriteindex); + for (int i=0; i(m3.ReadNextByte()); + subsprite.x = static_cast(m3.ReadNextByte()); + + unsigned short ch = m3.ReadNextTwoBytes(); + subsprite.tile = static_cast(ch & 0x3FF); + subsprite.flipH = (ch & 0x400) != 0; + subsprite.flipV = (ch & 0x800) != 0; + subsprite.objSize = static_cast((ch >> 12) & 3); + subsprite.objShape = static_cast((ch >> 14) & 3); + + /*std::cout << subsprite.y << std::endl; + std::cout << subsprite.x << std::endl; + std::cout << subsprite.flipH << "," << subsprite.flipV << std::endl;*/ + + sprite.subsprites.push_back(std::move(subsprite)); + } + + m3.SeekAdd(2); + + ss.sprites.push_back(std::move(sprite)); + } + + spritesheets_[i] = std::move(ss); + //if (i == 15) return; + } + } + + const std::map& SpriteSheets() const { return spritesheets_; } + +private: + + int GetPointerToSheet(BufferView m3, int index) { + int readAt = baseAddr_ + 4 + (index << 2); + int a = m3.ReadFourBytes(readAt); + //std::cout << readAt << " :: " << a << std::hex << std::endl; + if (a == 0) return -1; + return a + baseAddr_; + } + + int GetGfxPointer(BufferView m3, int index) { + return m3.ReadFourBytes(gfxAddr_ + 4 + (index << 2)) + gfxAddr_; + } + + int baseAddr_; + int gfxAddr_; + std::map spritesheets_; +}; + +int main(int argc, char** argv) { + if (argc < 3) { + std::cout << "Usage: ./sprite_dumper [path to rom] {sheet IDs}" << std::endl; + std::cout << "sheet IDs should be space separated references to the sheets to concatenate" << std::endl; + std::cout << "the format of the ID is BankNum.SheetNum" << std::endl; + return -1; + } + + Magick::InitializeMagick(nullptr); + + Rom m3(argv[1]); + PaletteSet palettes(m3.buffer()); + Bank b1(m3.buffer(), 0x1A442A4, 0x14383E4); + Bank b2(m3.buffer(), 0x1AE0638, 0x194BC30); + Bank b3(m3.buffer(), 0x1AEE4C4, 0x1A012B8); + Bank b4(m3.buffer(), 0x1AF1ED0, 0x1A36AA0); + + std::vector frames; + + for (int i=2; i> bankNum; + argfmt >> ch; //. + argfmt >> sheetNum; + + const Bank& bankToRead = [&](){ + switch (bankNum) { + case 0: return b1; + case 1: return b2; + case 2: return b3; + case 3: return b4; + default: throw std::invalid_argument("Invalid bank num: " + std::to_string(bankNum)); + } + }(); + + bankToRead.SpriteSheets().at(sheetNum).render(m3.buffer(), palettes, frames); + } + + int maxWidth = 0; + int maxHeight = 0; + for (const FrameOutput& f : frames) { + if (f.width > maxWidth) maxWidth = f.width; + if (f.height > maxHeight) maxHeight = f.height; + } + + const int FRAMES_PER_ROW = 10; + int sheetWidth; + int sheetHeight; + + std::ofstream datafile("out.txt"); + datafile << maxWidth << "," << maxHeight << " cell size" << std::endl; + datafile << FRAMES_PER_ROW << " frames per row" << std::endl; + datafile << frames.size() << " frames" << std::endl; + datafile << std::endl; + + if (frames.size() < FRAMES_PER_ROW) { + sheetWidth = frames.size() * maxWidth; + sheetHeight = maxHeight; + } else { + sheetWidth = FRAMES_PER_ROW * maxWidth; + sheetHeight = (frames.size() / FRAMES_PER_ROW + 1) * maxHeight; + } + + Magick::Image sheet(Magick::Geometry(sheetWidth, sheetHeight), "transparent"); + for (int i=0; i