Moteur JavaScript/Web expérimental pour faire tourner des visual novels LuckSystem / LucaSystem / LuckEngine directement dans le navigateur, à partir des fichiers originaux possédés par l'utilisateur.
LuckEngine-Web est une réimplémentation web du runtime nécessaire à AIR et, à terme, aux autres jeux proches du moteur LuckSystem/LucaSystem. L'objectif est simple : remplacer l'exécutable Windows par un moteur moderne, portable et lisible, capable d'interpréter les ressources du jeu dans un navigateur.
Le projet ne contient aucune ressource commerciale : pas de scripts originaux, pas d'images, pas de voix, pas de musiques, pas de vidéos. Le joueur doit posséder le jeu et importer ses propres fichiers .PAK localement.
Fichiers du jeu possédés par l'utilisateur
│
▼
SCRIPT.PAK / BGCG.PAK / CHARCG.PAK / EVENTCG.PAK / PARTS.PAK / voice.PAK / SE.PAK / MUSIC.PAK
│
▼
PakReader ──► AIRParser ──► AIRVM ──► Game ──► CanvasRenderer + AudioManager
│ │ │ │
│ │ │ └── sauvegardes navigateur
│ │ └── variables, choix, sauts, scènes
│ └── CodeLine, opcodes, chaînes, expressions
└── extraction d'entrées PAK / images CZ / audio brutLe moteur est jouable, mais encore en phase de portage/reverse-engineering. Il permet déjà de lancer AIR dans le navigateur avec une grande partie du rendu, de la logique de script, des choix, de l'audio et de l'interface.
- Lecture des conteneurs
.PAKdu jeu. - Parsing des scripts
SCRIPT.PAKen lignes de code interprétables. - Exécution VM des opcodes importants : dialogues, choix, conditions, sauts, changement de
seen, appels, variables. - Décodage des images
CZ0,CZ1,CZ3,CZ4avec LZW et reconstruction des deltas de lignes. - Affichage canvas des décors, CG, sprites/personnages, fenêtres de dialogue et choix.
- Gestion des choix
SELECTavec séparation$det écriture de variable. - Évaluation d'expressions
#NNNN, opérateurs arithmétiques/logiques et conditionsIFN/IFY. - Audio navigateur : voix, SE ponctuels, SE d'ambiance bouclés, BGM.
- Vidéos d'introduction/cinématiques via
<video>quand les fichiers sont fournis. - Sauvegarde locale via le navigateur.
- Import des fichiers utilisateur sans upload serveur.
- Tests Node.js synthétiques pour valider les formats essentiels.
- Certains opcodes graphiques avancés restent à affiner selon les scènes.
- Le mapping exact de toutes les images/personnages peut encore demander des corrections par cas réel.
- Le rendu vise la fidélité, mais n'est pas encore une reproduction parfaite de l'exécutable original.
- La compatibilité hors AIR dépendra du travail de mapping opcode/plugin pour les autres titres.
git clone https://github.com/TON-COMPTE/LuckEngine-Web.git
cd LuckEngine-Web
npm test
python3 -m http.server 8080Ouvre ensuite :
http://localhost:8080Le projet utilise des modules ES côté navigateur. Il faut donc le servir en HTTP, même en local. Ouvrir index.html directement en file:// peut bloquer certains imports selon le navigateur.
Le dépôt ne fournit pas les fichiers d'AIR. Pour jouer, importe ou place tes propres fichiers dans game/AIR/ :
game/AIR/
├── SCRIPT.PAK obligatoire pour les scripts
├── BGCG.PAK décors
├── CHARCG.PAK sprites/personnages
├── EVENTCG.PAK CG événementiels
├── OTHCG.PAK images diverses
├── SYSCG.PAK UI système
├── SYSCG2.PAK UI système additionnelle
├── PARTS.PAK fenêtres, choix, éléments UI
├── voice.PAK voix
├── voice1.PAK voix additionnelles éventuelles
├── SE.PAK effets sonores
└── MUSIC.PAK musiquesTu peux aussi déposer les fichiers directement dans l'interface web. Ils restent côté navigateur, dans le stockage local/IndexedDB, et ne sont pas envoyés à un serveur par LuckEngine-Web.
npm testLance les tests synthétiques : lecture PAK, parsing script, SELECT, expressions, VM, changement de seen, décodeur CZ.
npm run inspect:pakInspecte ./game/AIR/SCRIPT.PAK et liste ses entrées.
npm run extract:script8Extrait l'entrée 8 du SCRIPT.PAK vers scripts/8.bin.
npm run parse:script8Parse une entrée extraite pour vérifier les opcodes et chaînes.
npm run serveLance un serveur statique local sur le port 8080.
LuckEngine-Web/
├── index.html
├── package.json
├── LICENSE
├── NOTICE.md
├── README.md
├── game/
│ └── AIR/
│ └── PUT_AIR_FILES_HERE.txt
├── scripts/
│ ├── README.txt
│ └── UPDATE_GITHUB.md
├── docs/
│ ├── reverse/
│ │ ├── AIR.txt
│ │ ├── AIR.py
│ │ ├── pak.go
│ │ ├── script.go
│ │ └── vm.go
│ └── claude/
│ ├── CLAUDE_PROMPT.md
│ └── notes.md
└── src/
├── app/
│ ├── boot.js
│ ├── config.js
│ ├── Game.js
│ └── charcgKeys.js
├── assets/
│ └── AssetStore.js
├── audio/
│ └── AudioManager.js
├── image/
│ └── czimage.js
├── pak/
│ └── PakReader.js
├── render/
│ └── CanvasRenderer.js
├── save/
│ └── SaveManager.js
├── script/
│ ├── AIRParser.js
│ ├── OpcodeTable.js
│ └── ScriptBinaryReader.js
├── tests/
│ ├── extractScriptEntry.js
│ ├── inspectPak.js
│ ├── selftest.js
│ └── testParseScript.js
└── vm/
├── AIRVM.js
└── ExprEval.jsLe moteur a été construit en combinant trois sources de compréhension :
- observation de données réelles extraites d'un jeu possédé légalement ;
- comparaison avec les outils publics LuckSystem ;
- réécriture progressive en JavaScript, validée par tests et par exécution dans le navigateur.
L'objectif n'est pas de redistribuer AIR ni de contourner la possession du jeu. Le projet documente et réimplémente un format de ressources pour permettre l'interopérabilité et la préservation côté navigateur.
Les fichiers .PAK sont traités comme des conteneurs de ressources. Le lecteur src/pak/PakReader.js est dérivé de l'analyse du fichier pak.go de LuckSystem et des dumps réels.
Format utilisé par le moteur :
Header PAK, little-endian
0x00 uint32 HeaderLength
0x04 uint32 FileCount
0x08 uint32 IDStart
0x0C uint32 BlockSize
0x10 uint32 Unknown2
0x14 uint32 Unknown3
0x18 uint32 Unknown4
0x1C uint32 Unknown5
0x20 uint32 FlagsLa table des entrées n'est pas supposée à un offset fixe. Elle est retrouvée par scan depuis l'offset 0x20, en cherchant le premier uint32 égal à :
HeaderLength / BlockSizeChaque entrée contient ensuite :
uint32 OffsetInBlocks
uint32 LengthInBytesL'offset réel est calculé ainsi :
OffsetInBytes = OffsetInBlocks * BlockSizeSi Flags & 512 est actif, le PAK contient une table de noms. Dans ce cas, un pointeur situé juste avant la table d'entrées donne le début des noms null-terminés. Le moteur peut donc récupérer les ressources soit par index, soit par ID, soit par nom.
Les scripts sont lus sous forme de CodeLine. Le parser src/script/AIRParser.js et le lecteur binaire src/script/ScriptBinaryReader.js reprennent la logique observée dans script.go.
Structure générale :
uint16 Len
uint8 Opcode
uint8 FixedFlag
bytes RawBytes[Len - 4]
byte Padding éventuel si Len impairFixedFlag indique combien de uint16 initiaux doivent être séparés du reste des paramètres :
FixedFlag = 0 → aucun paramètre fixe
FixedFlag = 1 → 1 uint16 fixe
FixedFlag >= 2 → 2 uint16 fixesLes opcodes sont nommés à partir de docs/reverse/AIR.txt, puis chaque instruction est interprétée dans AIRParser.js.
Les textes de dialogue combinent plusieurs encodages :
- japonais : UTF-16LE ;
- slot anglais/traduction : UTF-8 avec longueur négative encodée sous forme
0x10000 - taille; - chinois/autre slot : UTF-16LE.
Exemple logique pour MESSAGE :
uint16 voiceOrUnknown
string jp UTF-16LE
string en/traduction UTF-8
string zh UTF-16LE
bytes tail éventuelsPour le patch FR testé, le texte français se trouve dans le slot en/traduction.
SELECT contient le texte des choix, séparé par $d.
SELECT("Choix 1$dChoix 2")Le premier uint16 du SELECT est traité comme l'identifiant de variable écrit par le choix. Quand le joueur choisit une option, le moteur écrit par exemple :
#6001 = 0
#6001 = 1
#6001 = 2Les opcodes suivants comme IFN ou IFY relisent ensuite cette variable pour décider de la branche.
src/vm/AIRVM.js exécute les instructions dans l'ordre et maintient l'état du jeu : variables, pile d'appels, sprites, scène courante, changement de seen.
Les opcodes de flux actuellement pris en compte incluent :
GOTO
ONGOTO
GOSUB
RETURN
JUMP
FARCALL
FARRETURN
IFY
IFN
ENDPoint important observé pendant le reverse : END n'est pas traité comme une fin absolue du jeu. Dans certains scripts, il faut continuer la logique de scène ou attendre un changement de seen.
src/vm/ExprEval.js est un portage JavaScript de la logique d'expressions de LuckSystem.
Il gère notamment :
#NNNN
+ - * / %
& | ^ << >>
> < >= <= == !=
&& ||
parenthèsesCette partie est essentielle parce que les routes ne sont pas de simples boutons : elles dépendent de variables internes, de conditions et de sauts conditionnels.
src/image/czimage.js est le portage JavaScript du décodeur CZ de LuckSystem.
Formats actuellement gérés :
CZ0 image brute
CZ1 palette / RGB / RGBA
CZ3 LineDiff
CZ4 LineDiff4 avec RGB et alpha séparés
LZW décompression utilisée par les variantes compresséesLe pipeline image est :
IMAGELOAD / HAIKEI_SET / nom de ressource
│
▼
Recherche dans BGCG.PAK / CHARCG.PAK / EVENTCG.PAK / PARTS.PAK
│
▼
Extraction de l'entrée PAK
│
▼
decodeCZ(bytes)
│
▼
ImageData RGBA
│
▼
CanvasRendererLe moteur applique aussi des corrections de rendu comme l'edge-bleed pour éviter les franges autour des pixels transparents.
L'audio est résolu à partir des paramètres de script et des PAK fournis par l'utilisateur.
Mapping actuellement utilisé :
VOICE id direct depuis le champ du MESSAGE → voice.PAK / voice1.PAK
SE index = (u16 >> 8) - 65 → SE.PAK
BGM index = (u16 & 0xFF) - 161 → MUSIC.PAK / BGM.PAKLes SE dont le troisième argument vaut 512 sont traités comme des ambiances bouclées : cigales, pluie, vent, etc. Les SE ponctuels utilisent un autre canal pour ne pas couper l'ambiance.
Le rendu utilise les vrais éléments UI importés par l'utilisateur depuis PARTS.PAK :
- fenêtre de dialogue
MWIN0; - fenêtres de choix
SELWIN/SELWIN_s; - médaillons de date ;
- éléments de titre et de menu quand disponibles.
Le but est d'éviter une interface générique et de se rapprocher progressivement du ressenti original.
LuckEngine-Web est un projet statique. Il peut être servi par Nginx, Apache, Caddy, GitHub Pages ou n'importe quel serveur HTTP.
Exemple Nginx minimal :
server {
listen 80;
server_name example.com;
root /var/www/luckengine-web;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}Pour un dépôt public, garde cette règle : aucun fichier de jeu ne doit être commit.
Le .gitignore du projet exclut déjà les extensions et dossiers à risque :
*.PAK
*.pak
*.webm
*.mp4
*.ogv
*.png
*.jpg
*.wav
*.ogg
game/AIR/*
scripts/*.binAvant de pousser sur GitHub :
git status --ignoredLes ressources commerciales doivent apparaître dans les fichiers ignorés, jamais dans les fichiers suivis.
Le code original de LuckEngine-Web est publié sous licence MIT. Voir LICENSE.
Cela signifie que le code du moteur peut être utilisé, copié, modifié et redistribué, y compris dans des forks, à condition de conserver la notice de copyright et la licence.
Cette licence ne s'applique pas aux éléments suivants :
- les fichiers originaux d'AIR ou d'autres visual novels ;
- les scripts, images, voix, musiques, vidéos, polices et ressources commerciales ;
- les marques, noms, logos et personnages appartenant à leurs ayants droit ;
- tout fichier importé par l'utilisateur pour jouer.
Les parties portées ou adaptées depuis LuckSystem restent soumises à leurs notices d'origine. Voir NOTICE.md.
- Conception du portage web, intégration JavaScript, tests navigateur, reverse-engineering pratique : KikenZozo.
- Assistance de développement et de documentation : outils IA utilisés ponctuellement pour structurer, déboguer et documenter le projet. La responsabilité du dépôt publié reste celle du mainteneur.
Ce projet s'appuie fortement sur l'étude de LuckSystem, outil open-source de reverse-engineering et de traduction pour les moteurs Prototype/LucaSystem/LuckSystem.
Dépôt principal :
https://github.com/wetor/LuckSystemCrédit principal :
LuckSystem — Copyright (c) 2026 WéΤοr — MIT LicenseÉléments étudiés, portés ou adaptés :
pak/pak.go → src/pak/PakReader.js
game/expr/expr.go → src/vm/ExprEval.js
game/expr/utils.go → src/vm/ExprEval.js
czimage/*.go → src/image/czimage.js
script/script.go → src/script/AIRParser.js + ScriptBinaryReader.js
game/operator/*.go → sémantique des opcodes
data/AIR.py / AIR.txt → table et paramètres AIRLe fork LuckSystem-2.3.2-Yoremi-Update a aussi servi de référence pratique pour les corrections, le support de formats et les workflows AIR :
https://github.com/yoremi-trad-fr/LuckSystem-2.3.2-Yoremi-UpdateAIR, ses ressources, son univers, ses personnages, ses images, ses musiques, ses voix et ses scripts appartiennent à leurs ayants droit, notamment Key / VisualArts selon l'édition concernée.
LuckEngine-Web n'est pas affilié, approuvé, sponsorisé ou maintenu par Key, VisualArts, Prototype ou tout autre ayant droit.
LuckEngine-Web existe pour trois raisons :
- Préservation : permettre à des œuvres anciennes ou dépendantes de vieux runtimes de rester jouables.
- Interopérabilité : comprendre les formats pour les exécuter sur des plateformes modernes.
- Accessibilité : rendre le jeu possible sur PC, tablette, téléphone ou serveur personnel, sans machine virtuelle lourde.
Le projet ne doit pas devenir un moyen de redistribuer des jeux commerciaux. Le dépôt public doit rester un moteur vide : l'utilisateur fournit ses fichiers légalement obtenus.
- Stabiliser tous les opcodes visuels utilisés dans AIR.
- Améliorer les transitions, fades, shakes et effets spéciaux.
- Reproduire plus fidèlement les menus originaux.
- Renforcer le système de sauvegardes/export/import.
- Ajouter des outils de diagnostic pour mapper plus vite les entrées PAK.
- Préparer des profils pour d'autres jeux LuckSystem/LucaSystem.
- Ajouter une documentation développeur plus complète dans
docs/.
Les contributions sont bienvenues si elles respectent ces règles :
- ne jamais envoyer de fichiers commerciaux ;
- documenter les observations de reverse-engineering ;
- préférer des tests reproductibles avec données synthétiques ;
- créditer clairement les sources open-source utilisées ;
- séparer le moteur générique des données propres à un jeu.
Ce projet est fourni à des fins d'interopérabilité, d'apprentissage, de préservation et d'expérimentation technique. Il ne fournit aucun jeu et ne donne aucun droit sur les ressources commerciales nécessaires pour jouer.