silphscope 1.0.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,15 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
4
+ export function decode1bppTile(tileBytes) {
5
+ const pixels = new Uint8Array(64);
6
+ for (let i = 0; i < 8; i++) {
7
+ const byte = tileBytes[i];
8
+ for (let bit = 0; bit < 8; bit++) {
9
+ const pixelIndex = i * 8 + bit;
10
+ const pixel = (byte >> bit) & 1;
11
+ pixels[pixelIndex] = pixel;
12
+ }
13
+ }
14
+ return pixels;
15
+ }
@@ -0,0 +1,15 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
4
+ export function decode4bppTile(tileBytes) {
5
+ const pixels = new Uint8Array(64);
6
+ for (let i = 0; i < 32; i++) {
7
+ const byte = tileBytes[i];
8
+ const p1 = byte & 0xF;
9
+ const p2 = byte >> 4;
10
+ const pixelIndex = i * 2;
11
+ pixels[pixelIndex] = p1;
12
+ pixels[pixelIndex + 1] = p2;
13
+ }
14
+ return pixels;
15
+ }
@@ -0,0 +1,17 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
4
+ export function decodePalette(data) {
5
+ const colors = [];
6
+ for (let i = 0; i < data.length; i += 2) {
7
+ const value = data[i] | (data[i + 1] << 8);
8
+ let r = value & 0x1F;
9
+ let g = (value >> 5) & 0x1F;
10
+ let b = (value >> 10) & 0x1F;
11
+ r = Math.floor((r * 255) / 31);
12
+ g = Math.floor((g * 255) / 31);
13
+ b = Math.floor((b * 255) / 31);
14
+ colors.push([r, g, b]);
15
+ }
16
+ return colors;
17
+ }
@@ -0,0 +1,24 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
4
+ import { lz77Decompress } from "./lz77-decompress.js";
5
+
6
+ export function extract(asset, rom) {
7
+ if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
8
+ throw new TypeError("extract(asset, rom) requires a ROM Buffer/Uint8Array as second argument");
9
+ }
10
+
11
+ const start = asset.offset;
12
+ if (rom[start] === 0x10) {
13
+ try {
14
+ const slice = rom.slice(start);
15
+ const data = lz77Decompress(slice);
16
+ return { ...asset, data, compressed: true };
17
+ } catch (e) {
18
+ console.warn(`lz77 failed for ${asset.name}... treating as raw :p`);
19
+ }
20
+ }
21
+
22
+ const slice = rom.slice(start, start + asset.size);
23
+ return { ...asset, data: slice, compressed: false };
24
+ }
@@ -0,0 +1,70 @@
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 path from "path";
6
+ import { fileURLToPath } from "url";
7
+ import { renderMon } from "./render-mons.js";
8
+
9
+ const currentDir = path.dirname(fileURLToPath(import.meta.url));
10
+
11
+ function loadDefaultJson(relativePath) {
12
+ const absolutePath = path.join(currentDir, "..", relativePath);
13
+ return JSON.parse(fs.readFileSync(absolutePath, "utf-8"));
14
+ }
15
+
16
+ const assets = loadDefaultJson("../graphics-maps/fr-graphic-map.json");
17
+ const mons = loadDefaultJson("../mon-data/monData.json");
18
+
19
+ export async function renderAllMons(rom, options = {}) {
20
+ if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
21
+ throw new TypeError("renderAllMons(rom, options) requires rom Buffer/Uint8Array as first argument");
22
+ }
23
+
24
+ const {
25
+ assets: providedAssets = assets,
26
+ mons: providedMons = mons,
27
+ outputDir = "./out",
28
+ icon = true,
29
+ footprint = true,
30
+ } = options;
31
+
32
+ fs.mkdirSync(outputDir, { recursive: true });
33
+
34
+ for (const monName of Object.keys(providedMons)) {
35
+ await renderMon(monName, providedMons, providedAssets, rom, {
36
+ side: "front",
37
+ variant: "normal",
38
+ icon,
39
+ footprint,
40
+ outputDir,
41
+ });
42
+ await renderMon(monName, providedMons, providedAssets, rom, {
43
+ side: "front",
44
+ variant: "shiny",
45
+ icon: false,
46
+ footprint: false,
47
+ outputDir,
48
+ });
49
+ await renderMon(monName, providedMons, providedAssets, rom, {
50
+ side: "back",
51
+ variant: "normal",
52
+ icon: false,
53
+ footprint: false,
54
+ outputDir,
55
+ });
56
+ await renderMon(monName, providedMons, providedAssets, rom, {
57
+ side: "back",
58
+ variant: "shiny",
59
+ icon: false,
60
+ footprint: false,
61
+ outputDir,
62
+ });
63
+ console.log(`Done: ${monName}`);
64
+ }
65
+ }
66
+
67
+ export function loadDefaultRom() {
68
+ const romPath = path.resolve(process.cwd(), "../../pokefirered.gba");
69
+ return fs.readFileSync(romPath);
70
+ }
@@ -0,0 +1,58 @@
1
+ /*
2
+ * Derivative work of lz77 decompression code (within the gbagfx tool) by YamaArashi(2015)
3
+ * Licensed under MIT. see NOTICE file in project root for full license.
4
+ *
5
+ * JS port and modifications by chickenPoo, 2026
6
+ */
7
+
8
+ export function lz77Decompress(src) { // in theory this should work like flawlessly unless I messed up my port lol
9
+ if (!src || src.length < 4) {
10
+ throw new Error("invalid lz77 data");
11
+ }
12
+ if (src[0] !== 0x10) {
13
+ throw new Error("not lz77 compressed data");
14
+ }
15
+
16
+ const destSize = src[1] | (src[2] << 8) | (src[3] << 16);
17
+ const dest = new Uint8Array(destSize);
18
+ let srcPos = 4;
19
+ let destPos = 0;
20
+
21
+ while (destPos < destSize) {
22
+ if (srcPos >= src.length) {
23
+ throw new Error("unexpected end of data");
24
+ }
25
+
26
+ let flags = src[srcPos++];
27
+
28
+ for (let i = 0; i < 8; i++) {
29
+ if (flags & 0x80) {
30
+ if (srcPos + 1 >= src.length) {
31
+ throw new Error("unexpected end of data");
32
+ }
33
+
34
+ const byte1 = src[srcPos++];
35
+ const byte2 = src[srcPos++];
36
+ const blockSize = (byte1 >> 4) + 3;
37
+ const blockDistance = (((byte1 & 0xF) << 8) | byte2) + 1;
38
+ let copySrc = destPos - blockDistance;
39
+
40
+ if (copySrc < 0) {
41
+ throw new Error("invalid back reference");
42
+ }
43
+ for (let j = 0; j < blockSize; j++) {
44
+ if (destPos >= destSize) break;
45
+ dest[destPos++] = dest[copySrc + j];
46
+ }
47
+ } else {
48
+ if (srcPos >= src.length) {
49
+ throw new Error("unexpected end of data");
50
+ }
51
+ dest[destPos++] = src[srcPos++];
52
+ }
53
+ if (destPos >= destSize) break;
54
+ flags <<= 1;
55
+ }
56
+ }
57
+ return dest;
58
+ }
@@ -0,0 +1,82 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
4
+ import { PNG } from "pngjs";
5
+ import { extract } from "./extract.js";
6
+ import { decode1bppTile } from "./decode-1bpp.js";
7
+ import fs from "fs";
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 renderMonFoot(monName, mons, assets, rom, options = {}) {
17
+ const { outputDir = null } = options;
18
+
19
+ if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
20
+ throw new TypeError("renderMonFoot(..., rom) requires a ROM Buffer/Uint8Array");
21
+ }
22
+
23
+ const mon = mons[monName];
24
+ if (!mon) {
25
+ throw new Error(`Missing mon entry for ${monName}`);
26
+ }
27
+
28
+ if (!mon.footprintPics && monName.includes("UNOWN")) return;
29
+
30
+ const footAsset = assets.find(a => a.name === mon.footprintPics);
31
+ if (!footAsset) {
32
+ throw new Error(`Missing foot asset for ${monName}`);
33
+ }
34
+
35
+ const footData = extract(footAsset, rom);
36
+ const tiles = [];
37
+ const tileSize = 8;
38
+ const numTiles = footData.data.length / tileSize;
39
+ const width = 16;
40
+ const height = 16;
41
+ const tilesPerRow = width / 8;
42
+
43
+ for (let i = 0; i < numTiles; i++) {
44
+ const start = i * tileSize;
45
+ const tileBytes = footData.data.slice(start, start + tileSize);
46
+ tiles.push(decode1bppTile(tileBytes));
47
+ }
48
+
49
+ const image = new Uint8ClampedArray(width * height * 4);
50
+ for (let t = 0; t < tiles.length; t++) {
51
+ const tile = tiles[t];
52
+ const tileX = t % tilesPerRow;
53
+ const tileY = Math.floor(t / tilesPerRow);
54
+ for (let py = 0; py < 8; py++) {
55
+ for (let px = 0; px < 8; px++) {
56
+ const pixelIndex = py * 8 + px;
57
+ const value = tile[pixelIndex];
58
+ const x = tileX * 8 + px;
59
+ const y = tileY * 8 + py;
60
+ const outIndex = (y * width + x) * 4;
61
+ const color = value ? 0 : 255;
62
+ image[outIndex] = color;
63
+ image[outIndex + 1] = color;
64
+ image[outIndex + 2] = color;
65
+ image[outIndex + 3] = value ? 255 : 0;
66
+ }
67
+ }
68
+ }
69
+
70
+ const png = new PNG({ width, height });
71
+ png.data = image;
72
+ const pngBuffer = await streamToBuffer(png.pack());
73
+
74
+ if (outputDir) {
75
+ const dir = `${outputDir}/${monName}`;
76
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
77
+ const fileName = `${dir}/footprint.png`;
78
+ fs.writeFileSync(fileName, pngBuffer);
79
+ }
80
+
81
+ return pngBuffer;
82
+ }
@@ -0,0 +1,87 @@
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
+ import { extract } from "./extract.js";
7
+ import { PNG } from "pngjs";
8
+ import fs from "fs";
9
+
10
+ const streamToBuffer = (stream) => new Promise((resolve, reject) => {
11
+ const chunks = [];
12
+ stream.on("data", chunk => chunks.push(chunk));
13
+ stream.on("end", () => resolve(Buffer.concat(chunks)));
14
+ stream.on("error", reject);
15
+ });
16
+
17
+ export async function renderMonIcon(monName, mons, assets, iconPalettes, rom, options = {}) {
18
+ const { outputDir = null } = options;
19
+
20
+ if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
21
+ throw new TypeError("renderMonIcon(..., rom) requires a ROM Buffer/Uint8Array");
22
+ }
23
+
24
+ const mon = mons[monName];
25
+ if (!mon) {
26
+ throw new Error(`Missing mon entry for ${monName}`);
27
+ }
28
+
29
+ const iconAsset = assets.find(a => a.name === mon.Icon);
30
+ if (!iconAsset) throw new Error(`Missing icon asset for ${monName}`);
31
+
32
+ const iconData = extract(iconAsset, rom);
33
+ const palIndex = Number(mon.iconPalIndex);
34
+ const palSize = 32;
35
+ const palStart = palIndex * palSize;
36
+ const palEnd = palStart + palSize;
37
+ 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
+ const width = 32;
44
+ 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
+
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
+ }
74
+
75
+ const png = new PNG({ width, height });
76
+ png.data = image;
77
+ const pngBuffer = await streamToBuffer(png.pack());
78
+
79
+ if (outputDir) {
80
+ const dir = `${outputDir}/${monName}`;
81
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
82
+ const fileName = `${dir}/icon.png`;
83
+ fs.writeFileSync(fileName, pngBuffer);
84
+ }
85
+
86
+ return pngBuffer;
87
+ }
@@ -0,0 +1,104 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
4
+ import { PNG } from "pngjs";
5
+ import fs from "fs";
6
+ import { decode4bppTile } from "./decode-4bpp.js";
7
+ import { decodePalette } from "./decode-palette.js";
8
+ import { extract } from "./extract.js";
9
+ import { renderMonIcon } from "./render-mon-icon.js";
10
+ import { renderMonFoot } from "./render-mon-foot.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 renderMon(monName, mons, assets, rom, options = {}) {
20
+ const {
21
+ side = "front",
22
+ variant = "normal",
23
+ icon = false,
24
+ footprint = false,
25
+ outputDir = null,
26
+ } = options;
27
+
28
+ if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
29
+ throw new TypeError("renderMon(..., rom) requires a ROM Buffer/Uint8Array");
30
+ }
31
+
32
+ if (icon === true) {
33
+ const comboPal = assets.find(a => a.name === "gMonIconPalettes");
34
+ if (!comboPal) throw new Error("Missing gMonIconPalettes asset");
35
+ await renderMonIcon(monName, mons, assets, comboPal, rom, { outputDir });
36
+ }
37
+ if (footprint === true) {
38
+ await renderMonFoot(monName, mons, assets, rom, { outputDir });
39
+ }
40
+
41
+ const mon = mons[monName];
42
+ if (!mon) {
43
+ throw new Error(`Missing mon: ${monName}`);
44
+ }
45
+
46
+ const picName = side === "back" ? mon.backPics : mon.frontPics;
47
+ const monPic = assets.find(a => a.name === picName);
48
+ const palType = variant === "shiny" ? mon.shinyPalette : mon.normPalette;
49
+ const monPal = assets.find(a => a.name === palType);
50
+
51
+ if (!monPic || !monPal) {
52
+ throw new Error(`Missing assets for: ${monName}`);
53
+ }
54
+
55
+ const monImageData = extract(monPic, rom);
56
+ const rawMonPalData = extract(monPal, rom);
57
+ const tileSize = 32;
58
+ const numTiles = monImageData.data.length / tileSize;
59
+ const width = 64;
60
+ const height = 64;
61
+ const tilesPerRow = width / 8;
62
+ const monPalData = decodePalette(rawMonPalData.data);
63
+
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
+ }
91
+
92
+ const png = new PNG({ width, height });
93
+ png.data = image;
94
+ const pngBuffer = await streamToBuffer(png.pack());
95
+
96
+ if (outputDir) {
97
+ const dir = `${outputDir}/${monName}`;
98
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
99
+ const fileName = `${dir}/${side}${variant === "shiny" ? "_shiny" : ""}.png`;
100
+ fs.writeFileSync(fileName, pngBuffer);
101
+ }
102
+
103
+ return pngBuffer;
104
+ }
@@ -0,0 +1,113 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ const monFrontPicMap = path.resolve("../pokemon_graphics/front_pic_table.h");
5
+ const monBackPicMap = path.resolve("../pokemon_graphics/back_pic_table.h");
6
+ const monFootprintMap = path.resolve("../pokemon_graphics/footprint_table.h");
7
+ const monPaletteMap = path.resolve("../pokemon_graphics/palette_table.h");
8
+ const monShinyPaletteMap = path.resolve("../pokemon_graphics/shiny_palette_table.h");
9
+ const monImageDimensionMap = path.resolve("../pokemon_graphics/front_pic_coordinates.h");
10
+ const monIconPaletteIdxMap = path.resolve("../pokemon_icon.c");
11
+ const frontPicLines = fs.readFileSync(monFrontPicMap, "utf-8").split("\n");
12
+ const backPicLines = fs.readFileSync(monBackPicMap, "utf-8").split("\n");
13
+ const footprintLines = fs.readFileSync(monFootprintMap, "utf8").split("\n");
14
+ const paletteLines = fs.readFileSync(monPaletteMap, "utf-8").split("\n");
15
+ const shinyPaletteLines = fs.readFileSync(monShinyPaletteMap, "utf-8").split("\n");
16
+ const imageDimLines = fs.readFileSync(monImageDimensionMap, "utf-8").split("\n");
17
+ const iconPalIdxLines = fs.readFileSync(monIconPaletteIdxMap, "utf-8").split("\n");
18
+ const monData = {};
19
+
20
+ function extractMonData(lines, category) {
21
+ for (let i = 0; i < lines.length; i++) {
22
+ let match;
23
+ let monName;
24
+ let secondArgument;
25
+ if (category !== "footprintPics" && category !== "imageDimensions" && category !== "iconPalIndex") {
26
+ match = lines[i].match(/\(([^,]+),\s*([^,]+)\)/);
27
+ if (match) {
28
+ monName = match[1];
29
+ secondArgument = match[2];
30
+ }
31
+ } else if (category === "footprintPics") {
32
+ match = lines[i].match(/\[(SPECIES_\w+)\]\s*=\s*(gMonFootprint_\w+)/);
33
+ if (match) {
34
+ monName = match[1].replace("SPECIES_", "");
35
+ secondArgument = match[2];
36
+ }
37
+ } else if (category === "imageDimensions" && lines[i].includes(".size = MON_COORDS_SIZE")) {
38
+ const prevLine = lines[i - 2];
39
+ match = lines[i].match(/MON_COORDS_SIZE\((\d+),\s*(\d+)\)/);
40
+ if (match) {
41
+ monName = prevLine.match(/\[(SPECIES_\w+)\]/)[1].replace("SPECIES_", "");
42
+ const width = match[1];
43
+ const height = match[2];
44
+ if (!monData[monName]) {
45
+ monData[monName] = {};
46
+ }
47
+ monData[monName]["width"] = width;
48
+ monData[monName]["height"] = height;
49
+ }
50
+ } else if (category === "iconPalIndex") {
51
+ match = lines[i].match(/\[(SPECIES_\w+)\]\s*=\s*(\d+)\s*,?/);
52
+ if (match) {
53
+ monName = match[1].replace("SPECIES_", "");
54
+ secondArgument = match[2];
55
+ }
56
+ }
57
+ if (monName && secondArgument) {
58
+ if (!monData[monName]) {
59
+ monData[monName] = {};
60
+ }
61
+ if (!monData[monName][category]) {
62
+ monData[monName][category] = secondArgument;
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ extractMonData(frontPicLines, "frontPics");
69
+ extractMonData(backPicLines, "backPics");
70
+ extractMonData(paletteLines, "normPalette");
71
+ extractMonData(shinyPaletteLines, "shinyPalette");
72
+ extractMonData(footprintLines, "footprintPics");
73
+ extractMonData(imageDimLines, "imageDimensions");
74
+ extractMonData(iconPalIdxLines, "iconPalIndex");
75
+ function uncapitalize(string) {
76
+ return string.charAt(0) + string.slice(1).toLowerCase();
77
+ }
78
+ Object.entries(monData).forEach(([monName]) => {
79
+ let monName2 = uncapitalize(monName);
80
+ if (monName2.includes("Old_unown_") || monName2.includes("None")) {
81
+ monName2 = "QuestionMark";
82
+ }
83
+ if (monName2.includes("Unown_")) {
84
+ monName2 = monName2.slice(0, 5) + monName2[6].toUpperCase() + monName2.slice(7);
85
+ if (monName2 === "UnownEmark") {
86
+ monName2 = "UnownExclamationMark";
87
+ } else if (monName2 === "UnownQmark") {
88
+ monName2 = "UnownQuestionMark";
89
+ }
90
+ }
91
+ if (monName2.includes("Nidoran")) {
92
+ if (monName2.includes("Nidoran_f")) {
93
+ monName2 = "NidoranF";
94
+ }
95
+ if (monName2.includes("Nidoran_m")) {
96
+ monName2 = "NidoranM";
97
+ }
98
+ }
99
+ if (monName2.includes("Mr_mime")) {
100
+ monName2 = "Mrmime";
101
+ }
102
+ if (monName2 === "Unown") {
103
+ monName2 = "UnownA";
104
+ }
105
+ if (monName2 === "Ho_oh") {
106
+ monName2 = "HoOh";
107
+ }
108
+ console.log(monName2);
109
+ const iconName = `gMonIcon_${monName2}`
110
+ monData[monName]["Icon"] = iconName;
111
+ })
112
+
113
+ fs.writeFileSync("../mon-data/monData.json", JSON.stringify(monData, null, 2));
@@ -0,0 +1,85 @@
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 path from "path";
6
+
7
+ const frRomMap = path.resolve("../rom-maps/pokefirered.map");
8
+ const lines = fs.readFileSync(frRomMap, "utf-8").split("\n");
9
+ const symbols = [];
10
+
11
+ for (const line of lines) {
12
+ const match = line.match(/^\s*0x([0-9a-fA-F]+)\s+([A-Za-z0-9_]+)$/);
13
+ if (!match) continue;
14
+
15
+ const address = parseInt(match[1], 16);
16
+ const name = match[2];
17
+ if (!name.startsWith('g')) continue;
18
+
19
+ if (address >= 0x08d00000 && address <= 0x08ffffff) {
20
+ symbols.push({ name, address });
21
+ }
22
+ }
23
+ /**
24
+ *
25
+ {
26
+ "name": "gMonIconPalettes",
27
+ "address": 138229568,
28
+ "size": 96,
29
+ "offset": 4011840,
30
+ "type": "palette"
31
+ },
32
+ {
33
+ "name": "gMonIconPaletteIndices",
34
+ "address": 138231424,
35
+ "size": 440,
36
+ "offset": 4013696,
37
+ "type": "palette"
38
+ },
39
+
40
+ just leaving these here... they are used for the firered icon palettes and I am too lazy to add
41
+ this to the "parser" for now... so yea :p
42
+ */
43
+
44
+ symbols.sort((a, b) => a.address - b.address);
45
+
46
+ const romEnd = 0x09ffffff;
47
+
48
+ function findNextAddressFromLines(currentAddress, lines) {
49
+ for (const line of lines) {
50
+ const match = line.match(/0x([0-9a-fA-F]+)/);
51
+ if (!match) continue;
52
+
53
+ const addr = parseInt(match[1], 16);
54
+ if (addr > currentAddress) return addr;
55
+ }
56
+ return null;
57
+ }
58
+
59
+ for (let i = 0; i < symbols.length; i++) {
60
+ const current = symbols[i];
61
+ const next = symbols[i + 1];
62
+ current.size = next
63
+ ? next.address - current.address
64
+ : romEnd - current.address; // really not needed anymore since like I actually fixed the thing instead of relying on this garbage thingy
65
+ current.offset = current.address - 0x08000000;
66
+ if (!next) {
67
+ const nextAddress = findNextAddressFromLines(current.address, lines);
68
+ current.size = nextAddress
69
+ ? nextAddress - current.address
70
+ : 0;
71
+ }
72
+ }
73
+
74
+ function classifyType(name) {
75
+ if (name.includes('Pal')) return "palette";
76
+ if (name.includes("Tilemap")) return "tilemap";
77
+ if (name.includes("Gfx") || name.includes("Pic")) return "gfx";
78
+ return "unknown";
79
+ }
80
+
81
+ for (const sym of symbols) {
82
+ sym.type = classifyType(sym.name);
83
+ }
84
+
85
+ fs.writeFileSync("../graphics-maps/fr-graphic-map.json", JSON.stringify(symbols, null, 2));