summary refs log tree commit diff stats
path: root/tools/sprite_dumper
diff options
context:
space:
mode:
authorKelly Rauchenberger <fefferburbia@gmail.com>2021-01-30 22:20:23 -0500
committerKelly Rauchenberger <fefferburbia@gmail.com>2021-01-30 22:20:23 -0500
commit39cc01d386bc765f43d00118599846fc7273eaaf (patch)
treea560d88a524f8ec48dd540265032129f4c91c7cb /tools/sprite_dumper
parentd1df87ce04f6d79fed94ab154fa098ccc83ebab8 (diff)
downloadtanetane-39cc01d386bc765f43d00118599846fc7273eaaf.tar.gz
tanetane-39cc01d386bc765f43d00118599846fc7273eaaf.tar.bz2
tanetane-39cc01d386bc765f43d00118599846fc7273eaaf.zip
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.
Diffstat (limited to 'tools/sprite_dumper')
-rw-r--r--tools/sprite_dumper/CMakeLists.txt15
-rw-r--r--tools/sprite_dumper/main.cpp358
2 files changed, 373 insertions, 0 deletions
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 @@
1cmake_minimum_required (VERSION 3.1)
2project (sprite_dumper)
3
4set(CMAKE_BUILD_TYPE Debug)
5
6find_package(PkgConfig)
7pkg_check_modules(GraphicsMagick GraphicsMagick++ REQUIRED)
8
9include_directories(${GraphicsMagick_INCLUDE_DIRS})
10
11add_executable(sprite_dumper main.cpp)
12
13set_property(TARGET sprite_dumper PROPERTY CXX_STANDARD 17)
14set_property(TARGET sprite_dumper PROPERTY CXX_STANDARD_REQUIRED ON)
15target_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 @@
1// Inspired a lot by https://github.com/jeffman/MOTHER-3-Funland
2
3#include <fstream>
4#include <string_view>
5#include <stdexcept>
6#include <string>
7#include <vector>
8#include <iostream>
9#include <Magick++.h>
10
11class Rom {
12public:
13
14 explicit Rom(std::string_view filename) {
15 std::ifstream romfile(filename.data(), std::ios::binary);
16 if (!romfile.is_open()) {
17 throw std::invalid_argument("Could not find ROM file: " + std::string(filename));
18 }
19
20 romfile.seekg(0, romfile.end);
21 int length = romfile.tellg();
22 romfile.seekg(0, romfile.beg);
23
24 if (length != 0x2000000) {
25 throw std::invalid_argument("Incorrect ROM length");
26 }
27
28 data_ = std::vector<char>(length, 0);
29 romfile.read(data_.data(), length);
30
31 const char header[] = {'M','O','T','H','E','R','3',0,0,0,0,0,'A','3','U','J'};
32 std::string headerTest;
33 for (int i = 0xA0; i < 0xB0; i++) {
34 if (data_.at(i) != header[i-0xA0]) {
35 std::cout << i << std::endl;
36
37 throw std::invalid_argument("Invalid ROM header");
38 }
39 }
40 }
41
42 const std::vector<char>& data() const { return data_; }
43
44 int ReadNextByte() {
45 int o2r = offset_;
46 offset_++;
47 return ReadByte(o2r);
48 }
49
50 int ReadByte(int addr) const {
51 return static_cast<unsigned char>(data_[addr]);
52 }
53
54 int ReadNextTwoBytes() {
55 int o2r = offset_;
56 offset_+=2;
57 return ReadTwoBytes(o2r);
58 }
59
60 int ReadTwoBytes(int addr) const {
61 return static_cast<unsigned char>(data_[addr]) | (data_[addr + 1] << 8);
62 }
63
64 int ReadNextFourBytes() {
65 int o2r = offset_;
66 offset_+=4;
67 return ReadFourBytes(o2r);
68 }
69
70 int ReadFourBytes(int addr) const {
71 return static_cast<unsigned char>(data_[addr]) | (data_[addr + 1] << 8) | (data_[addr + 2] << 16) | (data_[addr + 3] << 24);
72 }
73
74 void Seek(int offset) {
75 offset_ = offset;
76 }
77
78 void SeekAdd(int delta) {
79 offset_ += delta;
80 }
81
82private:
83
84 std::vector<char> data_;
85 int offset_ = 0;
86};
87
88class Palette {
89public:
90
91 Palette() = default;
92
93 Palette(Rom& m3, const int addr) {
94 for (int i=0; i<16; i++) {
95 unsigned short ch = m3.ReadTwoBytes(addr + (i << 1));
96 int r = (ch & 0x1F);
97 int g = ((ch >> 5) & 0x1F);
98 int b = ((ch >> 10) & 0x1F);
99 colors_.push_back(Magick::ColorRGB((r << 3)/256.0, (g << 3)/256.0, (b << 3)/256.0));
100 }
101 }
102
103 const std::vector<Magick::Color>& Colors() const { return colors_; }
104
105private:
106 std::vector<Magick::Color> colors_;
107};
108
109class PaletteSet {
110public:
111 PaletteSet(Rom& m3) : m3_(m3) {
112 for (int i=0; i<16; i++) {
113 defaultPals_.emplace_back(m3, GetPointer(0) + (i << 5));
114 }
115 }
116
117 const Palette& GetPalette(int spriteindex, int palnum = -1) {
118 int a = GetPointer(spriteindex);
119
120 if ((spriteindex > 0xFF) && (spriteindex < 0x26C)) {
121 specialPals_[spriteindex] = Palette(m3_, a);
122 return specialPals_[spriteindex];
123 } else {
124 if (palnum == -1) {
125 palnum = static_cast<unsigned char>(m3_.ReadByte(0x1433D7C + 1 + ((spriteindex + 1) * 12))) & 0xF;
126 }
127
128 return defaultPals_.at(palnum);
129 }
130 }
131
132private:
133
134 int GetPointer(int spriteindex) {
135 const int ADDR = 0x1A41548;
136
137 int index = 0;
138
139 if ((spriteindex > 0xFF) && (spriteindex < 0x26C)) {
140 // These sprites use the (spriteindex - 0xFF)th entry
141 index = spriteindex - 0xff;
142 }
143
144 int a = m3_.ReadFourBytes(ADDR + 4 + (index << 2));
145 if (a == -1) return -1;
146 return ADDR + a;
147 }
148
149 Rom& m3_;
150 std::vector<Palette> defaultPals_;
151 std::map<int, Palette> specialPals_;
152};
153
154struct Subsprite {
155 int x;
156 int y;
157 int tile;
158 bool flipH;
159 bool flipV;
160 int objSize;
161 int objShape;
162
163 int Width() const { return WIDTHS[objShape][objSize]; }
164
165 int Height() const { return HEIGHTS[objShape][objSize]; }
166
167private:
168
169 static constexpr int WIDTHS[3][4] = { { 8, 16, 32, 64 }, { 16, 32, 32, 64 }, { 8, 8, 16, 32 } };
170 static constexpr int HEIGHTS[3][4] = { { 8, 16, 32, 64 }, { 8, 8, 16, 32 }, { 16, 32, 32, 64 } };
171};
172
173struct Sprite {
174 std::vector<Subsprite> subsprites;
175
176 Magick::Image render(Rom& m3, const Palette& palette, const int gfxPtr) const {
177 Magick::Image result;
178
179 if (subsprites.empty()) return result;
180
181 int minX = subsprites[0].x;
182 int minY = subsprites[0].y;
183 int maxX = minX + subsprites[0].Width();
184 int maxY = minY + subsprites[0].Height();
185
186 for (int j=1; j<subsprites.size(); j++) {
187 const Subsprite& o = subsprites[j];
188
189 if (o.x < minX) minX = o.x;
190 if (o.y < minY) minY = o.y;
191 if ((o.x + o.Width()) > maxX) maxX = o.x + o.Width();
192 if ((o.y + o.Height()) > maxY) maxY = o.y + o.Height();
193 }
194
195 int width = maxX - minX;
196 int height = maxY - minY;
197 int centerX = -minX;
198 int centerY = -minY;
199
200 result = Magick::Image(Magick::Geometry(width, height), "transparent");
201 result.modifyImage();
202
203 Magick::Pixels view(result);
204
205 for (const Subsprite& o : subsprites) {
206 int tilePointer = o.tile << 5;
207 for (int y=0; y<o.Height(); y += 8) {
208 int actualY = o.flipV ? (o.Height() - y - 8) : y;
209
210 for (int x=0; x<o.Width(); x += 8) {
211 int tileData[8][8];
212
213 int tileSrcOffset = gfxPtr + tilePointer;
214 for (int ty=0; ty<8; ty++) {
215 for (int tx=0; tx<4; tx++) {
216 unsigned char ch = m3.ReadByte(tileSrcOffset++);
217 tileData[tx*2][ty] = static_cast<unsigned char>(ch & 0xF);
218 tileData[tx*2+1][ty] = static_cast<unsigned char>((ch >> 4) & 0xF);
219 }
220 }
221
222 tilePointer += 0x20;
223
224 int actualX = o.flipH ? (o.Width() - x - 8) : x;
225
226 int destX = o.x + centerX + actualX;
227 int destY = o.y + centerY + actualY;
228
229 Magick::PixelPacket* pixels = view.get(destX,destY,8,8);
230
231 for (int ty=0; ty<8; ty++) {
232 int actualTy = o.flipV ? (7-ty) : ty;
233
234 for (int tx=0; tx<8; tx++) {
235 int actualTx = o.flipH ? (7-tx) : tx;
236
237 if (tileData[actualTx][actualTy] != 0) {
238 auto& c = palette.Colors().at(tileData[actualTx][actualTy]);
239 std::cout << c.redQuantum() << "," << c.greenQuantum() << "," << c.blueQuantum() << std::endl;
240 *pixels = palette.Colors().at(tileData[actualTx][actualTy]);
241 std::cout << tileData[actualTx][actualTy] << std::endl;
242 }
243 pixels++;
244 }
245 }
246
247 view.sync();
248 }
249 }
250 }
251
252 return result;
253 }
254};
255
256struct SpriteSheet {
257 int gfxPtr;
258 std::vector<Sprite> sprites;
259
260 Magick::Image render(Rom& m3, PaletteSet& palettes) const {
261
262
263 for (int i=0; i<sprites.size(); i++) {
264 const Palette& palette = palettes.GetPalette(i);
265 return sprites[0].render(m3, palette, gfxPtr);
266
267 }
268 }
269};
270
271class Bank {
272public:
273
274 Bank(Rom& m3, const int baseAddr, const int gfxAddr) : baseAddr_(baseAddr), gfxAddr_(gfxAddr) {
275 int numEntries = m3.ReadFourBytes(baseAddr);
276 std::cout << numEntries << std::endl;
277
278 for (int i = 0; i < numEntries; i++) {
279 int sheetAddr = GetPointerToSheet(m3, i);
280 if (sheetAddr == -1) continue;
281
282 SpriteSheet ss;
283 ss.gfxPtr = GetGfxPointer(m3, i);
284
285 m3.Seek(sheetAddr);
286 m3.SeekAdd(8);
287
288 unsigned short spriteCount = m3.ReadNextTwoBytes();
289 for (int j=0; j<spriteCount; j++) {
290 Sprite sprite;
291
292 unsigned short subspriteCount = m3.ReadNextTwoBytes();
293 for (int k=0; k<subspriteCount; k++) {
294 Subsprite subsprite;
295 subsprite.y = static_cast<char>(m3.ReadNextByte());
296 subsprite.x = static_cast<char>(m3.ReadNextByte());
297
298 unsigned short ch = m3.ReadNextTwoBytes();
299 subsprite.tile = static_cast<unsigned short>(ch & 0x3FF);
300 subsprite.flipH = (ch & 0x400) != 0;
301 subsprite.flipV = (ch & 0x800) != 0;
302 subsprite.objSize = static_cast<unsigned char>((ch >> 12) & 3);
303 subsprite.objShape = static_cast<unsigned char>((ch >> 14) & 3);
304
305 std::cout << subsprite.y << std::endl;
306 std::cout << subsprite.x << std::endl;
307 std::cout << subsprite.flipH << "," << subsprite.flipV << std::endl;
308
309 sprite.subsprites.push_back(std::move(subsprite));
310 }
311
312 m3.SeekAdd(2);
313
314 ss.sprites.push_back(std::move(sprite));
315 }
316
317 spritesheets_.push_back(std::move(ss));
318 if (i == 1) return;
319 }
320 }
321
322 const std::vector<SpriteSheet>& SpriteSheets() const { return spritesheets_; }
323
324private:
325
326 int GetPointerToSheet(Rom& m3, int index) {
327 int a = m3.ReadFourBytes(baseAddr_ + 4 + (index << 2));
328 if (a == 0) return -1;
329 return a + baseAddr_;
330 }
331
332 int GetGfxPointer(Rom& m3, int index) {
333 return m3.ReadFourBytes(gfxAddr_ + 4 + (index << 2)) + gfxAddr_;
334 }
335
336 int baseAddr_;
337 int gfxAddr_;
338 std::vector<SpriteSheet> spritesheets_;
339};
340
341int main(int argc, char** argv) {
342 if (argc != 2) {
343 std::cout << "Usage" << std::endl;
344 return -1;
345 }
346
347 Magick::InitializeMagick(nullptr);
348
349 Rom m3(argv[1]);
350 PaletteSet palettes(m3);
351 //const int banks[] = {0x1A442A4, 0x1AE0638, 0x1AEE4C4, 0x1AF1ED0};
352 Bank b1(m3, 0x1A442A4, 0x14383E4);
353 Magick::Image im = b1.SpriteSheets()[1].render(m3, palettes);
354 im.magick("png");
355 im.write("out.png");
356
357 return 0;
358} \ No newline at end of file