silphscope 1.3.6 → 1.4.0

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.
@@ -0,0 +1,38 @@
1
+ {
2
+ "POKE": {
3
+ "index": 0
4
+ },
5
+ "GREAT": {
6
+ "index": 1
7
+ },
8
+ "SAFARI": {
9
+ "index": 2
10
+ },
11
+ "ULTRA": {
12
+ "index": 3
13
+ },
14
+ "MASTER": {
15
+ "index": 4
16
+ },
17
+ "NET": {
18
+ "index": 5
19
+ },
20
+ "DIVE": {
21
+ "index": 6
22
+ },
23
+ "NEST": {
24
+ "index": 7
25
+ },
26
+ "REPEAT": {
27
+ "index": 8
28
+ },
29
+ "TIMER": {
30
+ "index": 9
31
+ },
32
+ "LUXURY": {
33
+ "index": 10
34
+ },
35
+ "PREMIER": {
36
+ "index": 11
37
+ }
38
+ }
package/main.js CHANGED
@@ -5,5 +5,5 @@ import { extract } from "./src/graphics/extract.js"; // random message for a com
5
5
 
6
6
  export { renderMon, renderMonIcon, renderMonFoot, extract };
7
7
 
8
- import { renderAllMons, renderAllIcons, renderAllTrainers, renderAllMoves, renderAllGraphics } from "./src/graphics/graphics-extractor-main.js";
9
- export { renderAllMons, renderAllIcons, renderAllTrainers, renderAllMoves ,renderAllGraphics };
8
+ import { renderAllMons, renderAllIcons, renderAllTrainers, renderAllMoves, renderAllBalls, renderAllGraphics } from "./src/graphics/graphics-extractor-main.js";
9
+ export { renderAllMons, renderAllIcons, renderAllTrainers, renderAllMoves, renderAllBalls, renderAllGraphics };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "silphscope",
3
- "version": "1.3.6",
3
+ "version": "1.4.0",
4
4
  "description": "A firered/leafgreen ROM asset extractor for use in web applications",
5
5
  "main": "main.js",
6
6
  "exports": {
@@ -22,7 +22,11 @@
22
22
  "!src/icon-data-parser.js",
23
23
  "!src/mon-data-parser.js",
24
24
  "!src/trainer-data-parser.js",
25
- "!src/move-data-parser.js"
25
+ "!src/move-data-parser.js",
26
+ "!src/rom-map-parser.js",
27
+ "!rom-maps/",
28
+ "!graphics-maps/",
29
+ "ball-data/"
26
30
  ],
27
31
  "keywords": [],
28
32
  "author": "chickenPoo",
@@ -21,5 +21,9 @@ export const firered = {
21
21
  itemIconTable: 0x3D4294, // newer update: (so turns out this old value was the pointer to the table lol however this new one is the actual table... for real this time!) rest of the old message: so this was very hard to find since it isn't labeled in the .map of ROMs... luckily Ghidra and the ROM decomps exist so that helped a ton... issue is I believe this contains both the palette and gfx in each listing (should probably double check pokefirered to confirm...) so it will be interesting to extract assets I suppose...
22
22
  moveAnimPicTable: 0x3ACC08,
23
23
  moveAnimPaletteTable: 0x3AD510,
24
+ ballParticlePicTable: 0x40BF48,
25
+ ballParticlePalTable: 0x40BFA8,
26
+ ballAnimPicTable: 0x26056C,
27
+ ballAnimPalTable: 0x2605CC, // any idea what I am doing next?
24
28
  } // if this works out... well hehe... :D
25
29
  }
