silphscope 1.4.1 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -3
- package/ball-data/ballData.json +130 -12
- package/package.json +18 -2
- package/rom-configs/{firered.js → fireredRev0.js} +2 -1
- package/rom-configs/fireredRev1.js +12 -0
- package/rom-configs/leafgreenRev0.js +10 -0
- package/rom-configs/leafgreenRev1.js +10 -0
- package/src/get-rom-config.js +19 -6
- package/src/graphics/balls/render-ball-particle.js +36 -3
- package/src/graphics/balls/render-balls.js +39 -4
- package/src/graphics/graphics-extractor-main.js +10 -1
- package/src/graphics/moves/render-moves.js +6 -0
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
still a WIP however hopefully with some more work I can get this into a workable state...
|
|
2
2
|
|
|
3
|
+
(Amazingly Newer!) Update:
|
|
4
|
+
|
|
5
|
+
so now ball extraction works! still need to cut the images up but that should be simple
|
|
6
|
+
|
|
7
|
+
(also move graphics are basically done except for ICE_CHUNK it is a weird image... and I don't know how I am going to cut it up... but everything else is working! that makes it sound like ICE_CHUNK doesn't work... which it does it just doesn't get nicely cut up)
|
|
8
|
+
|
|
3
9
|
(Even Newer!) Update:
|
|
4
10
|
|
|
5
11
|
moves now work... kinda... still working on getting it all the way done but it mostly works!
|
|
@@ -26,11 +32,12 @@ await renderAllGraphics(rom, {
|
|
|
26
32
|
outputIconDir: "./Assets/Icons", // same thing here :p
|
|
27
33
|
outputTrainerDir: "./Assets/Trainers", // ...
|
|
28
34
|
outputMoveDir: "./Assets/Moves",
|
|
29
|
-
sortUnusedMoves: true // just sorts the unused moves into a sub-directory
|
|
35
|
+
sortUnusedMoves: true, // just sorts the unused moves into a sub-directory
|
|
36
|
+
outputBallDir: "./Assets/Balls"
|
|
30
37
|
});
|
|
31
38
|
```
|
|
32
39
|
|
|
33
|
-
Of course though the above is for extracting all graphics (which is kinda a lie... In reality it only extracts mon images, item icons, trainer images, and
|
|
40
|
+
Of course though the above is for extracting all graphics (which is kinda a lie... In reality it only extracts mon images, item icons, trainer images, move images, and ball images... but like I said this is a WIP :p so wait a bit please!).
|
|
34
41
|
|
|
35
42
|
But if you want say just the mon images or item icons refer below:
|
|
36
43
|
|
|
@@ -81,4 +88,15 @@ await renderAllMoves(rom, {
|
|
|
81
88
|
renderMasterImage: true, // kinda forgot about this... basically it renders a uncut image of the move anim if you like
|
|
82
89
|
sortUnused: true, // sorts unused moves into a sub-directory
|
|
83
90
|
})
|
|
84
|
-
```
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
ball image extraction:
|
|
94
|
+
```JavaScript
|
|
95
|
+
import fs from "fs";
|
|
96
|
+
import { renderAllBalls } from "silphscope" // o-O
|
|
97
|
+
|
|
98
|
+
const rom = fs.readFileSync("./path/to/your/rom.gba") // the file path explains :/
|
|
99
|
+
await renderAllBalls(rom, {
|
|
100
|
+
outputDir: "./Assets/Balls",
|
|
101
|
+
ballParticles: true, // set to false if you don't want the ball particles :p
|
|
102
|
+
})
|
package/ball-data/ballData.json
CHANGED
|
@@ -1,38 +1,156 @@
|
|
|
1
1
|
{
|
|
2
2
|
"POKE": {
|
|
3
|
-
"index": 0
|
|
3
|
+
"index": 0,
|
|
4
|
+
"frameCount": 2,
|
|
5
|
+
"frames": [
|
|
6
|
+
{ "x": 0, "y": 0, "width": 16, "height": 16 },
|
|
7
|
+
{ "x": 0, "y": 16, "width": 16, "height": 16 }
|
|
8
|
+
],
|
|
9
|
+
"particleFrameCount": 3,
|
|
10
|
+
"particleFrames": [
|
|
11
|
+
{ "x": 0, "y": 0, "width": 8, "height": 8 },
|
|
12
|
+
{ "x": 0, "y": 8, "width": 8, "height": 8 },
|
|
13
|
+
{ "x": 0, "y": 16, "width": 8, "height": 8 }
|
|
14
|
+
]
|
|
4
15
|
},
|
|
5
16
|
"GREAT": {
|
|
6
|
-
"index": 1
|
|
17
|
+
"index": 1,
|
|
18
|
+
"frameCount": 2,
|
|
19
|
+
"frames": [
|
|
20
|
+
{ "x": 0, "y": 0, "width": 16, "height": 16 },
|
|
21
|
+
{ "x": 0, "y": 16, "width": 16, "height": 16 }
|
|
22
|
+
],
|
|
23
|
+
"particleFrameCount": 2,
|
|
24
|
+
"particleFrames": [
|
|
25
|
+
{ "x": 0, "y": 8, "width": 8, "height": 8 },
|
|
26
|
+
{ "x": 0, "y": 16, "width": 8, "height": 8 }
|
|
27
|
+
]
|
|
7
28
|
},
|
|
8
29
|
"SAFARI": {
|
|
9
|
-
"index": 2
|
|
30
|
+
"index": 2,
|
|
31
|
+
"frameCount": 2,
|
|
32
|
+
"frames": [
|
|
33
|
+
{ "x": 0, "y": 0, "width": 16, "height": 16 },
|
|
34
|
+
{ "x": 0, "y": 16, "width": 16, "height": 16 }
|
|
35
|
+
],
|
|
36
|
+
"particleFrameCount": 2,
|
|
37
|
+
"particleFrames": [
|
|
38
|
+
{ "x": 0, "y": 0, "width": 8, "height": 8 },
|
|
39
|
+
{ "x": 0, "y": 8, "width": 8, "height": 8 }
|
|
40
|
+
]
|
|
10
41
|
},
|
|
11
42
|
"ULTRA": {
|
|
12
|
-
"index": 3
|
|
43
|
+
"index": 3,
|
|
44
|
+
"frameCount": 2,
|
|
45
|
+
"frames": [
|
|
46
|
+
{ "x": 0, "y": 0, "width": 16, "height": 16 },
|
|
47
|
+
{ "x": 0, "y": 16, "width": 16, "height": 16 }
|
|
48
|
+
],
|
|
49
|
+
"particleFrameCount": 1,
|
|
50
|
+
"particleFrames": [
|
|
51
|
+
{ "x": 0, "y": 56, "width": 8, "height": 8 }
|
|
52
|
+
]
|
|
13
53
|
},
|
|
14
54
|
"MASTER": {
|
|
15
|
-
"index": 4
|
|
55
|
+
"index": 4,
|
|
56
|
+
"frameCount": 2,
|
|
57
|
+
"frames": [
|
|
58
|
+
{ "x": 0, "y": 0, "width": 16, "height": 16 },
|
|
59
|
+
{ "x": 0, "y": 16, "width": 16, "height": 16 }
|
|
60
|
+
],
|
|
61
|
+
"particleFrameCount": 1,
|
|
62
|
+
"particleFrames": [
|
|
63
|
+
{ "x": 0, "y": 24, "width": 8, "height": 8 }
|
|
64
|
+
]
|
|
16
65
|
},
|
|
17
66
|
"NET": {
|
|
18
|
-
"index": 5
|
|
67
|
+
"index": 5,
|
|
68
|
+
"frameCount": 2,
|
|
69
|
+
"frames": [
|
|
70
|
+
{ "x": 0, "y": 0, "width": 16, "height": 16 },
|
|
71
|
+
{ "x": 0, "y": 16, "width": 16, "height": 16 }
|
|
72
|
+
],
|
|
73
|
+
"particleFrameCount": 1,
|
|
74
|
+
"particleFrames": [
|
|
75
|
+
{ "x": 0, "y": 32, "width": 8, "height": 8 }
|
|
76
|
+
]
|
|
19
77
|
},
|
|
20
78
|
"DIVE": {
|
|
21
|
-
"index": 6
|
|
79
|
+
"index": 6,
|
|
80
|
+
"frameCount": 3,
|
|
81
|
+
"frames": [
|
|
82
|
+
{ "x": 0, "y": 0, "width": 16, "height": 16 },
|
|
83
|
+
{ "x": 0, "y": 16, "width": 16, "height": 16 },
|
|
84
|
+
{ "x": 0, "y": 32, "width": 16, "height": 16 }
|
|
85
|
+
],
|
|
86
|
+
"particleFrameCount": 1,
|
|
87
|
+
"particleFrames": [
|
|
88
|
+
{ "x": 0, "y": 32, "width": 8, "height": 8 }
|
|
89
|
+
]
|
|
22
90
|
},
|
|
23
91
|
"NEST": {
|
|
24
|
-
"index": 7
|
|
92
|
+
"index": 7,
|
|
93
|
+
"frameCount": 2,
|
|
94
|
+
"frames": [
|
|
95
|
+
{ "x": 0, "y": 0, "width": 16, "height": 16 },
|
|
96
|
+
{ "x": 0, "y": 16, "width": 16, "height": 16 }
|
|
97
|
+
],
|
|
98
|
+
"particleFrameCount": 1,
|
|
99
|
+
"particleFrames": [
|
|
100
|
+
{ "x": 0, "y": 40, "width": 8, "height": 8 }
|
|
101
|
+
]
|
|
25
102
|
},
|
|
26
103
|
"REPEAT": {
|
|
27
|
-
"index": 8
|
|
104
|
+
"index": 8,
|
|
105
|
+
"frameCount": 2,
|
|
106
|
+
"frames": [
|
|
107
|
+
{ "x": 0, "y": 0, "width": 16, "height": 16 },
|
|
108
|
+
{ "x": 0, "y": 16, "width": 16, "height": 16 }
|
|
109
|
+
],
|
|
110
|
+
"particleFrameCount": 1,
|
|
111
|
+
"particleFrames": [
|
|
112
|
+
{ "x": 0, "y": 56, "width": 8, "height": 8 }
|
|
113
|
+
]
|
|
28
114
|
},
|
|
29
115
|
"TIMER": {
|
|
30
|
-
"index": 9
|
|
116
|
+
"index": 9,
|
|
117
|
+
"frameCount": 3,
|
|
118
|
+
"frames": [
|
|
119
|
+
{ "x": 0, "y": 0, "width": 16, "height": 16 },
|
|
120
|
+
{ "x": 0, "y": 16, "width": 16, "height": 16 },
|
|
121
|
+
{ "x": 0, "y": 32, "width": 16, "height": 16 }
|
|
122
|
+
],
|
|
123
|
+
"particleFrameCount": 1,
|
|
124
|
+
"particleFrames": [
|
|
125
|
+
{ "x": 0, "y": 56, "width": 8, "height": 8 }
|
|
126
|
+
]
|
|
31
127
|
},
|
|
32
128
|
"LUXURY": {
|
|
33
|
-
"index": 10
|
|
129
|
+
"index": 10,
|
|
130
|
+
"frameCount": 3,
|
|
131
|
+
"frames": [
|
|
132
|
+
{ "x": 0, "y": 0, "width": 16, "height": 16 },
|
|
133
|
+
{ "x": 0, "y": 16, "width": 16, "height": 16 },
|
|
134
|
+
{ "x": 0, "y": 32, "width": 16, "height": 16 }
|
|
135
|
+
],
|
|
136
|
+
"particleFrameCount": 2,
|
|
137
|
+
"particleFrames": [
|
|
138
|
+
{ "x": 0, "y": 48, "width": 8, "height": 8 },
|
|
139
|
+
{ "x": 0, "y": 56, "width": 8, "height": 8 }
|
|
140
|
+
]
|
|
34
141
|
},
|
|
35
142
|
"PREMIER": {
|
|
36
|
-
"index": 11
|
|
143
|
+
"index": 11,
|
|
144
|
+
"frameCount": 3,
|
|
145
|
+
"frames": [
|
|
146
|
+
{ "x": 0, "y": 0, "width": 16, "height": 16 },
|
|
147
|
+
{ "x": 0, "y": 16, "width": 16, "height": 16 },
|
|
148
|
+
{ "x": 0, "y": 32, "width": 16, "height": 16 }
|
|
149
|
+
],
|
|
150
|
+
"particleFrameCount": 2,
|
|
151
|
+
"particleFrames": [
|
|
152
|
+
{ "x": 0, "y": 48, "width": 8, "height": 8 },
|
|
153
|
+
{ "x": 0, "y": 56, "width": 8, "height": 8 }
|
|
154
|
+
]
|
|
37
155
|
}
|
|
38
156
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "silphscope",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"description": "A firered/leafgreen ROM asset extractor for use in web applications",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"exports": {
|
|
@@ -28,7 +28,23 @@
|
|
|
28
28
|
"!graphics-maps/",
|
|
29
29
|
"ball-data/"
|
|
30
30
|
],
|
|
31
|
-
"keywords": [
|
|
31
|
+
"keywords": [
|
|
32
|
+
"gba",
|
|
33
|
+
"gameboy-advance",
|
|
34
|
+
"pokemon",
|
|
35
|
+
"firered",
|
|
36
|
+
"pokemon-firered",
|
|
37
|
+
"leafgreen",
|
|
38
|
+
"pokemon-leafgreen",
|
|
39
|
+
"rom",
|
|
40
|
+
"asset",
|
|
41
|
+
"asset-extractor",
|
|
42
|
+
"sprite",
|
|
43
|
+
"sprite-extractor",
|
|
44
|
+
"extractor",
|
|
45
|
+
"gen3",
|
|
46
|
+
"generation-3"
|
|
47
|
+
],
|
|
32
48
|
"author": "chickenPoo",
|
|
33
49
|
"license": "MIT",
|
|
34
50
|
"type": "module",
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
// I have an idea but it needs math D:
|
|
5
5
|
|
|
6
|
-
export const
|
|
6
|
+
export const fireredRev0 = {
|
|
7
7
|
code: "BPRE", // I find this weird... like what does BP stand for? is it: "Battle Pokemon"? no idea :o
|
|
8
|
+
rev: 0, // I probably should have mentioned I have been making this tool with a rev0 ROM... but oh well it isn't like people are actually using this tool at least right now so no one has probably run into the "why isn't m firered ROM working!" issue :p
|
|
8
9
|
tables: { // so the idea is essentially instead of a big JSON like we have now we instead just view the table graphics and build from that... it in theory could be slower because more lookups instead of direct references but it should be more maintainable...
|
|
9
10
|
monFrontSprites: 0x2350AC,
|
|
10
11
|
monBackSprites: 0x23654C,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Copyright (c) 2026 chickenPoo
|
|
2
|
+
// Licensed under the MIT License. See LICENSE file in project root.
|
|
3
|
+
|
|
4
|
+
// time to see if my goofy ideas payed off lol
|
|
5
|
+
|
|
6
|
+
export const fireredRev1 = {
|
|
7
|
+
code: "BPRE",
|
|
8
|
+
rev: 1,
|
|
9
|
+
tables: {
|
|
10
|
+
// will add these later :p
|
|
11
|
+
}
|
|
12
|
+
}
|
package/src/get-rom-config.js
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
// Copyright (c) 2026 chickenPoo
|
|
2
2
|
// Licensed under the MIT License. See LICENSE file in project root.
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { fireredRev0 } from "../rom-configs/fireredRev0.js";
|
|
5
|
+
import { fireredRev1 } from "../rom-configs/fireredRev1.js";
|
|
6
|
+
import { leafgreenRev0 } from "../rom-configs/leafgreenRev0.js";
|
|
7
|
+
import { leafgreenRev1 } from "../rom-configs/leafgreenRev1.js";
|
|
5
8
|
|
|
6
|
-
function
|
|
7
|
-
return
|
|
9
|
+
function detectRomInfo(rom) {
|
|
10
|
+
return {
|
|
11
|
+
code: new TextDecoder().decode(rom.slice(0xAC, 0xB0)),
|
|
12
|
+
rev: rom[0xBC],
|
|
13
|
+
}
|
|
8
14
|
}
|
|
9
15
|
|
|
10
16
|
export function getRomConfig(rom) {
|
|
11
|
-
const
|
|
12
|
-
if (code ===
|
|
13
|
-
|
|
17
|
+
const romInfo = detectRomInfo(rom);
|
|
18
|
+
if (romInfo.code === fireredRev0.code || romInfo.code === fireredRev1.code) {
|
|
19
|
+
if (romInfo.rev === fireredRev0.rev) return fireredRev0;
|
|
20
|
+
if (romInfo.rev === fireredRev1.rev) return fireredRev1;
|
|
21
|
+
}
|
|
22
|
+
if (romInfo.code === leafgreenRev0.code || romInfo.code === leafgreenRev1.code) { // yes I know checking both of the codes is redundant since they are the same but let me enjoy my useless non-DRY code ;)
|
|
23
|
+
if (romInfo.rev === leafgreenRev0.rev) return leafgreenRev0;
|
|
24
|
+
if (romInfo.rev === leafgreenRev1.rev) return leafgreenRev1;
|
|
25
|
+
}
|
|
26
|
+
throw new Error(`Unsupported ROM: ${romInfo.code} rev ${romInfo.rev}`); // should be evrything I think...
|
|
14
27
|
}
|
|
@@ -15,8 +15,29 @@ const streamToBuffer = (stream) => new Promise((resolve, reject) => {
|
|
|
15
15
|
stream.on("error", reject);
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
+
const extractFrameFromImage = (imageData, fullWidth, frameData) => {
|
|
19
|
+
const { x, y, width, height } = frameData;
|
|
20
|
+
const frameImage = new Uint8ClampedArray(width * height * 4);
|
|
21
|
+
|
|
22
|
+
for (let row = 0; row < height; row++) {
|
|
23
|
+
for (let col = 0; col < width; col++) {
|
|
24
|
+
const srcIndex = ((y + row) * fullWidth + (x + col)) * 4;
|
|
25
|
+
const dstIndex = (row * width + col) * 4;
|
|
26
|
+
frameImage[dstIndex] = imageData[srcIndex];
|
|
27
|
+
frameImage[dstIndex + 1] = imageData[srcIndex + 1];
|
|
28
|
+
frameImage[dstIndex + 2] = imageData[srcIndex + 2];
|
|
29
|
+
frameImage[dstIndex + 3] = imageData[srcIndex + 3];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return frameImage;
|
|
34
|
+
};
|
|
35
|
+
|
|
18
36
|
export async function renderBallParticle(ballName, balls, reader, rom, options = {}) {
|
|
19
|
-
const {
|
|
37
|
+
const {
|
|
38
|
+
outputDir = null,
|
|
39
|
+
renderMasterBallParticleImage = false,
|
|
40
|
+
} = options;
|
|
20
41
|
if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
|
|
21
42
|
throw new TypeError("renderBallParticle(..., rom) requires a ROM Buffer/Uint8Array");
|
|
22
43
|
}
|
|
@@ -51,8 +72,20 @@ export async function renderBallParticle(ballName, balls, reader, rom, options =
|
|
|
51
72
|
if (outputDir) {
|
|
52
73
|
const dir = `${outputDir}/${ballName}`;
|
|
53
74
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
54
|
-
|
|
55
|
-
|
|
75
|
+
if (renderMasterBallParticleImage) {
|
|
76
|
+
fs.writeFileSync(`${dir}/master-particle.png`, pngBuffer);
|
|
77
|
+
}
|
|
78
|
+
for (let i = 0; i < ball.particleFrames.length; i++) {
|
|
79
|
+
const frame = ball.particleFrames[i];
|
|
80
|
+
const frameImageData = extractFrameFromImage(image, width, frame);
|
|
81
|
+
|
|
82
|
+
const png = new PNG({ width: frame.width, height: frame.height });
|
|
83
|
+
png.data = frameImageData;
|
|
84
|
+
const pngFrameBuffer = await streamToBuffer(png.pack());
|
|
85
|
+
|
|
86
|
+
const fileName = `${dir}/particle-${i}.png`;
|
|
87
|
+
fs.writeFileSync(fileName, pngFrameBuffer);
|
|
88
|
+
}
|
|
56
89
|
}
|
|
57
90
|
|
|
58
91
|
return pngBuffer;
|
|
@@ -16,10 +16,30 @@ const streamToBuffer = (stream) => new Promise((resolve, reject) => {
|
|
|
16
16
|
stream.on("error", reject);
|
|
17
17
|
})
|
|
18
18
|
|
|
19
|
+
const extractFrameFromImage = (imageData, fullWidth, frameData) => {
|
|
20
|
+
const { x, y, width, height } = frameData;
|
|
21
|
+
const frameImage = new Uint8ClampedArray(width * height * 4);
|
|
22
|
+
|
|
23
|
+
for (let row = 0; row < height; row++) {
|
|
24
|
+
for (let col = 0; col < width; col++) {
|
|
25
|
+
const srcIndex = ((y + row) * fullWidth + (x + col)) * 4;
|
|
26
|
+
const dstIndex = (row * width + col) * 4;
|
|
27
|
+
frameImage[dstIndex] = imageData[srcIndex];
|
|
28
|
+
frameImage[dstIndex + 1] = imageData[srcIndex + 1];
|
|
29
|
+
frameImage[dstIndex + 2] = imageData[srcIndex + 2];
|
|
30
|
+
frameImage[dstIndex + 3] = imageData[srcIndex + 3];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return frameImage;
|
|
35
|
+
};
|
|
36
|
+
|
|
19
37
|
export async function renderBall(ballName, balls, reader, rom, options = {}) {
|
|
20
38
|
const {
|
|
21
39
|
outputDir = null,
|
|
22
40
|
ballParticles = false,
|
|
41
|
+
renderMasterBallImage = false,
|
|
42
|
+
renderMasterBallParticleImage = false,
|
|
23
43
|
} = options;
|
|
24
44
|
if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
|
|
25
45
|
throw new TypeError("renderBall(..., rom) requires a ROM Buffer/Uint8Array");
|
|
@@ -30,11 +50,14 @@ export async function renderBall(ballName, balls, reader, rom, options = {}) {
|
|
|
30
50
|
throw new Error(`Missing Ball: ${ballName}`);
|
|
31
51
|
}
|
|
32
52
|
if (ballParticles) {
|
|
33
|
-
renderBallParticle(ballName, balls, reader, rom, {
|
|
53
|
+
renderBallParticle(ballName, balls, reader, rom, {
|
|
54
|
+
outputDir,
|
|
55
|
+
renderMasterBallParticleImage,
|
|
56
|
+
});
|
|
34
57
|
}
|
|
35
58
|
const ballPal = resolveBallSpritePal(ball, reader, ballName);
|
|
36
59
|
const ballPic = resolveBallSpritePic(ball, reader, ballName);
|
|
37
|
-
if(!ballPal || !ballPic) {
|
|
60
|
+
if (!ballPal || !ballPic) {
|
|
38
61
|
throw new Error(`Missing assets for: ${ballName}`);
|
|
39
62
|
}
|
|
40
63
|
|
|
@@ -57,8 +80,20 @@ export async function renderBall(ballName, balls, reader, rom, options = {}) {
|
|
|
57
80
|
if (outputDir) {
|
|
58
81
|
const dir = `${outputDir}/${ballName}`;
|
|
59
82
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
60
|
-
|
|
61
|
-
|
|
83
|
+
if (renderMasterBallImage) {
|
|
84
|
+
fs.writeFileSync(`${dir}/master-image.png`, pngBuffer)
|
|
85
|
+
}
|
|
86
|
+
for (let i = 0; i < ball.frames.length; i++) {
|
|
87
|
+
const frame = ball.frames[i];
|
|
88
|
+
const frameImageData = extractFrameFromImage(image, width, frame);
|
|
89
|
+
|
|
90
|
+
const png = new PNG({ width: frame.width, height: frame.height });
|
|
91
|
+
png.data = frameImageData;
|
|
92
|
+
const pngFrameBuffer = await streamToBuffer(png.pack());
|
|
93
|
+
|
|
94
|
+
const fileName = `${dir}/frame-${i}.png`;
|
|
95
|
+
fs.writeFileSync(fileName, pngFrameBuffer);
|
|
96
|
+
}
|
|
62
97
|
}
|
|
63
98
|
|
|
64
99
|
return pngBuffer;
|
|
@@ -130,6 +130,8 @@ export async function renderAllBalls(rom, options= {}) {
|
|
|
130
130
|
balls: providedBalls = balls,
|
|
131
131
|
outputDir = "./out",
|
|
132
132
|
ballParticles = true,
|
|
133
|
+
renderMasterBallImage = true,
|
|
134
|
+
renderMasterBallParticleImage = true,
|
|
133
135
|
} = options;
|
|
134
136
|
|
|
135
137
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
@@ -138,7 +140,12 @@ export async function renderAllBalls(rom, options= {}) {
|
|
|
138
140
|
const reader = new RomReader(rom, config);
|
|
139
141
|
|
|
140
142
|
for (const ballName of Object.keys(providedBalls)) {
|
|
141
|
-
await renderBall(ballName, providedBalls, reader, rom, {
|
|
143
|
+
await renderBall(ballName, providedBalls, reader, rom, {
|
|
144
|
+
outputDir,
|
|
145
|
+
ballParticles,
|
|
146
|
+
renderMasterBallImage,
|
|
147
|
+
renderMasterBallParticleImage,
|
|
148
|
+
});
|
|
142
149
|
console.log(`Done: ${ballName}`);
|
|
143
150
|
}
|
|
144
151
|
}
|
|
@@ -181,6 +188,8 @@ export async function renderAllGraphics(rom, options = {}) { // eventually I wil
|
|
|
181
188
|
await renderAllBalls(rom, {
|
|
182
189
|
outputDir: outputBallDir,
|
|
183
190
|
ballParticles: true,
|
|
191
|
+
renderMasterBallImage: true,
|
|
192
|
+
renderMasterBallParticleImage: true,
|
|
184
193
|
});
|
|
185
194
|
}
|
|
186
195
|
|
|
@@ -68,6 +68,10 @@ export async function renderMove(moveName, moves, reader, rom, options = {}) {
|
|
|
68
68
|
height,
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
const png = new PNG({ width, height });
|
|
72
|
+
png.data = image;
|
|
73
|
+
const pngBuffer = await streamToBuffer(png.pack());
|
|
74
|
+
|
|
71
75
|
if (outputDir) { // I will update this later but in theory it should also work... eventually though it will need a split inside to handle full image generation :p
|
|
72
76
|
const rootDir = (sortUnused && move?.unused === true)? `${outputDir}/unused` : `${outputDir}`
|
|
73
77
|
const dir = `${rootDir}/${moveName}`;
|
|
@@ -90,4 +94,6 @@ export async function renderMove(moveName, moves, reader, rom, options = {}) {
|
|
|
90
94
|
fs.writeFileSync(fileName, pngBuffer);
|
|
91
95
|
}
|
|
92
96
|
}
|
|
97
|
+
|
|
98
|
+
return pngBuffer;
|
|
93
99
|
}
|