From 39cc01d386bc765f43d00118599846fc7273eaaf Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Sat, 30 Jan 2021 22:20:23 -0500 Subject: Started sprite dumper tool It's not done yet but hey it can output a single frame of Flint standing still. That's progress baybie. --- tools/sprite_dumper/CMakeLists.txt | 15 ++ tools/sprite_dumper/main.cpp | 358 +++++++++++++++++++++++++++++++++++++ 2 files changed, 373 insertions(+) create mode 100644 tools/sprite_dumper/CMakeLists.txt create mode 100644 tools/sprite_dumper/main.cpp (limited to 'tools') diff --git a/tools/sprite_dumper/CMakeLists.txt b/tools/sprite_dumper/CMakeLists.txt new file mode 100644 index 0000000..5a90c5e --- /dev/null +++ b/tools/sprite_dumper/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required (VERSION 3.1) +project (sprite_dumper) + +set(CMAKE_BUILD_TYPE Debug) + +find_package(PkgConfig) +pkg_check_modules(GraphicsMagick GraphicsMagick++ REQUIRED) + +include_directories(${GraphicsMagick_INCLUDE_DIRS}) + +add_executable(sprite_dumper main.cpp) + +set_property(TARGET sprite_dumper PROPERTY CXX_STANDARD 17) +set_property(TARGET sprite_dumper PROPERTY CXX_STANDARD_REQUIRED ON) +target_link_libraries(sprite_dumper ${GraphicsMagick_LIBRARIES}) diff --git a/tools/sprite_dumper/main.cpp b/tools/sprite_dumper/main.cpp new file mode 100644 index 0000000..e84a2d7 --- /dev/null +++ b/tools/sprite_dumper/main.cpp @@ -0,0 +1,358 @@ +// Inspired a lot by https://github.com/jeffman/MOTHER-3-Funland + +#include +#include +#include +#include +#include +#include +#include + +class Rom { +public: + + explicit Rom(std::string_view filename) { + std::ifstream romfile(filename.data(), std::ios::binary); + if (!romfile.is_open()) { + throw std::invalid_argument("Could not find ROM file: " + std::string(filename)); + } + + romfile.seekg(0, romfile.end); + int length = romfile.tellg(); + romfile.seekg(0, romfile.beg); + + if (length != 0x2000000) { + throw std::invalid_argument("Incorrect ROM length"); + } + + data_ = std::vector(length, 0); + romfile.read(data_.data(), length); + + const char header[] = {'M','O','T','H','E','R','3',0,0,0,0,0,'A','3','U','J'}; + std::string headerTest; + for (int i = 0xA0; i < 0xB0; i++) { + if (data_.at(i) != header[i-0xA0]) { + std::cout << i << std::endl; + + throw std::invalid_argument("Invalid ROM header"); + } + } + } + + const std::vector& data() const { return data_; } + + int ReadNextByte() { + int o2r = offset_; + offset_++; + return ReadByte(o2r); + } + + int ReadByte(int addr) const { + return static_cast(data_[addr]); + } + + int ReadNextTwoBytes() { + int o2r = offset_; + offset_+=2; + return ReadTwoBytes(o2r); + } + + int ReadTwoBytes(int addr) const { + return static_cast(data_[addr]) | (data_[addr + 1] << 8); + } + + int ReadNextFourBytes() { + int o2r = offset_; + offset_+=4; + return ReadFourBytes(o2r); + } + + int ReadFourBytes(int addr) const { + return static_cast(data_[addr]) | (data_[addr + 1] << 8) | (data_[addr + 2] << 16) | (data_[addr + 3] << 24); + } + + void Seek(int offset) { + offset_ = offset; + } + + void SeekAdd(int delta) { + offset_ += delta; + } + +private: + + std::vector data_; + int offset_ = 0; +}; + +class Palette { +public: + + Palette() = default; + + Palette(Rom& 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(Rom& 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; + } + + Rom& 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 Sprite { + std::vector subsprites; + + Magick::Image render(Rom& m3, const Palette& palette, const int gfxPtr) const { + Magick::Image result; + + if (subsprites.empty()) return result; + + 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; + + result = Magick::Image(Magick::Geometry(width, height), "transparent"); + result.modifyImage(); + + Magick::Pixels view(result); + + 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 result; + } +}; + +struct SpriteSheet { + int gfxPtr; + std::vector sprites; + + Magick::Image render(Rom& m3, PaletteSet& palettes) const { + + + 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_.push_back(std::move(ss)); + if (i == 1) return; + } + } + + const std::vector& SpriteSheets() const { return spritesheets_; } + +private: + + int GetPointerToSheet(Rom& m3, int index) { + int a = m3.ReadFourBytes(baseAddr_ + 4 + (index << 2)); + if (a == 0) return -1; + return a + baseAddr_; + } + + int GetGfxPointer(Rom& m3, int index) { + return m3.ReadFourBytes(gfxAddr_ + 4 + (index << 2)) + gfxAddr_; + } + + int baseAddr_; + int gfxAddr_; + std::vector spritesheets_; +}; + +int main(int argc, char** argv) { + if (argc != 2) { + std::cout << "Usage" << std::endl; + return -1; + } + + Magick::InitializeMagick(nullptr); + + Rom m3(argv[1]); + PaletteSet palettes(m3); + //const int banks[] = {0x1A442A4, 0x1AE0638, 0x1AEE4C4, 0x1AF1ED0}; + Bank b1(m3, 0x1A442A4, 0x14383E4); + Magick::Image im = b1.SpriteSheets()[1].render(m3, palettes); + im.magick("png"); + im.write("out.png"); + + return 0; +} \ No newline at end of file -- cgit 1.4.1