summary refs log tree commit diff stats
path: root/tools/sprite_dumper/main.cpp
diff options
context:
space:
mode:
authorKelly Rauchenberger <fefferburbia@gmail.com>2021-01-31 20:04:51 -0500
committerKelly Rauchenberger <fefferburbia@gmail.com>2021-01-31 20:04:51 -0500
commita64c85a3f5641581a19ef52a5c7898ec3cb71754 (patch)
tree8df10037fa552887966d9f0d5b147e0fd35abbf4 /tools/sprite_dumper/main.cpp
parent66e8f5b7d5d245bb7f5d4c4963e70e6b97e0bd4d (diff)
downloadtanetane-a64c85a3f5641581a19ef52a5c7898ec3cb71754.tar.gz
tanetane-a64c85a3f5641581a19ef52a5c7898ec3cb71754.tar.bz2
tanetane-a64c85a3f5641581a19ef52a5c7898ec3cb71754.zip
Abstracted some of the sprite dumper functionality out
Diffstat (limited to 'tools/sprite_dumper/main.cpp')
-rw-r--r--tools/sprite_dumper/main.cpp437
1 files changed, 0 insertions, 437 deletions
diff --git a/tools/sprite_dumper/main.cpp b/tools/sprite_dumper/main.cpp deleted file mode 100644 index 2ba37c8..0000000 --- a/tools/sprite_dumper/main.cpp +++ /dev/null
@@ -1,437 +0,0 @@
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#include <sstream>
11
12class Rom {
13public:
14
15 explicit Rom(std::string_view filename) {
16 std::ifstream romfile(filename.data(), std::ios::binary);
17 if (!romfile.is_open()) {
18 throw std::invalid_argument("Could not find ROM file: " + std::string(filename));
19 }
20
21 romfile.seekg(0, romfile.end);
22 int length = romfile.tellg();
23 romfile.seekg(0, romfile.beg);
24
25 if (length != 0x2000000) {
26 throw std::invalid_argument("Incorrect ROM length");
27 }
28
29 data_ = std::vector<char>(length, 0);
30 romfile.read(data_.data(), length);
31
32 const char header[] = {'M','O','T','H','E','R','3',0,0,0,0,0,'A','3','U','J'};
33 std::string headerTest;
34 for (int i = 0xA0; i < 0xB0; i++) {
35 if (data_.at(i) != header[i-0xA0]) {
36 std::cout << i << std::endl;
37
38 throw std::invalid_argument("Invalid ROM header");
39 }
40 }
41 }
42
43 const std::vector<char>& data() const { return data_; }
44
45 int ReadNextByte() {
46 int o2r = offset_;
47 offset_++;
48 return ReadByte(o2r);
49 }
50
51 int ReadByte(int addr) const {
52 return static_cast<unsigned char>(data_[addr]);
53 }
54
55 int ReadNextTwoBytes() {
56 int o2r = offset_;
57 offset_+=2;
58 return ReadTwoBytes(o2r);
59 }
60
61 int ReadTwoBytes(int addr) const {
62 return static_cast<unsigned char>(data_[addr]) | (static_cast<unsigned char>(data_[addr + 1]) << 8);
63 }
64
65 int ReadNextFourBytes() {
66 int o2r = offset_;
67 offset_+=4;
68 return ReadFourBytes(o2r);
69 }
70
71 unsigned long ReadFourBytes(int addr) const {
72 return static_cast<unsigned char>(data_[addr]) | (static_cast<unsigned char>(data_[addr + 1]) << 8) | (static_cast<unsigned char>(data_[addr + 2]) << 16) | (static_cast<unsigned char>(data_[addr + 3]) << 24);
73 }
74
75 void Seek(int offset) {
76 offset_ = offset;
77 }
78
79 void SeekAdd(int delta) {
80 offset_ += delta;
81 }
82
83private:
84
85 std::vector<char> data_;
86 int offset_ = 0;
87};
88
89class Palette {
90public:
91
92 Palette() = default;
93
94 Palette(Rom& m3, const int addr) {
95 for (int i=0; i<16; i++) {
96 unsigned short ch = m3.ReadTwoBytes(addr + (i << 1));
97 int r = (ch & 0x1F);
98 int g = ((ch >> 5) & 0x1F);
99 int b = ((ch >> 10) & 0x1F);
100 colors_.push_back(Magick::ColorRGB((r << 3)/256.0, (g << 3)/256.0, (b << 3)/256.0));
101 }
102 }
103
104 const std::vector<Magick::Color>& Colors() const { return colors_; }
105
106private:
107 std::vector<Magick::Color> colors_;
108};
109
110class PaletteSet {
111public:
112 PaletteSet(Rom& m3) : m3_(m3) {
113 for (int i=0; i<16; i++) {
114 defaultPals_.emplace_back(m3, GetPointer(0) + (i << 5));
115 }
116 }
117
118 const Palette& GetPalette(int spriteindex, int palnum = -1) {
119 int a = GetPointer(spriteindex);
120
121 if ((spriteindex > 0xFF) && (spriteindex < 0x26C)) {
122 specialPals_[spriteindex] = Palette(m3_, a);
123 return specialPals_[spriteindex];
124 } else {
125 if (palnum == -1) {
126 palnum = static_cast<unsigned char>(m3_.ReadByte(0x1433D7C + 1 + ((spriteindex + 1) * 12))) & 0xF;
127 }
128
129 return defaultPals_.at(palnum);
130 }
131 }
132
133private:
134
135 int GetPointer(int spriteindex) {
136 const int ADDR = 0x1A41548;
137
138 int index = 0;
139
140 if ((spriteindex > 0xFF) && (spriteindex < 0x26C)) {
141 // These sprites use the (spriteindex - 0xFF)th entry
142 index = spriteindex - 0xff;
143 }
144
145 int a = m3_.ReadFourBytes(ADDR + 4 + (index << 2));
146 if (a == -1) return -1;
147 return ADDR + a;
148 }
149
150 Rom& m3_;
151 std::vector<Palette> defaultPals_;
152 std::map<int, Palette> specialPals_;
153};
154
155struct Subsprite {
156 int x;
157 int y;
158 int tile;
159 bool flipH;
160 bool flipV;
161 int objSize;
162 int objShape;
163
164 int Width() const { return WIDTHS[objShape][objSize]; }
165
166 int Height() const { return HEIGHTS[objShape][objSize]; }
167
168private:
169
170 static constexpr int WIDTHS[3][4] = { { 8, 16, 32, 64 }, { 16, 32, 32, 64 }, { 8, 8, 16, 32 } };
171 static constexpr int HEIGHTS[3][4] = { { 8, 16, 32, 64 }, { 8, 8, 16, 32 }, { 16, 32, 32, 64 } };
172};
173
174struct FrameOutput {
175 Magick::Image image;
176 int width;
177 int height;
178 int centerX;
179 int centerY;
180};
181
182struct Sprite {
183 std::vector<Subsprite> subsprites;
184
185 FrameOutput render(Rom& m3, const Palette& palette, const int gfxPtr) const {
186 FrameOutput output;
187
188 if (subsprites.empty()) return output;
189
190 int minX = subsprites[0].x;
191 int minY = subsprites[0].y;
192 int maxX = minX + subsprites[0].Width();
193 int maxY = minY + subsprites[0].Height();
194
195 for (int j=1; j<subsprites.size(); j++) {
196 const Subsprite& o = subsprites[j];
197
198 if (o.x < minX) minX = o.x;
199 if (o.y < minY) minY = o.y;
200 if ((o.x + o.Width()) > maxX) maxX = o.x + o.Width();
201 if ((o.y + o.Height()) > maxY) maxY = o.y + o.Height();
202 }
203
204 int width = maxX - minX;
205 int height = maxY - minY;
206 int centerX = -minX;
207 int centerY = -minY;
208
209 output.width = width;
210 output.height = height;
211 output.centerX = centerX;
212 output.centerY = centerY;
213 output.image = Magick::Image(Magick::Geometry(width, height), "transparent");
214 output.image.modifyImage();
215
216 Magick::Pixels view(output.image);
217
218 for (const Subsprite& o : subsprites) {
219 int tilePointer = o.tile << 5;
220 for (int y=0; y<o.Height(); y += 8) {
221 int actualY = o.flipV ? (o.Height() - y - 8) : y;
222
223 for (int x=0; x<o.Width(); x += 8) {
224 int tileData[8][8];
225
226 int tileSrcOffset = gfxPtr + tilePointer;
227 for (int ty=0; ty<8; ty++) {
228 for (int tx=0; tx<4; tx++) {
229 unsigned char ch = m3.ReadByte(tileSrcOffset++);
230 tileData[tx*2][ty] = static_cast<unsigned char>(ch & 0xF);
231 tileData[tx*2+1][ty] = static_cast<unsigned char>((ch >> 4) & 0xF);
232 }
233 }
234
235 tilePointer += 0x20;
236
237 int actualX = o.flipH ? (o.Width() - x - 8) : x;
238
239 int destX = o.x + centerX + actualX;
240 int destY = o.y + centerY + actualY;
241
242 Magick::PixelPacket* pixels = view.get(destX,destY,8,8);
243
244 for (int ty=0; ty<8; ty++) {
245 int actualTy = o.flipV ? (7-ty) : ty;
246
247 for (int tx=0; tx<8; tx++) {
248 int actualTx = o.flipH ? (7-tx) : tx;
249
250 if (tileData[actualTx][actualTy] != 0) {
251 //auto& c = palette.Colors().at(tileData[actualTx][actualTy]);
252 //std::cout << c.redQuantum() << "," << c.greenQuantum() << "," << c.blueQuantum() << std::endl;
253 *pixels = palette.Colors().at(tileData[actualTx][actualTy]);
254 //std::cout << tileData[actualTx][actualTy] << std::endl;
255 }
256 pixels++;
257 }
258 }
259
260 view.sync();
261 }
262 }
263 }
264
265 return output;
266 }
267};
268
269struct SpriteSheet {
270 int spriteindex = 0;
271 int gfxPtr;
272 std::vector<Sprite> sprites;
273
274 void render(Rom& m3, PaletteSet& palettes, std::vector<FrameOutput>& frames) const {
275 const Palette& palette = palettes.GetPalette(spriteindex);
276 for (int i=0; i<sprites.size(); i++) {
277 FrameOutput f = sprites[i].render(m3, palette, gfxPtr);
278 frames.push_back(std::move(f));
279 }
280 }
281};
282
283class Bank {
284public:
285
286 Bank(Rom& m3, const int baseAddr, const int gfxAddr) : baseAddr_(baseAddr), gfxAddr_(gfxAddr) {
287 int numEntries = m3.ReadFourBytes(baseAddr);
288 std::cout << numEntries << std::endl;
289
290 for (int i = 0; i < numEntries; i++) {
291 int sheetAddr = GetPointerToSheet(m3, i);
292 if (sheetAddr == -1) continue;
293
294 SpriteSheet ss;
295 ss.spriteindex = i;
296 ss.gfxPtr = GetGfxPointer(m3, i);
297
298 m3.Seek(sheetAddr);
299 m3.SeekAdd(8);
300
301 unsigned short spriteCount = m3.ReadNextTwoBytes();
302 //std::cout << i << ":" << spriteCount << " at " << std::hex << sheetAddr << std::endl;
303 for (int j=0; j<spriteCount; j++) {
304 Sprite sprite;
305
306 unsigned short subspriteCount = m3.ReadNextTwoBytes();
307 for (int k=0; k<subspriteCount; k++) {
308 Subsprite subsprite;
309 subsprite.y = static_cast<char>(m3.ReadNextByte());
310 subsprite.x = static_cast<char>(m3.ReadNextByte());
311
312 unsigned short ch = m3.ReadNextTwoBytes();
313 subsprite.tile = static_cast<unsigned short>(ch & 0x3FF);
314 subsprite.flipH = (ch & 0x400) != 0;
315 subsprite.flipV = (ch & 0x800) != 0;
316 subsprite.objSize = static_cast<unsigned char>((ch >> 12) & 3);
317 subsprite.objShape = static_cast<unsigned char>((ch >> 14) & 3);
318
319 /*std::cout << subsprite.y << std::endl;
320 std::cout << subsprite.x << std::endl;
321 std::cout << subsprite.flipH << "," << subsprite.flipV << std::endl;*/
322
323 sprite.subsprites.push_back(std::move(subsprite));
324 }
325
326 m3.SeekAdd(2);
327
328 ss.sprites.push_back(std::move(sprite));
329 }
330
331 spritesheets_[i] = std::move(ss);
332 //if (i == 15) return;
333 }
334 }
335
336 const std::map<int, SpriteSheet>& SpriteSheets() const { return spritesheets_; }
337
338private:
339
340 int GetPointerToSheet(Rom& m3, int index) {
341 int readAt = baseAddr_ + 4 + (index << 2);
342 int a = m3.ReadFourBytes(readAt);
343 //std::cout << readAt << " :: " << a << std::hex << std::endl;
344 if (a == 0) return -1;
345 return a + baseAddr_;
346 }
347
348 int GetGfxPointer(Rom& m3, int index) {
349 return m3.ReadFourBytes(gfxAddr_ + 4 + (index << 2)) + gfxAddr_;
350 }
351
352 int baseAddr_;
353 int gfxAddr_;
354 std::map<int, SpriteSheet> spritesheets_;
355};
356
357int main(int argc, char** argv) {
358 if (argc < 3) {
359 std::cout << "Usage: ./sprite_dumper [path to rom] {sheet IDs}" << std::endl;
360 std::cout << "sheet IDs should be space separated references to the sheets to concatenate" << std::endl;
361 std::cout << "the format of the ID is BankNum.SheetNum" << std::endl;
362 return -1;
363 }
364
365 Magick::InitializeMagick(nullptr);
366
367 Rom m3(argv[1]);
368 PaletteSet palettes(m3);
369 Bank b1(m3, 0x1A442A4, 0x14383E4);
370 Bank b2(m3, 0x1AE0638, 0x194BC30);
371 Bank b3(m3, 0x1AEE4C4, 0x1A012B8);
372 Bank b4(m3, 0x1AF1ED0, 0x1A36AA0);
373
374 std::vector<FrameOutput> frames;
375
376 for (int i=2; i<argc; i++) {
377 std::stringstream argfmt;
378 argfmt << argv[i];
379
380 int bankNum;
381 char ch;
382 int sheetNum;
383 argfmt >> bankNum;
384 argfmt >> ch; //.
385 argfmt >> sheetNum;
386
387 const Bank& bankToRead = [&](){
388 switch (bankNum) {
389 case 0: return b1;
390 case 1: return b2;
391 case 2: return b3;
392 case 3: return b4;
393 default: throw std::invalid_argument("Invalid bank num: " + std::to_string(bankNum));
394 }
395 }();
396
397 bankToRead.SpriteSheets().at(sheetNum).render(m3, palettes, frames);
398 }
399
400 int maxWidth = 0;
401 int maxHeight = 0;
402 for (const FrameOutput& f : frames) {
403 if (f.width > maxWidth) maxWidth = f.width;
404 if (f.height > maxHeight) maxHeight = f.height;
405 }
406
407 const int FRAMES_PER_ROW = 10;
408 int sheetWidth;
409 int sheetHeight;
410
411 std::ofstream datafile("out.txt");
412 datafile << maxWidth << "," << maxHeight << " cell size" << std::endl;
413 datafile << FRAMES_PER_ROW << " frames per row" << std::endl;
414 datafile << frames.size() << " frames" << std::endl;
415 datafile << std::endl;
416
417 if (frames.size() < FRAMES_PER_ROW) {
418 sheetWidth = frames.size() * maxWidth;
419 sheetHeight = maxHeight;
420 } else {
421 sheetWidth = FRAMES_PER_ROW * maxWidth;
422 sheetHeight = (frames.size() / FRAMES_PER_ROW + 1) * maxHeight;
423 }
424
425 Magick::Image sheet(Magick::Geometry(sheetWidth, sheetHeight), "transparent");
426 for (int i=0; i<frames.size(); i++) {
427 const FrameOutput& f = frames.at(i);
428 sheet.composite(f.image, (i%FRAMES_PER_ROW)*maxWidth, (i/FRAMES_PER_ROW)*maxHeight, Magick::OverCompositeOp);
429 datafile << f.width << "," << f.height << "," << f.centerX << "," << f.centerY << std::endl;
430 }
431
432 //Magick::Image im = b2.SpriteSheets().at(3).render(m3, palettes);
433 sheet.magick("png");
434 sheet.write("out.png");
435
436 return 0;
437} \ No newline at end of file