A decompiler and compiler for MES bytecode, the scripting format used by visual novel engines from Elf, Alice Soft, Silky's, Fairytale, and other software houses throughout the late 80s and into the 90s. Ported from tomyun's "juice" tool written in Racket. Yes, the title is a bad pun.
Juice converts MES binary scripts into human-readable RKT (Lisp-style S-expression) source files, and compiles them back. This enables translation, modding, and analysis of games built on these engines.
Not all engine commands have been identified yet. Unresolved commands appear in decompiled scripts as (cmd:N ...) with their raw opcode number. If you figure out what one does, contributions are welcome.
A companion tool, juice-img, handles the image formats used by the same games (GP4, GPC, GPA), converting them to and from PNG/GIF.
Six engine versions were used across these games. Functionally, they fall into three distinct families:
| Family | Versions | Notes |
|---|---|---|
| AI1 | AI1, AI2 | Older engine with simpler bytecode. AI2 is functionally identical to AI1. |
| AI5 | AI4, AI5 | The most common engine. AI4 is functionally identical to AI5. |
| ADV | AI3, ADV | Advanced engine with segment-based structure. ADV forked from AI3; they are functionally equivalent. |
AI5WIN, a Windows port of AI5, also exists but is not currently supported by juice. It shares most of its bytecode format with AI5 but has some Windows-specific differences.
Decompile all MES scripts in a directory:
juice -d *.MES
This produces .rkt source files alongside the originals. Edit them as needed, then compile back:
juice -c *.rkt
For games that need specific settings, use a preset:
juice -d -p yuno *.MES
Settings from the preset are stored in the RKT file's (meta ...) block, so you only need to specify the preset when decompiling. Compilation reads the settings from the file automatically.
| Flag | Description |
|---|---|
-d, --decompile |
Decompile MES bytecode into RKT source |
-c, --compile |
Compile RKT source into MES bytecode |
-P, --show-preset |
Show supported game presets |
-v, --version |
Show version |
-h, --help |
Show usage help |
| Flag | Description |
|---|---|
-f, --force |
Overwrite existing output files |
-o, --output PATH |
Specify output file path. Cannot be used with multiple input files. |
| Flag | Description |
|---|---|
-e, --engine TYPE |
Engine type: AI5 (default), AI1, ADV. Aliases: AI2 maps to AI1, AI4 maps to AI5, AI5X maps to AI5 with --dictbase D0 --extraop. |
-p, --preset NAME |
Apply a named game preset. Overrides engine, charset, and other defaults. See --show-preset for the full list. |
| Flag | Description |
|---|---|
-C, --charset NAME |
Character set for SJIS mapping. Default: pc98. Options: pc98, english, europe, korean-gamebox, korean-hannuri, korean-kk, korean-parangsae, chinese. |
Charsets define the mapping between Shift-JIS byte pairs in the binary and Unicode characters in the RKT source. The default pc98 charset covers standard Japanese SJIS plus PC-98-exclusive characters. The other charsets extend this with mappings for translation projects targeting other languages (Korean syllables, European diacritics, etc.).
| Flag | Description |
|---|---|
-D, --dictbase HEX |
Dictionary base offset in hex. Default: 80. Some later games use D0. |
-E, --extraop |
Enable extended opcodes used by some later AI5/ADV games. |
The dictionary base determines how many byte values are reserved for dictionary references vs. inline characters. Games like Isaku, YU-NO, Kakyuusei, Jack, and Mobius Roid use D0 instead of the default 80. If you see errors like "dict index >= dict size" during decompilation, try --dictbase D0.
The --extraop flag changes how certain opcodes are decoded. It's required for games like X-Girl and YU-NO that introduced slightly different instruction layouts for a few opcodes. Most later (1994+) ADV games require extraop. If decompilation produces empty or garbled output, this flag (possibly combined with --dictbase D0) is often the fix.
| Flag | Description |
|---|---|
--auto-engine |
Auto-detect engine type from the first input file. Prints the detected engine before processing. |
--no-decode |
Skip SJIS character decoding. Leaves characters as raw byte pairs instead of converting to Unicode. |
--no-resolve |
Skip command/function name resolution. Leaves raw opcode numbers instead of symbolic names like wait or mes-jump. |
--protag SPEC |
Control fusion of protagonist proc/call instructions into text nodes. See below. |
Many games use a (proc N) or (call VAR) instruction between text fragments to insert the protagonist's name. By default, these appear as separate instructions:
(text "ใ")
(proc 0)
(text "ใHello.")With --protag 0, the decompiler fuses them into the text:
(text "ใ" 0 "ใHello.")Values for --protag:
none(default): no fusionall: fuse all proc/call instructions found between text- A number (e.g.
0,3,51): fuse only the specified proc ID - A letter (e.g.
Z): fuse only the specified call variable - A comma-separated list (e.g.
0,Zor0,3): fuse multiple
| Flag | Description |
|---|---|
--auto-wrap |
Auto-wrap text nodes to fit the text-frame width defined in the script. Useful when translated text is longer than the original. |
--no-compress |
Skip AI5 dictionary compression. Produces larger output files. |
When --auto-wrap is enabled, the compiler scans the script for (text-frame X1 Y1 X2 Y2) commands to determine the display width, then automatically inserts line breaks in (text "...") strings that would overflow. This is especially useful for translation projects to Western languages where the translated script is longer than the Japanese.
Presets bundle the correct engine, dictionary base, extraop, and protagonist settings for specific games. Use -P or --show-preset to list them. You only need the preset when decompiling; the settings are saved in the RKT file's metadata.
Full preset list
| Preset | Game | Engine | Special Settings |
|---|---|---|---|
aishi |
Ai Shimai | AI5 | defaults |
angel |
Angel Hearts | AI1 | |
coc |
Curse of Castle | AI5 | defaults |
cre |
Crescent | AI5 | defaults |
deja |
De-Ja | AI1 | protag: variable Y |
deja2 |
De-Ja 2 | AI5 | defaults |
dk |
Dragon Knight | AI1 | |
dk2 |
Dragon Knight 2 | AI1 | protag: variable G |
dk3 |
Dragon Knight 3 | AI5 | protag: proc 26 |
dk4 |
Dragon Knight 4 | AI5 | defaults |
elle |
ELLE | AI5 | protag: variable Z |
foxy |
Foxy | AI5 | defaults |
foxy2 |
Foxy 2 | AI5 | defaults |
isaku |
Isaku | AI5 | dictbase D0, extraop |
jack |
Jack | AI5 | dictbase D0 |
jan |
Jan Jaka Jan | AI5 | protag: proc 51 |
kakyu |
Kakyuusei | AI5 | dictbase D0, protag: proc 3 |
kawa |
Kawarazaki-ke no Ichizoku | AI5 | defaults |
metal |
Metal Eye | AI5 | defaults |
metal2 |
Metal Eye 2 | AI5 | defaults |
mobius |
Mobius Roid | AI5 | dictbase D0 |
nanpa |
Doukyuusei | AI5 | protag: proc 0 |
nanpa2 |
Doukyuusei 2 | AI5 | protag: proc 0 |
nono |
Nonomura Byouin no Hitobito | AI5 | defaults |
pinky |
Pinky Ponky 1/2/3 | AI1 | protag: variable N |
pre |
Premium | AI5 | defaults |
pre2 |
Premium 2 | AI5 | defaults |
raygun |
RAY-GUN | AI1 | |
reira |
Reira Slave Doll | AI5 | defaults |
shima |
Ushinawareta Rakuen | AI5 | defaults |
syan |
Shangrlia | AI5 | defaults |
syan2 |
Shangrlia 2 | AI5 | defaults |
ten |
Tenshin Ranma | AI5 | defaults |
ww |
Words Worth | AI5 | protag: proc 0 |
xgirl |
X-Girl | ADV | extraop |
yuno |
YU-NO | AI5 | dictbase D0, extraop, protag: proc 0 |
When compiling, juice reads settings from the (meta ...) block at the top of the RKT file. These override CLI defaults, so you generally don't need to repeat flags when compiling:
(meta (engine 'AI5) (charset "pc98") (extraop #t))- Accepts multiple input files. Glob patterns (
*,?) are expanded automatically. - Output filenames are generated by swapping the extension:
.mesbecomes.rkt(decompile),.rktbecomes.mes(compile). - The
-oflag overrides the output path but can only be used with a single input file.
| Flag | Description |
|---|---|
-d, --decode |
Decode image files to PNG (GP4, GPC) or GIF (GPA) |
-c, --compile |
Compile PNG to GP4/GPC, or GIF to GPA |
-v, --version |
Show version |
-h, --help |
Show usage help |
| Flag | Description |
|---|---|
-f, --force |
Overwrite existing output files |
-o, --output PATH |
Specify output file path (single file only) |
-F, --format FMT |
Target format when compiling PNG: gp4 or gpc. Default: gpc. Ignored if the output file extension already specifies a format. |
-p, --palette PATH |
Palette source (a GPC file) for GPA decode. If not specified, juice-img searches the parent directory for a matching GPC file. |
-W, --force-width N |
Override canvas width for GP4 decode. Default: 640. Only applies to GP4 files. |
| Format | Input | Output | Notes |
|---|---|---|---|
| GP4 | .gp4 |
.png |
4-pixel column format with move-to-front + RLE compression. Width must be a multiple of 4; non-conforming images are truncated with a warning during encode. |
| GPC | .gpc |
.png |
Indexed color with palette, XOR + interlacing compression. |
| GPA | .gpa |
.gif |
Multi-frame animation. Requires a palette (embedded or from a separate GPC file). |
- When decoding GPA files, if the animation has no embedded palette, juice-img searches for a GPC file with the same name in the parent directory tree. Use
-pto specify one manually. - If no palette is found at all, the resulting GIF will come out entirely black. If your decoded GPA looks all black, this is why.
- The last frame of a decoded GIF may contain metadata (original negative offsets) used for lossless re-encoding. Do not edit this frame if you plan to re-encode the GPA.
- CMake 3.16 or higher
- A C++17 compiler (Clang, GCC, or MSVC)
- iconv (optional, provides fallback for unmapped characters during SJIS conversion)
- Willingness to suffer and fight with
cmakeflags for hours on end.
mkdir build && cd build
cmake ..
cmake --build .Assuming you have your system set up perfectly like mine, this produces two executables: juice and juice-img.
If you don't, well - good luck.
- Windows (MinGW): The build statically links the C++ runtime so the executables can run standalone. If system iconv is not available, a vendored win-iconv is used automatically.
- Linux/macOS: iconv is typically provided by the system's libc. If CMake cannot find it, the build proceeds without it, but some uncommon character mappings may not be available.
The most common cause is using the wrong engine settings. Try these in order:
- Use
--auto-engineto let juice detect the engine type from the file. - Try a preset if one exists for your game (
-Plists them all). - Try
--extraopif you're working with a later AI5 or ADV game. - Try
--dictbase D0if you see dictionary index errors.
Use -f to force overwrite.
lime-juice formats its RKT script slightly differently, which may result in being unable to compile RKT files that were created by tomyun or dantecsm's juice.exe. Try re-decompiling the .MES from the original file, fixing any non-text differences, and then compiling your RKT script file again.
Make sure you're using the right charset (-C). The default pc98 is correct for most Japanese games, but English translations and other localizations need their specific charset.
The GPA file may not have an embedded palette. Either provide one with -p palette.gpc or place the matching GPC file in a sibling directory where juice-img can find it.
- MES Binary Format Specification
- AI5 Scripting Reference
- AI1 Scripting Reference
- ADV Scripting Reference
- GP4 Image Format
- GPC/GPA Image Format
lime-juice: C++ port of Tomyun's "Juice" de/recompiler for PC-98 games
Copyright (C) 2026 Fuzion
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Most of this project's files are licensed under the GNU Public License, version 3 or later (SPDX: GPL-3.0-or-later). For the full text of this license, see the included /LICENSE.md.
Exempt from this license are: