summary refs log tree commit diff stats
path: root/tools/sprite_dumper/sprite_dumper.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/sprite_dumper.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/sprite_dumper.cpp')
-rw-r--r--tools/sprite_dumper/sprite_dumper.cpp361
1 files changed, 361 insertions, 0 deletions
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 @@
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#include "common.h"
12
13class Palette {
14public:
15
16 Palette() = default;
17
18 Palette(BufferView m3, const int addr) {
19 for (int i=0; i<16; i++) {
20 unsigned short ch = m3.ReadTwoBytes(addr + (i << 1));
21 int r = (ch & 0x1F);
22 int g = ((ch >> 5) & 0x1F);
23 int b = ((ch >> 10) & 0x1F);
24 colors_.push_back(Magick::ColorRGB((r << 3)/256.0, (g << 3)/256.0, (b << 3)/256.0));
25 }
26 }
27
28 const std::vector<Magick::Color>& Colors() const { return colors_; }
29
30private:
31 std::vector<Magick::Color> colors_;
32};
33
34class PaletteSet {
35public:
36 PaletteSet(BufferView m3) : m3_(m3) {
37 for (int i=0; i<16; i++) {
38 defaultPals_.emplace_back(m3, GetPointer(0) + (i << 5));
39 }
40 }
41
42 const Palette& GetPalette(int spriteindex, int palnum = -1) {
43 int a = GetPointer(spriteindex);
44
45 if ((spriteindex > 0xFF) && (spriteindex < 0x26C)) {
46 specialPals_[spriteindex] = Palette(m3_, a);
47 return specialPals_[spriteindex];
48 } else {
49 if (palnum == -1) {
50 palnum = static_cast<unsigned char>(m3_.ReadByte(0x1433D7C + 1 + ((spriteindex + 1) * 12))) & 0xF;
51 }
52
53 return defaultPals_.at(palnum);
54 }
55 }
56
57private:
58
59 int GetPointer(int spriteindex) {
60 const int ADDR = 0x1A41548;
61
62 int index = 0;
63
64 if ((spriteindex > 0xFF) && (spriteindex < 0x26C)) {
65 // These sprites use the (spriteindex - 0xFF)th entry
66 index = spriteindex - 0xff;
67 }
68
69 int a = m3_.ReadFourBytes(ADDR + 4 + (index << 2));
70 if (a == -1) return -1;
71 return ADDR + a;
72 }
73
74 BufferView m3_;
75 std::vector<Palette> defaultPals_;
76 std::map<int, Palette> specialPals_;
77};
78
79struct Subsprite {
80 int x;
81 int y;
82 int tile;
83 bool flipH;
84 bool flipV;
85 int objSize;
86 int objShape;
87
88 int Width() const { return WIDTHS[objShape][objSize]; }
89
90 int Height() const { return HEIGHTS[objShape][objSize]; }
91
92private:
93
94 static constexpr int WIDTHS[3][4] = { { 8, 16, 32, 64 }, { 16, 32, 32, 64 }, { 8, 8, 16, 32 } };
95 static constexpr int HEIGHTS[3][4] = { { 8, 16, 32, 64 }, { 8, 8, 16, 32 }, { 16, 32, 32, 64 } };
96};
97
98struct FrameOutput {
99 Magick::Image image;
100 int width;
101 int height;
102 int centerX;
103 int centerY;
104};
105
106struct Sprite {
107 std::vector<Subsprite> subsprites;
108
109 FrameOutput render(BufferView m3, const Palette& palette, const int gfxPtr) const {
110 FrameOutput output;
111
112 if (subsprites.empty()) return output;
113
114 int minX = subsprites[0].x;
115 int minY = subsprites[0].y;
116 int maxX = minX + subsprites[0].Width();
117 int maxY = minY + subsprites[0].Height();
118
119 for (int j=1; j<subsprites.size(); j++) {
120 const Subsprite& o = subsprites[j];
121
122 if (o.x < minX) minX = o.x;
123 if (o.y < minY) minY = o.y;
124 if ((o.x + o.Width()) > maxX) maxX = o.x + o.Width();
125 if ((o.y + o.Height()) > maxY) maxY = o.y + o.Height();
126 }
127
128 int width = maxX - minX;
129 int height = maxY - minY;
130 int centerX = -minX;
131 int centerY = -minY;
132
133 output.width = width;
134 output.height = height;
135 output.centerX = centerX;
136 output.centerY = centerY;
137 output.image = Magick::Image(Magick::Geometry(width, height), "transparent");
138 output.image.modifyImage();
139
140 Magick::Pixels view(output.image);
141
142 for (const Subsprite& o : subsprites) {
143 int tilePointer = o.tile << 5;
144 for (int y=0; y<o.Height(); y += 8) {
145 int actualY = o.flipV ? (o.Height() - y - 8) : y;
146
147 for (int x=0; x<o.Width(); x += 8) {
148 int tileData[8][8];
149
150 int tileSrcOffset = gfxPtr + tilePointer;
151 for (int ty=0; ty<8; ty++) {
152 for (int tx=0; tx<4; tx++) {
153 unsigned char ch = m3.ReadByte(tileSrcOffset++);
154 tileData[tx*2][ty] = static_cast<unsigned char>(ch & 0xF);
155 tileData[tx*2+1][ty] = static_cast<unsigned char>((ch >> 4) & 0xF);
156 }
157 }
158
159 tilePointer += 0x20;
160
161 int actualX = o.flipH ? (o.Width() - x - 8) : x;
162
163 int destX = o.x + centerX + actualX;
164 int destY = o.y + centerY + actualY;
165
166 Magick::PixelPacket* pixels = view.get(destX,destY,8,8);
167
168 for (int ty=0; ty<8; ty++) {
169 int actualTy = o.flipV ? (7-ty) : ty;
170
171 for (int tx=0; tx<8; tx++) {
172 int actualTx = o.flipH ? (7-tx) : tx;
173
174 if (tileData[actualTx][actualTy] != 0) {
175 //auto& c = palette.Colors().at(tileData[actualTx][actualTy]);
176 //std::cout << c.redQuantum() << "," << c.greenQuantum() << "," << c.blueQuantum() << std::endl;
177 *pixels = palette.Colors().at(tileData[actualTx][actualTy]);
178 //std::cout << tileData[actualTx][actualTy] << std::endl;
179 }
180 pixels++;
181 }
182 }
183
184 view.sync();
185 }
186 }
187 }
188
189 return output;
190 }
191};
192
193struct SpriteSheet {
194 int spriteindex = 0;
195 int gfxPtr;
196 std::vector<Sprite> sprites;
197
198 void render(BufferView m3, PaletteSet& palettes, std::vector<FrameOutput>& frames) const {
199 const Palette& palette = palettes.GetPalette(spriteindex);
200 for (int i=0; i<sprites.size(); i++) {
201 FrameOutput f = sprites[i].render(m3, palette, gfxPtr);
202 frames.push_back(std::move(f));
203 }
204 }
205};
206
207class Bank {
208public:
209
210 Bank(BufferView m3, const int baseAddr, const int gfxAddr) : baseAddr_(baseAddr), gfxAddr_(gfxAddr) {
211 int numEntries = m3.ReadFourBytes(baseAddr);
212 std::cout << numEntries << std::endl;
213
214 for (int i = 0; i < numEntries; i++) {
215 int sheetAddr = GetPointerToSheet(m3, i);
216 if (sheetAddr == -1) continue;
217
218 SpriteSheet ss;
219 ss.spriteindex = i;
220 ss.gfxPtr = GetGfxPointer(m3, i);
221
222 m3.Seek(sheetAddr);
223 m3.SeekAdd(8);
224
225 unsigned short spriteCount = m3.ReadNextTwoBytes();
226 //std::cout << i << ":" << spriteCount << " at " << std::hex << sheetAddr << std::endl;
227 for (int j=0; j<spriteCount; j++) {
228 Sprite sprite;
229
230 unsigned short subspriteCount = m3.ReadNextTwoBytes();
231 for (int k=0; k<subspriteCount; k++) {
232 Subsprite subsprite;
233 subsprite.y = static_cast<char>(m3.ReadNextByte());
234 subsprite.x = static_cast<char>(m3.ReadNextByte());
235
236 unsigned short ch = m3.ReadNextTwoBytes();
237 subsprite.tile = static_cast<unsigned short>(ch & 0x3FF);
238 subsprite.flipH = (ch & 0x400) != 0;
239 subsprite.flipV = (ch & 0x800) != 0;
240 subsprite.objSize = static_cast<unsigned char>((ch >> 12) & 3);
241 subsprite.objShape = static_cast<unsigned char>((ch >> 14) & 3);
242
243 /*std::cout << subsprite.y << std::endl;
244 std::cout << subsprite.x << std::endl;
245 std::cout << subsprite.flipH << "," << subsprite.flipV << std::endl;*/
246
247 sprite.subsprites.push_back(std::move(subsprite));
248 }
249
250 m3.SeekAdd(2);
251
252 ss.sprites.push_back(std::move(sprite));
253 }
254
255 spritesheets_[i] = std::move(ss);
256 //if (i == 15) return;
257 }
258 }
259
260 const std::map<int, SpriteSheet>& SpriteSheets() const { return spritesheets_; }
261
262private:
263
264 int GetPointerToSheet(BufferView m3, int index) {
265 int readAt = baseAddr_ + 4 + (index << 2);
266 int a = m3.ReadFourBytes(readAt);
267 //std::cout << readAt << " :: " << a << std::hex << std::endl;
268 if (a == 0) return -1;
269 return a + baseAddr_;
270 }
271
272 int GetGfxPointer(BufferView m3, int index) {
273 return m3.ReadFourBytes(gfxAddr_ + 4 + (index << 2)) + gfxAddr_;
274 }
275
276 int baseAddr_;
277 int gfxAddr_;
278 std::map<int, SpriteSheet> spritesheets_;
279};
280
281int main(int argc, char** argv) {
282 if (argc < 3) {
283 std::cout << "Usage: ./sprite_dumper [path to rom] {sheet IDs}" << std::endl;
284 std::cout << "sheet IDs should be space separated references to the sheets to concatenate" << std::endl;
285 std::cout << "the format of the ID is BankNum.SheetNum" << std::endl;
286 return -1;
287 }
288
289 Magick::InitializeMagick(nullptr);
290
291 Rom m3(argv[1]);
292 PaletteSet palettes(m3.buffer());
293 Bank b1(m3.buffer(), 0x1A442A4, 0x14383E4);
294 Bank b2(m3.buffer(), 0x1AE0638, 0x194BC30);
295 Bank b3(m3.buffer(), 0x1AEE4C4, 0x1A012B8);
296 Bank b4(m3.buffer(), 0x1AF1ED0, 0x1A36AA0);
297
298 std::vector<FrameOutput> frames;
299
300 for (int i=2; i<argc; i++) {
301 std::stringstream argfmt;
302 argfmt << argv[i];
303
304 int bankNum;
305 char ch;
306 int sheetNum;
307 argfmt >> bankNum;
308 argfmt >> ch; //.
309 argfmt >> sheetNum;
310
311 const Bank& bankToRead = [&](){
312 switch (bankNum) {
313 case 0: return b1;
314 case 1: return b2;
315 case 2: return b3;
316 case 3: return b4;
317 default: throw std::invalid_argument("Invalid bank num: " + std::to_string(bankNum));
318 }
319 }();
320
321 bankToRead.SpriteSheets().at(sheetNum).render(m3.buffer(), palettes, frames);
322 }
323
324 int maxWidth = 0;
325 int maxHeight = 0;
326 for (const FrameOutput& f : frames) {
327 if (f.width > maxWidth) maxWidth = f.width;
328 if (f.height > maxHeight) maxHeight = f.height;
329 }
330
331 const int FRAMES_PER_ROW = 10;
332 int sheetWidth;
333 int sheetHeight;
334
335 std::ofstream datafile("out.txt");
336 datafile << maxWidth << "," << maxHeight << " cell size" << std::endl;
337 datafile << FRAMES_PER_ROW << " frames per row" << std::endl;
338 datafile << frames.size() << " frames" << std::endl;
339 datafile << std::endl;
340
341 if (frames.size() < FRAMES_PER_ROW) {
342 sheetWidth = frames.size() * maxWidth;
343 sheetHeight = maxHeight;
344 } else {
345 sheetWidth = FRAMES_PER_ROW * maxWidth;
346 sheetHeight = (frames.size() / FRAMES_PER_ROW + 1) * maxHeight;
347 }
348
349 Magick::Image sheet(Magick::Geometry(sheetWidth, sheetHeight), "transparent");
350 for (int i=0; i<frames.size(); i++) {
351 const FrameOutput& f = frames.at(i);
352 sheet.composite(f.image, (i%FRAMES_PER_ROW)*maxWidth, (i/FRAMES_PER_ROW)*maxHeight, Magick::OverCompositeOp);
353 datafile << f.width << "," << f.height << "," << f.centerX << "," << f.centerY << std::endl;
354 }
355
356 //Magick::Image im = b2.SpriteSheets().at(3).render(m3, palettes);
357 sheet.magick("png");
358 sheet.write("out.png");
359
360 return 0;
361} \ No newline at end of file