silphscope 1.4.2 → 1.4.4
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 +20 -3
- package/rom-configs/{firered.js → fireredRev0.js} +4 -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 +18 -0
- package/src/graphics/mons/render-mon-foot.js +2 -2
- package/src/graphics/mons/render-mon-icon.js +3 -3
- package/src/graphics/mons/render-mons.js +4 -2
- package/src/graphics/moves/render-moves.js +6 -0
- package/src/map-limit.js +25 -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.4",
|
|
4
4
|
"description": "A firered/leafgreen ROM asset extractor for use in web applications",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"exports": {
|
|
@@ -26,9 +26,26 @@
|
|
|
26
26
|
"!src/rom-map-parser.js",
|
|
27
27
|
"!rom-maps/",
|
|
28
28
|
"!graphics-maps/",
|
|
29
|
-
"ball-data/"
|
|
29
|
+
"ball-data/",
|
|
30
|
+
"!src/object-event-data-parser.js"
|
|
31
|
+
],
|
|
32
|
+
"keywords": [
|
|
33
|
+
"gba",
|
|
34
|
+
"gameboy-advance",
|
|
35
|
+
"pokemon",
|
|
36
|
+
"firered",
|
|
37
|
+
"pokemon-firered",
|
|
38
|
+
"leafgreen",
|
|
39
|
+
"pokemon-leafgreen",
|
|
40
|
+
"rom",
|
|
41
|
+
"asset",
|
|
42
|
+
"asset-extractor",
|
|
43
|
+
"sprite",
|
|
44
|
+
"sprite-extractor",
|
|
45
|
+
"extractor",
|
|
46
|
+
"gen3",
|
|
47
|
+
"generation-3"
|
|
30
48
|
],
|
|
31
|
-
"keywords": [],
|
|
32
49
|
"author": "chickenPoo",
|
|
33
50
|
"license": "MIT",
|
|
34
51
|
"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,
|
|
@@ -25,5 +26,7 @@ export const firered = {
|
|
|
25
26
|
ballParticlePalTable: 0x40BFA8,
|
|
26
27
|
ballAnimPicTable: 0x26056C,
|
|
27
28
|
ballAnimPalTable: 0x2605CC, // any idea what I am doing next?
|
|
29
|
+
objectEventPicTable: 0x39FDB0,
|
|
30
|
+
objectEventPalTable: 0x3A5158, // so these tables... technically aren't tables (except for the palette one kinda...) they are actually pointers to objects which then contain children who actually contain another object which finally contains a pointer to the actual graphics... this will be fun... (anyway will work on this eventually...)
|
|
28
31
|
} // if this works out... well hehe... :D
|
|
29
32
|
}
|
|
@@ -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;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
+
import { mapLimit } from "../map-limit.js";
|
|
7
8
|
import { renderMon } from "./mons/render-mons.js";
|
|
8
9
|
import { renderIcon } from "./icons/render-icons.js";
|
|
9
10
|
import { renderTrainer } from "./trainers/render-trainers.js";
|
|
@@ -43,6 +44,16 @@ export async function renderAllMons(rom, options = {}) {
|
|
|
43
44
|
const config = getRomConfig(rom);
|
|
44
45
|
const reader = new RomReader(rom, config);
|
|
45
46
|
|
|
47
|
+
await mapLimit(Object.keys(providedMons), 4, async (monName) => { // so in theory we should be running the function 4 times concurrently now...
|
|
48
|
+
await renderMon(monName, providedMons, reader, rom, {
|
|
49
|
+
variant: ["normal", "shiny"],
|
|
50
|
+
icon,
|
|
51
|
+
footprint,
|
|
52
|
+
outputDir,
|
|
53
|
+
});
|
|
54
|
+
console.log(`Done: ${monName}`);
|
|
55
|
+
});
|
|
56
|
+
/* just going to leave this here for now in case I decide to make it so that you can toggle between concurrent usage and I suppose synchronous would be the correct term for this down here...
|
|
46
57
|
for (const monName of Object.keys(providedMons)) {
|
|
47
58
|
await renderMon(monName, providedMons, reader, rom, { // hopefully this is faster since we are no longer calling the function 4 times lol
|
|
48
59
|
side: ["front", "back"],
|
|
@@ -53,6 +64,7 @@ export async function renderAllMons(rom, options = {}) {
|
|
|
53
64
|
});
|
|
54
65
|
console.log(`Done: ${monName}`);
|
|
55
66
|
}
|
|
67
|
+
*/
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
export async function renderAllIcons(rom, options = {}) {
|
|
@@ -130,6 +142,8 @@ export async function renderAllBalls(rom, options= {}) {
|
|
|
130
142
|
balls: providedBalls = balls,
|
|
131
143
|
outputDir = "./out",
|
|
132
144
|
ballParticles = true,
|
|
145
|
+
renderMasterBallImage = true,
|
|
146
|
+
renderMasterBallParticleImage = true,
|
|
133
147
|
} = options;
|
|
134
148
|
|
|
135
149
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
@@ -141,6 +155,8 @@ export async function renderAllBalls(rom, options= {}) {
|
|
|
141
155
|
await renderBall(ballName, providedBalls, reader, rom, {
|
|
142
156
|
outputDir,
|
|
143
157
|
ballParticles,
|
|
158
|
+
renderMasterBallImage,
|
|
159
|
+
renderMasterBallParticleImage,
|
|
144
160
|
});
|
|
145
161
|
console.log(`Done: ${ballName}`);
|
|
146
162
|
}
|
|
@@ -184,6 +200,8 @@ export async function renderAllGraphics(rom, options = {}) { // eventually I wil
|
|
|
184
200
|
await renderAllBalls(rom, {
|
|
185
201
|
outputDir: outputBallDir,
|
|
186
202
|
ballParticles: true,
|
|
203
|
+
renderMasterBallImage: true,
|
|
204
|
+
renderMasterBallParticleImage: true,
|
|
187
205
|
});
|
|
188
206
|
}
|
|
189
207
|
|
|
@@ -74,9 +74,9 @@ export async function renderMonFoot(monName, mons, reader, rom, options = {}) {
|
|
|
74
74
|
|
|
75
75
|
if (outputDir) {
|
|
76
76
|
const dir = `${outputDir}/${monName}`;
|
|
77
|
-
|
|
77
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
78
78
|
const fileName = `${dir}/footprint.png`;
|
|
79
|
-
fs.
|
|
79
|
+
await fs.promises.writeFile(fileName, pngBuffer);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
return pngBuffer;
|
|
@@ -56,9 +56,9 @@ export async function renderMonIcon(monName, mons, reader, rom, options = {}) {
|
|
|
56
56
|
|
|
57
57
|
if (outputDir) {
|
|
58
58
|
const dir = `${outputDir}/${monName}`;
|
|
59
|
-
|
|
60
|
-
fs.
|
|
61
|
-
fs.
|
|
59
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
60
|
+
await fs.promises.writeFile(`${dir}/icon_frame1.png`, buffer1);
|
|
61
|
+
await fs.promises.writeFile(`${dir}/icon_frame2.png`, buffer2);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
return {
|
|
@@ -61,6 +61,9 @@ export async function renderMon(monName, mons, reader, rom, options = {}) {
|
|
|
61
61
|
const results = [];
|
|
62
62
|
const width = 64;
|
|
63
63
|
const height = 64;
|
|
64
|
+
if (outputDir) {
|
|
65
|
+
await fs.promises.mkdir(dir, { recursive: true }); // why was I using existsSync... eh well "fixed?" now I guess... also I moved this out of the loop as you can see so it only has to run... 440 times now... instead of 4x that number :p
|
|
66
|
+
}
|
|
64
67
|
for (const side of sides) {
|
|
65
68
|
for (const variant of variants) {
|
|
66
69
|
const image = render4bppImage({
|
|
@@ -76,9 +79,8 @@ export async function renderMon(monName, mons, reader, rom, options = {}) {
|
|
|
76
79
|
|
|
77
80
|
if (outputDir) {
|
|
78
81
|
const dir = `${outputDir}/${monName}`;
|
|
79
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); // I suppose it is better to have this out of the loop for performance but erm I would say its fine for now :p
|
|
80
82
|
const fileName = `${dir}/${side}${variant === "shiny" ? "_shiny" : ""}.png`;
|
|
81
|
-
fs.
|
|
83
|
+
await fs.promises.writeFile(fileName, pngBuffer);
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
results.push(pngBuffer);
|
|
@@ -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
|
}
|
package/src/map-limit.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Copyright (c) 2026 chickenPoo
|
|
2
|
+
// Licensed under the MIT License. See LICENSE file in project root.
|
|
3
|
+
|
|
4
|
+
export async function mapLimit(items, limit, mapper) { // I hope this works... in theory it should and it seems quite simple... it's just that I am garbage at async thingies...
|
|
5
|
+
const results = new Array(items.length);
|
|
6
|
+
let index = 0;
|
|
7
|
+
|
|
8
|
+
async function worker() {
|
|
9
|
+
while (true) {
|
|
10
|
+
const current = index++;
|
|
11
|
+
|
|
12
|
+
if (index >= items.length) return;
|
|
13
|
+
|
|
14
|
+
results[current] = await mapper(items[current], current);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
await Promise.all(
|
|
18
|
+
Array.from(
|
|
19
|
+
{ length: Math.min(limit, items.length) },
|
|
20
|
+
worker
|
|
21
|
+
)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return results;
|
|
25
|
+
}
|