@@ -0,0 +1,59 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
4
+ import fs from "fs";
5
+ import { PNG } from "pngjs";
6
+ import { extract } from "../extract.js";
7
+ import { render4bppImage } from "../render-4bpp-image.js";
8
+ import { resolveBallParticlePic } from "./resolvers/ball-particle-resolver.js";
9
+ import { resolveBallParticlePal } from "./resolvers/ball-particle-palette-resolver.js";
10
+
11
+ const streamToBuffer = (stream) => new Promise((resolve, reject) => {
12
+ const chunks = [];
13
+ stream.on("data", chunk => chunks.push(chunk));
14
+ stream.on("end", () => resolve(Buffer.concat(chunks)));
15
+ stream.on("error", reject);
16
+ })
17
+
18
+ export async function renderBallParticle(ballName, balls, reader, rom, options = {}) {
19
+ const { outputDir = null } = options;
20
+ if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
21
+ throw new TypeError("renderBallParticle(..., rom) requires a ROM Buffer/Uint8Array");
22
+ }
23
+
24
+ const ball = balls[ballName];
25
+ if (!ball) {
26
+ throw new Error(`Missing Ball: ${ballName}`);
27
+ }
28
+ const particlePic = resolveBallParticlePic(ball, reader, ballName);
29
+ const particlePal = resolveBallParticlePal(ball, reader, ballName);
30
+
31
+ if (!particlePic || !particlePal) {
32
+ throw new Error(`Missing assets for: ${ballName}`);
33
+ }
34
+
35
+ const particleImageData = extract(particlePic, rom);
36
+ const rawParticlePalData = extract(particlePal, rom);
37
+ const width = 8; // so actually the "particle image" is one image that just contains all of the particles used for the balls upon opening later on I will split the ones actually used by each ball so we aren't exporting a full redundant image :p
38
+ const height = 64;
39
+
40
+ const image = render4bppImage({
41
+ tileData: particleImageData.data,
42
+ paletteData: rawParticlePalData.data,
43
+ width,
44
+ height,
45
+ });
46
+
47
+ const png = new PNG({ width, height });
48
+ png.data = image;
49
+ const pngBuffer = streamToBuffer(png.pack());
50
+
51
+ if (outputDir) {
52
+ const dir = `${outputDir}/${ballName}`;
53
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
54
+ const fileName = `${dir}/particle.png`;
55
+ fs.writeFileSync(fileName, pngBuffer);
56
+ }
57
+
58
+ return pngBuffer;
59
+ }
@@ -0,0 +1,65 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
4
+ import fs from "fs";
5
+ import { PNG } from "pngjs";
6
+ import { extract } from "../extract.js";
7
+ import { render4bppImage } from "../render-4bpp-image.js";
8
+ import { resolveBallSpritePal } from "./resolvers/ball-sprite-palette-resolver.js";
9
+ import { resolveBallSpritePic } from "./resolvers/ball-sprite-resolver.js";
10
+ import { renderBallParticle } from "./render-ball-particle.js";
11
+
12
+ const streamToBuffer = (stream) => new Promise((resolve, reject) => {
13
+ const chunks = [];
14
+ stream.on("data", chunk => chunks.push(chunk));
15
+ stream.on("end", () => resolve(Buffer.concat(chunks)));
16
+ stream.on("error", reject);
17
+ })
18
+
19
+ export async function renderBall(ballName, balls, reader, rom, options = {}) {
20
+ const {
21
+ outputDir = null,
22
+ ballParticles = false,
23
+ } = options;
24
+ if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
25
+ throw new TypeError("renderBall(..., rom) requires a ROM Buffer/Uint8Array");
26
+ }
27
+
28
+ const ball = balls[ballName];
29
+ if (!ball) {
30
+ throw new Error(`Missing Ball: ${ballName}`);
31
+ }
32
+ if (ballParticles) {
33
+ renderBallParticle(ballName, balls, reader, rom, { outputDir });
34
+ }
35
+ const ballPal = resolveBallSpritePal(ball, reader, ballName);
36
+ const ballPic = resolveBallSpritePic(ball, reader, ballName);
37
+ if(!ballPal || !ballPic) {
38
+ throw new Error(`Missing assets for: ${ballName}`);
39
+ }
40
+
41
+ const ballImageData = extract(ballPic, rom);
42
+ const rawBallPalData = extract(ballPal, rom);
43
+ const width = 16;
44
+ const height = 48;
45
+
46
+ const image = render4bppImage({
47
+ tileData: ballImageData.data,
48
+ paletteData: rawBallPalData.data,
49
+ width,
50
+ height,
51
+ });
52
+
53
+ const png = new PNG({ width, height });
54
+ png.data = image;
55
+ const pngBuffer = streamToBuffer(png.pack());
56
+
57
+ if (outputDir) {
58
+ const dir = `${outputDir}/${ballName}`;
59
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
60
+ const fileName = `${dir}/ball.png`;
61
+ fs.writeFileSync(fileName, pngBuffer);
62
+ }
63
+
64
+ return pngBuffer;
65
+ }
@@ -0,0 +1,13 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
4
+ export function resolveBallParticlePal(ball, reader, ballName) {
5
+ const table = reader.getTable("ballParticlePalTable");
6
+ const entryOffset = table + ball.index * 8;
7
+ const ptr = reader.readPointer(entryOffset);
8
+ return {
9
+ name: `ball_${ballName}_particle_pal`,
10
+ offset: ptr,
11
+ size: 32,
12
+ }
13
+ }
@@ -0,0 +1,12 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
4
+ export function resolveBallParticlePic(ball, reader, ballName) {
5
+ const table = reader.getTable("ballParticlePicTable");
6
+ const entryOffset = table + ball.index * 8;
7
+ const ptr = reader.readPointer(entryOffset);
8
+ return {
9
+ name: `ball_${ballName}_particle_pic`,
10
+ offset: ptr,
11
+ }
12
+ }
@@ -0,0 +1,13 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
4
+ export function resolveBallSpritePal(ball, reader, ballName) {
5
+ const table = reader.getTable("ballAnimPalTable");
6
+ const entryOffset = table + ball.index * 8;
7
+ const ptr = reader.readPointer(entryOffset);
8
+ return {
9
+ name: `ball_${ballName}_pal`,
10
+ offset: ptr,
11
+ size: 32
12
+ }
13
+ }
@@ -0,0 +1,12 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
4
+ export function resolveBallSpritePic(ball, reader, ballName) {
5
+ const table = reader.getTable("ballAnimPicTable");
6
+ const entryOffset = table + ball.index * 8;
7
+ const ptr = reader.readPointer(entryOffset);
8
+ return {
9
+ name: `ball_${ballName}_pic`,
10
+ offset: ptr, // I think this is lz77 compressed so it doesn't need a size value...
11
+ }
12
+ }
@@ -10,6 +10,7 @@ import { renderTrainer } from "./trainers/render-trainers.js";
10
10
  import { RomReader } from "../rom-reader.js";
11
11
  import { getRomConfig } from "../get-rom-config.js";
12
12
  import { renderMove } from "./moves/render-moves.js";
13
+ import { renderBall } from "./balls/render-balls.js";
13
14
 
14
15
  const currentDir = path.dirname(fileURLToPath(import.meta.url));
15
16
 
@@ -23,6 +24,7 @@ const icons = loadDefaultJson("../item-data/itemData.json");
23
24
  const trainers = loadDefaultJson("../trainer-data/trainerData.json");
24
25
  const trainersBack = loadDefaultJson("../trainer-data/trainerBackData.json");
25
26
  const moves = loadDefaultJson("../move-data/moveData.json");
27
+ const balls = loadDefaultJson("../ball-data/ballData.json");
26
28
 
27
29
  export async function renderAllMons(rom, options = {}) {
28
30
  if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
@@ -123,6 +125,24 @@ export async function renderAllMoves(rom, options = {}) {
123
125
  }
124
126
  }
125
127
 
128
+ export async function renderAllBalls(rom, options= {}) {
129
+ const {
130
+ balls: providedBalls = balls,
131
+ outputDir = "./out",
132
+ ballParticles = true,
133
+ } = options;
134
+
135
+ fs.mkdirSync(outputDir, { recursive: true });
136
+
137
+ const config = getRomConfig(rom);
138
+ const reader = new RomReader(rom, config);
139
+
140
+ for (const ballName of Object.keys(providedBalls)) {
141
+ await renderBall(ballName, providedBalls, reader, rom, { outputDir });
142
+ console.log(`Done: ${ballName}`);
143
+ }
144
+ }
145
+
126
146
  export async function renderAllGraphics(rom, options = {}) { // eventually I will speed this up instead of doing it sequentially :p but for now its fine I guess
127
147
  if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
128
148
  throw new TypeError("renderAllGraphics(rom, options) requires rom Buffer/Uint8Array as first argument");
@@ -134,6 +154,7 @@ export async function renderAllGraphics(rom, options = {}) { // eventually I wil
134
154
  outputTrainerDir = "./out/trainers",
135
155
  outputMoveDir = "./out/moves",
136
156
  sortUnusedMoves = true,
157
+ outputBallDir = "./out/balls",
137
158
  } = options;
138
159
 
139
160
  await renderAllMons(rom, {
@@ -155,7 +176,12 @@ export async function renderAllGraphics(rom, options = {}) { // eventually I wil
155
176
  outputDir: outputMoveDir,
156
177
  renderMasterImage: true,
157
178
  sortUnused: sortUnusedMoves,
158
- })
179
+ });
180
+
181
+ await renderAllBalls(rom, {
182
+ outputDir: outputBallDir,
183
+ ballParticles: true,
184
+ });
159
185
  }
160
186
 
161
187
  export function loadDefaultRom() {