silphscope 1.0.1 → 1.1.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.
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  still a WIP however hopefully with some more work I can get this into a workable state...
2
2
 
3
+ (Newer!) Update:
4
+
5
+ still a WIP :p but erm you can extract more graphics!
6
+
3
7
  Update:
4
8
 
5
9
  so the project is semi-usable now you can download it as a npm package via:
@@ -10,12 +14,39 @@ so the project is semi-usable now you can download it as a npm package via:
10
14
  general use is something like this:
11
15
  ```JavaScript
12
16
  import fs from "fs";
13
- import { renderAllMons } from "silphscope"; // this is cool...
17
+ import { renderAllGraphics } from "silphscope"; // this is cool...
14
18
 
15
19
  const rom = fs.readFileSync("pokefirered.gba"); // replace with path to your own firered rom
20
+ await renderAllGraphics(rom, {
21
+ outputMonDir: "./Assets/monImages", // must I explain?
22
+ outputIconDir: "./Assets/Icons", // same thing here :p
23
+ });
24
+ ```
25
+
26
+ Of course though the above is for extracting all graphics (which is kinda a lie... In reality it only extracts mon images and item icons... but like I said this is a WIP :p so wait a bit please!).
27
+
28
+ But if you want say just the mon images or item icons refer below:
29
+
30
+ mon images extraction:
31
+ ```JavaScript
32
+ import fs from "fs";
33
+ import { renderAllMons } from "silphscope"; // never gets old :p
34
+
35
+ const rom = fs.readFileSync("pokefirered.gba")// once again replace with the path to your own firered rom
16
36
  await renderAllMons(rom, {
17
- outputDir: "./Assets/monImages", // must I explain?
18
- icon: true, // you should probably leave these two alone
19
- footprint: true // unless you don't want everything generated?
37
+ outputDir: "./Assets/monImages", // do I actually have to explain?
38
+ icon: true, // set to false if you don't want icons I guess...
39
+ footprint: true, // same as the above...
40
+ });
41
+ ```
42
+
43
+ item icon extraction:
44
+ ```JavaScript
45
+ import fs from "fs";
46
+ import { renderAllIcons } from "silphscope" // :D
47
+
48
+ const rom = fs.readFileSync("pokefirered.gba")// find your own rom and so on :l
49
+ await renderAllIcons(rom, {
50
+ outputDir: "./Assets/Icons" // no comment (wait... that was a comment :p)
20
51
  });
21
52
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "silphscope",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "A firered/leafgreen ROM asset extractor for use in web applications",
5
5
  "main": "main.js",
6
6
  "exports": {
@@ -5,6 +5,7 @@ import fs from "fs";
5
5
  import path from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { renderMon } from "./render-mons.js";
8
+ import { renderIcon } from "./icons/render-icons.js";
8
9
 
9
10
  const currentDir = path.dirname(fileURLToPath(import.meta.url));
10
11
 
@@ -15,6 +16,7 @@ function loadDefaultJson(relativePath) {
15
16
 
16
17
  const assets = loadDefaultJson("../graphics-maps/fr-graphic-map.json");
17
18
  const mons = loadDefaultJson("../mon-data/monData.json");
19
+ const icons = loadDefaultJson("../item-data/itemData.json");
18
20
 
19
21
  export async function renderAllMons(rom, options = {}) {
20
22
  if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
@@ -64,6 +66,46 @@ export async function renderAllMons(rom, options = {}) {
64
66
  }
65
67
  }
66
68
 
69
+ export async function renderAllIcons(rom, options = {}) {
70
+ if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
71
+ throw new TypeError("renderAllIcons(rom, options) requires rom Buffer/Uint8Array as first argument");
72
+ }
73
+
74
+ const {
75
+ assets: providedAssets = assets,
76
+ icons: providedIcons = icons,
77
+ outputDir = "./out",
78
+ } = options;
79
+
80
+ fs.mkdirSync(outputDir, { recursive: true });
81
+
82
+ for (const itemName of Object.keys(providedIcons)) {
83
+ await renderIcon(itemName, providedIcons, providedAssets, rom, { outputDir });
84
+ console.log(`Done: ${itemName}`);
85
+ }
86
+ }
87
+
88
+ 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
89
+ if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
90
+ throw new TypeError("renderAllGraphics(rom, options) requires rom Buffer/Uint8Array as first argument");
91
+ }
92
+
93
+ const {
94
+ outputMonDir = "./out/mons",
95
+ outputIconDir = "./out/icons",
96
+ } = options;
97
+
98
+ await renderAllMons(rom, {
99
+ outputDir: outputMonDir,
100
+ icon: true,
101
+ footprint: true,
102
+ });
103
+
104
+ await renderAllIcons(rom, {
105
+ outputDir: outputIconDir,
106
+ });
107
+ }
108
+
67
109
  export function loadDefaultRom() {
68
110
  const romPath = path.resolve(process.cwd(), "../../pokefirered.gba");
69
111
  return fs.readFileSync(romPath);
@@ -0,0 +1,53 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
4
+ import { extract } from "../extract.js";
5
+ import { PNG } from "pngjs";
6
+ import fs from "fs";
7
+ import { render4bppImage } from "../render-4bpp-image.js";
8
+
9
+ const streamToBuffer = (stream) => new Promise((resolve, reject) => {
10
+ const chunks = [];
11
+ stream.on("data", chunk => chunks.push(chunk));
12
+ stream.on("end", () => resolve(Buffer.concat(chunks)));
13
+ stream.on("error", reject);
14
+ });
15
+
16
+ export async function renderIcon(itemName, items, assets, rom, options = {}) {
17
+ const { outputDir = null } = options;
18
+
19
+ if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
20
+ throw new TypeError("renderIcon(..., rom) requires a ROM Buffer/Uint8Array");
21
+ }
22
+
23
+ const item = items[itemName];
24
+ if (!item) {
25
+ throw new Error(`Missing Item: ${itemName}`);
26
+ }
27
+
28
+ const iconPal = assets.find(a => a.name === item.palette);
29
+ const iconPic = assets.find(a => a.name === item.icon);
30
+ if (!iconPal || !iconPic) {
31
+ throw new Error(`Missing assets for: ${itemName}`);
32
+ }
33
+
34
+ const iconImageData = extract(iconPic, rom);
35
+ const rawIconPalData = extract(iconPal, rom);
36
+ const width = 24;
37
+ const height = 24;
38
+
39
+ const image = render4bppImage(iconImageData, rawIconPalData, width, height);
40
+
41
+ const png = new PNG({ width, height });
42
+ png.data = image;
43
+ const pngBuffer = await streamToBuffer(png.pack());
44
+
45
+ if (outputDir) {
46
+ const dir = `${outputDir}/${itemName}`;
47
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
48
+ const fileName = `${dir}/icon.png`;
49
+ fs.writeFileSync(fileName, pngBuffer);
50
+ }
51
+
52
+ return pngBuffer;
53
+ }
@@ -0,0 +1,46 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
4
+ import { decode4bppTile } from "./decode-4bpp.js";
5
+ import { decodePalette } from "./decode-palette.js";
6
+
7
+ export function render4bppImage({ tileData, paletteData, width, height, paletteOffset = 0, paletteSize = null }) {
8
+ const tileSize = 32;
9
+ const numTiles = tileData.length / tileSize;
10
+ const tilesPerRow = width / 8;
11
+ let finalPaletteData = paletteData;
12
+ if (paletteSize !== null) {
13
+ finalPaletteData = paletteData.slice(
14
+ paletteOffset,
15
+ paletteOffset + paletteSize
16
+ );
17
+ }
18
+ const palette = decodePalette(finalPaletteData);
19
+ const tiles = [];
20
+ for (let i = 0; i < numTiles; i++) {
21
+ const start = i * tileSize;
22
+ const tileBytes = tileData.slice(start, start + tileSize);
23
+ tiles.push(decode4bppTile(tileBytes));
24
+ }
25
+ const image = new Uint8ClampedArray(width * height * 4);
26
+ for (let t = 0; t < tiles.length; t++) {
27
+ const tile = tiles[t];
28
+ const tileX = t % tilesPerRow;
29
+ const tileY = Math.floor(t / tilesPerRow);
30
+ for (let py = 0; py < 8; py++) {
31
+ for (let px = 0; px < 8; px++) {
32
+ const pixelIndex = py * 8 + px;
33
+ const colorIndex = tile[pixelIndex];
34
+ const x = tileX * 8 + px;
35
+ const y = tileY * 8 + py;
36
+ const outIndex = (y * width + x) * 4;
37
+ const [r, g, b] = palette[colorIndex] || [0, 0, 0];
38
+ image[outIndex] = r;
39
+ image[outIndex + 1] = g;
40
+ image[outIndex + 2] = b;
41
+ image[outIndex + 3] = colorIndex === 0 ? 0 : 255;
42
+ }
43
+ }
44
+ }
45
+ return image;
46
+ }
@@ -1,11 +1,10 @@
1
1
  // Copyright (c) 2026 chickenPoo
2
2
  // Licensed under the MIT License. See LICENSE file in project root.
3
3
 
4
- import { decode4bppTile } from "./decode-4bpp.js";
5
- import { decodePalette } from "./decode-palette.js";
6
4
  import { extract } from "./extract.js";
7
5
  import { PNG } from "pngjs";
8
6
  import fs from "fs";
7
+ import { render4bppImage } from "./render-4bpp-image.js";
9
8
 
10
9
  const streamToBuffer = (stream) => new Promise((resolve, reject) => {
11
10
  const chunks = [];
@@ -32,45 +31,11 @@ export async function renderMonIcon(monName, mons, assets, iconPalettes, rom, op
32
31
  const iconData = extract(iconAsset, rom);
33
32
  const palIndex = Number(mon.iconPalIndex);
34
33
  const palSize = 32;
35
- const palStart = palIndex * palSize;
36
- const palEnd = palStart + palSize;
37
34
  const rawIconPalData = extract(iconPalettes, rom);
38
- const slicedPalData = rawIconPalData.data.slice(palStart, palEnd);
39
- const palette = decodePalette(slicedPalData);
40
-
41
- const tileSize = 32;
42
- const numTiles = iconData.data.length / tileSize;
43
35
  const width = 32;
44
36
  const height = 64;
45
- const tilesPerRow = width / 8;
46
-
47
- const tiles = [];
48
- for (let i = 0; i < numTiles; i++) {
49
- const start = i * tileSize;
50
- const tileBytes = iconData.data.slice(start, start + tileSize);
51
- tiles.push(decode4bppTile(tileBytes));
52
- }
53
37
 
54
- const image = new Uint8ClampedArray(width * height * 4);
55
- for (let t = 0; t < tiles.length; t++) {
56
- const tile = tiles[t];
57
- const tileX = t % tilesPerRow;
58
- const tileY = Math.floor(t / tilesPerRow);
59
- for (let py = 0; py < 8; py++) {
60
- for (let px = 0; px < 8; px++) {
61
- const pixelIndex = py * 8 + px;
62
- const colorIndex = tile[pixelIndex];
63
- const x = tileX * 8 + px;
64
- const y = tileY * 8 + py;
65
- const outIndex = (y * width + x) * 4;
66
- const [r, g, b] = palette[colorIndex] || [0, 0, 0];
67
- image[outIndex] = r;
68
- image[outIndex + 1] = g;
69
- image[outIndex + 2] = b;
70
- image[outIndex + 3] = colorIndex === 0 ? 0 : 255;
71
- }
72
- }
73
- }
38
+ image = render4bppImage(iconData, rawIconPalData, width, height, palIndex * palSize, palSize);
74
39
 
75
40
  const frameHeight = 32;
76
41
  const frameSize = width * frameHeight * 4;
@@ -3,8 +3,6 @@
3
3
 
4
4
  import { PNG } from "pngjs";
5
5
  import fs from "fs";
6
- import { decode4bppTile } from "./decode-4bpp.js";
7
- import { decodePalette } from "./decode-palette.js";
8
6
  import { extract } from "./extract.js";
9
7
  import { renderMonIcon } from "./render-mon-icon.js";
10
8
  import { renderMonFoot } from "./render-mon-foot.js";
@@ -54,40 +52,10 @@ export async function renderMon(monName, mons, assets, rom, options = {}) {
54
52
 
55
53
  const monImageData = extract(monPic, rom);
56
54
  const rawMonPalData = extract(monPal, rom);
57
- const tileSize = 32;
58
- const numTiles = monImageData.data.length / tileSize;
59
55
  const width = 64;
60
56
  const height = 64;
61
- const tilesPerRow = width / 8;
62
- const monPalData = decodePalette(rawMonPalData.data);
63
57
 
64
- const tiles = [];
65
- for (let i = 0; i < numTiles; i++) {
66
- const start = i * tileSize;
67
- const tileBytes = monImageData.data.slice(start, start + tileSize);
68
- tiles.push(decode4bppTile(tileBytes));
69
- }
70
-
71
- const image = new Uint8ClampedArray(width * height * 4);
72
- for (let t = 0; t < tiles.length; t++) {
73
- const tile = tiles[t];
74
- const tileX = t % tilesPerRow;
75
- const tileY = Math.floor(t / tilesPerRow);
76
- for (let py = 0; py < 8; py++) {
77
- for (let px = 0; px < 8; px++) {
78
- const pixelIndex = py * 8 + px;
79
- const colorIndex = tile[pixelIndex];
80
- const x = tileX * 8 + px;
81
- const y = tileY * 8 + py;
82
- const outIndex = (y * width + x) * 4;
83
- const [r, g, b] = monPalData[colorIndex] || [0, 0, 0];
84
- image[outIndex] = r;
85
- image[outIndex + 1] = g;
86
- image[outIndex + 2] = b;
87
- image[outIndex + 3] = colorIndex === 0 ? 0 : 255;
88
- }
89
- }
90
- }
58
+ const image = render4bppImage(monImageData, rawMonPalData, width, height, );
91
59
 
92
60
  const png = new PNG({ width, height });
93
61
  png.data = image;
@@ -0,0 +1,25 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ const itemIconDataMap = path.resolve("../item_graphics/item_icon_table.h");
5
+ const iconDataLines = fs.readFileSync(itemIconDataMap, "utf-8").split("\n");
6
+
7
+ const itemData = {};
8
+
9
+ for (let i = 0; i < iconDataLines.length; i++) {
10
+ const match = iconDataLines[i].match(/\[(ITEMS?_[A-Z0-9_]+)\]\s*=\s*\{\s*([^,]+),\s*([^}]+)\}/);
11
+ if (match) {
12
+ let itemName = match[1]
13
+ .replace("ITEM_", "")
14
+ .replace("ITEMS_", "");
15
+ const icon = match[2].trim();
16
+ const palette = match[3].trim();
17
+ if (!itemData[itemName]) {
18
+ itemData[itemName] = {};
19
+ }
20
+ itemData[itemName]["icon"] = icon;
21
+ itemData[itemName]["palette"] = palette;
22
+ }
23
+ }
24
+
25
+ fs.writeFileSync("../item-data/itemData.json", JSON.stringify(itemData, null, 2));