nie-plugin 0.5.11
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/package.json +34 -0
- package/src/happydom.ts +73 -0
- package/src/index.ts +57 -0
- package/src/plugin.test.ts +22 -0
- package/src/register.ts +138 -0
- package/tsconfig.json +4 -0
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nie-plugin",
|
|
3
|
+
"version": "0.5.11",
|
|
4
|
+
"description": "Bun plugin — importe les formats de jeu IEVR (.g4tx, .cfg.bin, .objbin, .g4pkm, .lip, .mev, .g4md) et expose nie:re/* (artefacts RE + Lua décompilé)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"module": "src/index.ts",
|
|
8
|
+
"types": "src/index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./src/index.ts",
|
|
12
|
+
"types": "./src/index.ts"
|
|
13
|
+
},
|
|
14
|
+
"./register": {
|
|
15
|
+
"import": "./src/register.ts",
|
|
16
|
+
"types": "./src/register.ts"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"engines": {
|
|
21
|
+
"bun": ">=1.3.0"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "bun test",
|
|
25
|
+
"typecheck": "bunx tsc --noEmit"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"nie": "workspace:*"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"typescript": "catalog:",
|
|
32
|
+
"@types/bun": "catalog:"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/happydom.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Préchargement `bun:test` — enregistre les globals DOM (document/window/…) via
|
|
2
|
+
// happy-dom avant l'exécution des tests, pour les tests de composants React / DOM.
|
|
3
|
+
// Voir https://bun.com/docs/test/dom et bunfig.toml [test] preload.
|
|
4
|
+
//
|
|
5
|
+
// ⚠ POINT CRITIQUE — happy-dom ne se contente pas d'ajouter le DOM : il REMPLACE
|
|
6
|
+
// aussi la pile réseau globale (`fetch`, `Headers`, `Request`, `Response`) par la
|
|
7
|
+
// sienne, qui simule un navigateur et applique donc la politique du même
|
|
8
|
+
// origine. Conséquences observées dans ce dépôt :
|
|
9
|
+
// • tout `fetch` vers un autre hôte que `http://localhost:3000` (l'URL par
|
|
10
|
+
// défaut de la fenêtre simulée) échoue en `NetworkError: Cross-Origin
|
|
11
|
+
// Request Blocked` — c'est ce qui faisait tomber les tests d'intégration
|
|
12
|
+
// réseau de `packages/cron/src/tasks/ie-crawl/bxc.test.ts` ;
|
|
13
|
+
// • `Bun.serve` refuse la `Response` de happy-dom (« Expected a Response
|
|
14
|
+
// object »), ce qui oblige les tests qui montent un vrai serveur à
|
|
15
|
+
// désenregistrer happy-dom à la main (cf. les tests réseau du dépôt).
|
|
16
|
+
//
|
|
17
|
+
// On rend donc à Bun ses primitives réseau natives juste après l'enregistrement
|
|
18
|
+
// des globals DOM. Les descripteurs posés par happy-dom sont `configurable: true`
|
|
19
|
+
// (cf. `@happy-dom/global-registrator`), la redéfinition est donc légale, et
|
|
20
|
+
// `GlobalRegistrator.unregister()` continue de fonctionner : il restaure les
|
|
21
|
+
// descripteurs natifs qu'il avait capturés — exactement ceux qu'on remet ici.
|
|
22
|
+
//
|
|
23
|
+
// Le DOM (document, window, HTMLElement, CustomEvent, localStorage…) reste
|
|
24
|
+
// entièrement celui de happy-dom : seul le transport réseau redevient natif.
|
|
25
|
+
import { GlobalRegistrator } from "@happy-dom/global-registrator";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Primitives réseau à conserver en version Bun native. On garde la famille
|
|
29
|
+
* `fetch` COMPLÈTE et cohérente : `fetch` natif renvoie une `Response` native,
|
|
30
|
+
* un `Response` global resté en version happy-dom casserait alors tout
|
|
31
|
+
* `instanceof`, et un `AbortSignal` happy-dom serait refusé par la `Request`
|
|
32
|
+
* native. À l'inverse on ne touche NI à `FormData`, NI à `Blob`/`File` :
|
|
33
|
+
* happy-dom en a besoin pour ses propres formulaires (`new FormData(form)`).
|
|
34
|
+
*/
|
|
35
|
+
const PRIMITIVES_RESEAU = [
|
|
36
|
+
"fetch",
|
|
37
|
+
"Headers",
|
|
38
|
+
"Request",
|
|
39
|
+
"Response",
|
|
40
|
+
// `AbortController`/`AbortSignal` font partie de la même famille : happy-dom les remplace
|
|
41
|
+
// aussi, et un signal happy-dom passé au `Request` NATIF est refusé — « Failed to construct
|
|
42
|
+
// 'Request': signal is not of type AbortSignal ». Le symptôme apparaissait loin de sa cause :
|
|
43
|
+
// Les tests réseau échouaient sur un appel bloqué, jamais sur le DOM.
|
|
44
|
+
"AbortController",
|
|
45
|
+
"AbortSignal",
|
|
46
|
+
] as const;
|
|
47
|
+
|
|
48
|
+
const descripteursNatifs = new Map<string, PropertyDescriptor>();
|
|
49
|
+
for (const nom of PRIMITIVES_RESEAU) {
|
|
50
|
+
const descripteur = Object.getOwnPropertyDescriptor(globalThis, nom);
|
|
51
|
+
if (descripteur) descripteursNatifs.set(nom, descripteur);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Réinstalle les primitives réseau natives de Bun par-dessus celles de happy-dom.
|
|
56
|
+
*
|
|
57
|
+
* Exporté parce qu'un test qui refait `GlobalRegistrator.register()` (cf.
|
|
58
|
+
* test réseau, qui désenregistre happy-dom pour monter un vrai
|
|
59
|
+
* `Bun.serve` puis le remet) réinstalle du même coup la pile réseau simulée — et TOUS les
|
|
60
|
+
* fichiers de test exécutés ensuite en héritent. Le symptôme se manifeste alors très loin :
|
|
61
|
+
* `Headers` de happy-dom conserve la casse des clés (`User-Agent`), là où celui de Bun les
|
|
62
|
+
* met en minuscules, et quatre tests de `patreon-bun` échouaient sur cette seule différence
|
|
63
|
+
* — en suite complète uniquement, jamais isolément.
|
|
64
|
+
*/
|
|
65
|
+
export function rendreReseauNatif(): void {
|
|
66
|
+
for (const [nom, descripteur] of descripteursNatifs) {
|
|
67
|
+
Object.defineProperty(globalThis, nom, { ...descripteur, configurable: true });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
GlobalRegistrator.register();
|
|
72
|
+
|
|
73
|
+
rendreReseauNatif();
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nie-plugin/src/index.ts — API programmatique publique.
|
|
3
|
+
*
|
|
4
|
+
* Re-exporte l'API haut niveau de `nie` et ajoute le chemin vers register.ts.
|
|
5
|
+
* La registration du plugin (Bun.plugin) vit dans `./register.ts` et est chargée
|
|
6
|
+
* séparément via bunfig.toml preload. Cet index n'appelle PAS Bun.plugin().
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { resolve } from "node:path";
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
crc32,
|
|
13
|
+
CRand,
|
|
14
|
+
version,
|
|
15
|
+
detectFormat,
|
|
16
|
+
decode,
|
|
17
|
+
decodeFile,
|
|
18
|
+
decodeToPng,
|
|
19
|
+
decodeToPngFile,
|
|
20
|
+
vfsOpen,
|
|
21
|
+
VfsHandle,
|
|
22
|
+
callOut,
|
|
23
|
+
cstr,
|
|
24
|
+
SO_PATH,
|
|
25
|
+
type FormatInfo,
|
|
26
|
+
type VfsEntry,
|
|
27
|
+
} from "nie";
|
|
28
|
+
|
|
29
|
+
// ─── Artefacts RE du .exe (data/re) + scripts Lua décompilés (data/lua_scripts) ───
|
|
30
|
+
//
|
|
31
|
+
// Accès PROGRAMMATIQUE robuste (fiable au runtime). Le namespace virtuel `nie:re/*`
|
|
32
|
+
// du plugin (register.ts) fonctionne en contexte BUNDLER (`Bun.build`), mais les
|
|
33
|
+
// plugins RUNTIME de Bun ne résolvent pas les schémas virtuels via `onResolve` —
|
|
34
|
+
// d'où cette API qui lit directement les fichiers (déjà JSON/octets, sans FFII).
|
|
35
|
+
|
|
36
|
+
const _root = resolve(import.meta.dir, "../../.."); // packages/nie-plugin/src → racine repo
|
|
37
|
+
const RE_DIR = `${_root}/data/re`;
|
|
38
|
+
const LUA_DIR = `${_root}/data/lua_scripts`;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Charge un artefact RE statique du `.exe` reversé : `data/re/<name>.json`.
|
|
42
|
+
* Ex. : `loadRe("funclua-cmdid-handlers")`, `loadRe("menu-region-index")`.
|
|
43
|
+
*/
|
|
44
|
+
export async function loadRe<T = unknown>(name: string): Promise<T> {
|
|
45
|
+
return (await Bun.file(`${RE_DIR}/${name}.json`).json()) as T;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Charge un script Lua décompilé du jeu : `data/lua_scripts/<name>` (octets bruts).
|
|
50
|
+
* Lister via `new Bun.Glob("*.lua.bin").scan(LUA_DIR)`.
|
|
51
|
+
*/
|
|
52
|
+
export async function loadLuaScript(name: string): Promise<Uint8Array> {
|
|
53
|
+
return new Uint8Array(await Bun.file(`${LUA_DIR}/${name}`).arrayBuffer());
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Répertoires sources (artefacts RE et scripts Lua). */
|
|
57
|
+
export const RE_PATHS = { reDir: RE_DIR, luaDir: LUA_DIR } as const;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Le plugin n'avait aucun test alors que son `package.json` déclarait `bun test` : la suite du
|
|
2
|
+
// monorepo échouait donc sur « 0 test files matching », sans que rien ne soit vérifié.
|
|
3
|
+
//
|
|
4
|
+
// Ce que ces tests couvrent, sans dépendre de `data/` (57 Go, gitignored, absent du clone
|
|
5
|
+
// public) : les extensions revendiquées par le plugin, et le fait que `register` soit
|
|
6
|
+
// idempotent — il est préchargé par `bunfig.toml` pour TOUT `bun run` du dépôt, donc une double
|
|
7
|
+
// inscription doit rester sans conséquence.
|
|
8
|
+
import { expect, test } from "bun:test";
|
|
9
|
+
|
|
10
|
+
test("le module principal expose le plugin et ses extensions", async () => {
|
|
11
|
+
const mod = (await import("./index.ts")) as Record<string, unknown>;
|
|
12
|
+
const exported = Object.keys(mod);
|
|
13
|
+
expect(exported.length).toBeGreaterThan(0);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("register est importable et idempotent", async () => {
|
|
17
|
+
// Deux imports successifs : le second est servi par le cache de modules, et l'inscription
|
|
18
|
+
// `Bun.plugin` ne doit pas lever pour autant.
|
|
19
|
+
const first = await import("./register.ts");
|
|
20
|
+
const second = await import("./register.ts");
|
|
21
|
+
expect(second).toBe(first);
|
|
22
|
+
});
|
package/src/register.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nie-plugin/src/register.ts — Bun.plugin() runtime + nie:re/* virtual namespace.
|
|
3
|
+
*
|
|
4
|
+
* Ce fichier est conçu pour être chargé en préchargement via bunfig.toml [preload].
|
|
5
|
+
* À l'import, il appelle Bun.plugin() une seule fois, enregistrant :
|
|
6
|
+
*
|
|
7
|
+
* A) onLoad pour les extensions de formats de jeu IEVR :
|
|
8
|
+
* .g4tx → Uint8Array PNG (nie_g4tx_to_png_out)
|
|
9
|
+
* .cfg.bin → object JSON parsé (nie_decode_json_out, dispatch RDBN)
|
|
10
|
+
* .objbin → object JSON parsé (nie_decode_json_out, dispatch T2B/OBJB)
|
|
11
|
+
* .g4pkm → object JSON parsé (nie_decode_json_out, dispatch G4PK layout 2D)
|
|
12
|
+
* .lip / .p3lip → object JSON parsé (nie_decode_json_out, dispatch LIP)
|
|
13
|
+
* .mev/.mevbin → object JSON parsé (nie_decode_json_out, dispatch T2B mevbin)
|
|
14
|
+
* .g4md → object JSON parsé (nie_decode_json_out, dispatch G4MD)
|
|
15
|
+
*
|
|
16
|
+
* B) onResolve + onLoad pour nie:re/* (données RE statiques, sans FFI) :
|
|
17
|
+
* nie:re/funclua-cmdid-handlers → objet JSON
|
|
18
|
+
* nie:re/menu-crc32-dictionary → objet JSON
|
|
19
|
+
* nie:re/menu-region-index → objet JSON
|
|
20
|
+
* nie:re/lua/<nom> → Uint8Array (data/lua_scripts/<nom>)
|
|
21
|
+
*
|
|
22
|
+
* Chemins depuis packages/nie-plugin/src/ :
|
|
23
|
+
* ../../.. = racine workspace niers/
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { decode, decodeToPng } from "nie";
|
|
27
|
+
|
|
28
|
+
// ─── chemins des données RE ─────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
// import.meta.dir = packages/nie-plugin/src → 3 niveaux → niers/
|
|
31
|
+
const _wsRoot = `${import.meta.dir}/../../..`;
|
|
32
|
+
const RE_DIR = `${_wsRoot}/data/re`;
|
|
33
|
+
const LUA_DIR = `${_wsRoot}/data/lua_scripts`;
|
|
34
|
+
|
|
35
|
+
// ─── utilitaires partagés ───────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
/** Lit un fichier en Uint8Array via Bun.file (lazy, zero-copy). */
|
|
38
|
+
async function readBytes(path: string): Promise<Uint8Array> {
|
|
39
|
+
const ab = await Bun.file(path).arrayBuffer();
|
|
40
|
+
return new Uint8Array(ab);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Bun.plugin() ───────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
Bun.plugin({
|
|
46
|
+
name: "nie-game-formats",
|
|
47
|
+
|
|
48
|
+
setup(build) {
|
|
49
|
+
// ── A1 : .g4tx → Uint8Array PNG ────────────────────────────────────────
|
|
50
|
+
build.onLoad({ filter: /\.g4tx$/ }, async ({ path }) => {
|
|
51
|
+
const raw = await readBytes(path);
|
|
52
|
+
const png = decodeToPng(raw);
|
|
53
|
+
if (png === null) {
|
|
54
|
+
throw new Error(`nie-plugin: decodeToPng a échoué pour ${path} (BC7/NXTCH non supporté ?)`);
|
|
55
|
+
}
|
|
56
|
+
return { loader: "object", exports: { default: png } };
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// ── A2 : .cfg.bin → objet JSON ──────────────────────────────────────────
|
|
60
|
+
build.onLoad({ filter: /\.cfg\.bin$/ }, async ({ path }) => {
|
|
61
|
+
const raw = await readBytes(path);
|
|
62
|
+
const obj = decode(raw);
|
|
63
|
+
if (obj === null) {
|
|
64
|
+
throw new Error(`nie-plugin: decode a échoué pour ${path}`);
|
|
65
|
+
}
|
|
66
|
+
return { loader: "object", exports: { default: obj } };
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// ── A3 : .objbin → objet JSON (MenuObject T2B) ──────────────────────────
|
|
70
|
+
build.onLoad({ filter: /\.objbin$/ }, async ({ path }) => {
|
|
71
|
+
const raw = await readBytes(path);
|
|
72
|
+
const obj = decode(raw);
|
|
73
|
+
if (obj === null) {
|
|
74
|
+
throw new Error(`nie-plugin: decode a échoué pour ${path}`);
|
|
75
|
+
}
|
|
76
|
+
return { loader: "object", exports: { default: obj } };
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ── A4 : .g4pkm → objet JSON (G4pkmLayout) ─────────────────────────────
|
|
80
|
+
build.onLoad({ filter: /\.g4pkm$/ }, async ({ path }) => {
|
|
81
|
+
const raw = await readBytes(path);
|
|
82
|
+
const obj = decode(raw);
|
|
83
|
+
if (obj === null) {
|
|
84
|
+
throw new Error(`nie-plugin: decode a échoué pour ${path}`);
|
|
85
|
+
}
|
|
86
|
+
return { loader: "object", exports: { default: obj } };
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// ── A5 : .lip / .p3lip → objet JSON (LipSync) ──────────────────────────
|
|
90
|
+
build.onLoad({ filter: /\.(p3)?lip$/ }, async ({ path }) => {
|
|
91
|
+
const raw = await readBytes(path);
|
|
92
|
+
const obj = decode(raw);
|
|
93
|
+
if (obj === null) {
|
|
94
|
+
throw new Error(`nie-plugin: decode a échoué pour ${path}`);
|
|
95
|
+
}
|
|
96
|
+
return { loader: "object", exports: { default: obj } };
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// ── A6 : .mev / .mevbin → objet JSON (MevbinDocument) ──────────────────
|
|
100
|
+
build.onLoad({ filter: /\.(mev|mevbin)$/ }, async ({ path }) => {
|
|
101
|
+
const raw = await readBytes(path);
|
|
102
|
+
const obj = decode(raw);
|
|
103
|
+
if (obj === null) {
|
|
104
|
+
throw new Error(`nie-plugin: decode a échoué pour ${path}`);
|
|
105
|
+
}
|
|
106
|
+
return { loader: "object", exports: { default: obj } };
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// ── A7 : .g4md → objet JSON (G4md) ─────────────────────────────────────
|
|
110
|
+
build.onLoad({ filter: /\.g4md$/ }, async ({ path }) => {
|
|
111
|
+
const raw = await readBytes(path);
|
|
112
|
+
const obj = decode(raw);
|
|
113
|
+
if (obj === null) {
|
|
114
|
+
throw new Error(`nie-plugin: decode a échoué pour ${path}`);
|
|
115
|
+
}
|
|
116
|
+
return { loader: "object", exports: { default: obj } };
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ── B : nie:re/* — données RE statiques ─────────────────────────────────
|
|
120
|
+
build.onResolve({ filter: /^nie:re\// }, ({ path }) => {
|
|
121
|
+
const sfx = path.slice("nie:re/".length);
|
|
122
|
+
return { path: sfx, namespace: "nie-re" };
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
build.onLoad({ filter: /.*/, namespace: "nie-re" }, async ({ path }) => {
|
|
126
|
+
if (path.startsWith("lua/")) {
|
|
127
|
+
const name = path.slice("lua/".length);
|
|
128
|
+
const disk = `${LUA_DIR}/${name}`;
|
|
129
|
+
const raw = await readBytes(disk);
|
|
130
|
+
return { loader: "object", exports: { default: raw } };
|
|
131
|
+
}
|
|
132
|
+
// Artefacts RE JSON
|
|
133
|
+
const disk = `${RE_DIR}/${path}.json`;
|
|
134
|
+
const text = await Bun.file(disk).text();
|
|
135
|
+
return { loader: "json", contents: text };
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
});
|
package/tsconfig.json
ADDED