Tools and notes for converting IBM J9/CDC JXE (rom.classes) images back to standard Java .class files / .jar archives.
| Directory | Description |
|---|---|
src/ |
Python implementation of JXE → JAR conversion |
test/custom_edgecases/ |
Exhaustive edge-case suite (Java 1.2) to validate the converter |
out/ |
Conversion outputs and logs |
vms/ |
Virtualized environments for legacy tooling (XP VM → WM5 emulator → jar2jxe) |
The converter is validated through edge-case tests and a JAR → JXE → JAR round-trip pipeline.
- Before: Wrote ROM class version as-is (can be non-standard).
- Now:
- Infers minimal version from flags/opcodes (minimum 46, no upper cap).
- Bumps to 49 when synthetic/enum/annotation flags are present so
javac/javapaccept output.
- Before: Ignored ROM field constant values.
- Now:
J9ROMFieldcaptures constant slots (const_value,const_value2,const_value3).jxe2jar.pyemitsConstantValuefor static final fields (int/float/long/double/String).- Recovers constants from ROM metadata even when the constant pool slot is placeholder/omitted.
- Before: Limited constant types and minimal UTF-8 handling.
- Now:
- Supports INTEGER/FLOAT/LONG/DOUBLE/STRING/REF and preserves descriptors.
- Encodes UTF-8 with
surrogatepassto keep odd ROM strings stable. - Decodes J9 ROM "LONG" slots into proper field/method refs using ROM base offsets.
- Before: Basic mapping, missing J9 wide opcodes and invokeinterface handling.
- Now:
- Wide opcodes are normalized to JVM
wide. invokeinterfacecount is computed from the descriptor.ldcis promoted toldc_wwhen CP index > 255.- J9 prefix opcodes are expanded correctly (e.g., implicit
aload_0prefix). - Branch and switch offsets are rewritten using output offset maps to avoid
javaperrors. - Switch padding uses output offset so alignment is correct.
- Invalid/missing CP refs are handled defensively instead of crashing.
- Wide opcodes are normalized to JVM
- Before: Conditional 45.3 layout (u8/u8/u16 for stack/locals/code_len).
- Now:
- Always JVM-standard u16/u16/u32 layout.
- Exception table offsets are rewritten after bytecode expansion.
- Before: Synthetic flags passed through blindly.
- Now:
--strip-syntheticoption clearsACC_SYNTHETICon classes/methods/fields for strict tooling.
- Before: Minimal
python JXE2JAR.py input.jxe output.jar. - Now: argparse with options:
--skip-jdk,--skip-classes,--skip-libs,--strip-synthetic.
- Build edge-case JAR:
sh test/custom_edgecases/build.sh
- Convert JAR → JXE with
jar2jxe.exe(seevms/xp/README.md). - Convert JXE → JAR with Python:
python3 src/jxe2jar.py input.jxe output.jar
- JDK/JRE classes are skipped by default using
src/rt.classes. - Use
--skip-jdk /path/to/rt.jarto override the JDK/JRE skip list. - Use
--skip-classesto provide additional JAR/JMOD/list files or a directory. - The converter preserves
ACC_SYNTHETICby default. Use--strip-syntheticif you need strictjavapoutput for 45.0 classes. - Classfile versions are inferred from flags/opcodes with a minimum of 46 (no upper cap).
- Some large binaries/ISOs are referenced via
.urlfiles pointing to original archives:vms/xp/en_vs_2005_pro_dvd.iso.urlvms/xp/en_windows_xp_professional_with_service_pack_3_x86_cd_vl_x14-73974.iso.url
src/README.md– Format knowledge and implementation detailstest/custom_edgecases/README.md– Test coverage listvms/xp/README.md– WM5 emulator + XP VM instructions
Thanks to the original repo, forks, and contributors: