Skip to content

Development

github-actions[bot] edited this page Nov 3, 2026 · 17 revisions

Development

Building and testing Legacy Connector. Requires JDK 21 (newer JDKs fail with "Unsupported class file major version"). Back to Home.

Requires JDK 21.

./gradlew build                 # runtime mod + loader (also builds the distributable jar)
./gradlew bundleJar             # single distributable jar: build/dist/legacyconnector-<ver>.jar, the ONLY jar for a mods folder
                                # (build/libs/*-runtime-only.jar has no loader: every legacy mod is then rejected as built for an old Minecraft)
./gradlew runClient             # dev client, mods from run/mods
./gradlew runServerTest         # headless smoke test with run-servertest/mods; the fixture mod self-tests and stops
LEVELTYPE=biomesoplenty:biomesop tools/dev/run-subset.sh biomesoplenty   # same, in a mod's world type
tools/dev/run-subset.sh hbm thaumcraft     # server test over a few mods only, with the
                                # block/item/entity self tests; the whole corpus takes minutes
./gradlew runClientTest         # client smoke test (run-clienttest, mods linked from run/mods): reports per mod
                                # which models show the missing texture, then quits
tools/dev/run-screenshot-test.sh agricraft appliedenergistics   # graphical test: creates a world (WORLDTYPE=biomesoplenty:biomesop
                                # for BoP), builds block/item showcases, writes screenshots to run-screenshots/shots
SCENARIO=village tools/dev/run-screenshot-test.sh witchery agricraft   # flies over the nearest village (legacy village components)
SCENARIO=gui MODS=rftools tools/dev/run-screenshot-test.sh rftools mcjtylib   # right-clicks each block of the mods and
                                # screenshots the screens that open (custom GUIs); SCENARIO=portal tests a Twilight Forest portal
tools/dev/docker-headless.sh run-screenshot-test.sh ...        # same, inside a Linux container with a virtual display
                                # (Xvfb + Mesa; needs `colima start`), so no window opens on the desktop;
                                # also works for run-client-test.sh
tools/dev/session.sh start <jar fragments>   # one long-lived headless client you drive without restarting it:
tools/dev/session.sh send cmd "legacyconnector dim 7"   # run a command / open GUIs / `mouse x y` / `click x y` / `useitem` / `inv` / `info` ...
tools/dev/session.sh shot name                 # screenshot -> ~/.cache/legacyconnector-docker/work/run-screenshots/control/out/name.png
tools/dev/survey.sh <namespace> <name>         # places every block of a mod on a sky platform and screenshots it
./gradlew checkLegacyMod -PlegacyJar=/path/a.jar,/path/b.jar   # e.g. every jar in test-mods/
                                # translate offline as one modset, list unlinkable references,
                                # generate + JVM-verify the synthesized API (no game launch)

Development commands (operator, any world): /legacyconnector mods lists the loaded legacy mods; /legacyconnector dim list|<id> [pos] switches to a legacy dimension; /legacyconnector gui <mod> <id> [pos] opens a mod GUI by its IGuiHandler id; /legacyconnector place <block> [meta], give <item> [meta] [count] and spawn <entity> [count] create legacy content with metadata; /legacyconnector list <blocks|items|entities> <mod> prints a mod's ids; /legacyconnector biome <id> jumps to a legacy biome; /legacyconnector inspect describes the block or entity you look at (legacy class, metadata, tile entity, render type). Ids tab-complete.

Project layout:

Path What
loader/ FML locator service: jar inspection, translation, linking, API synthesis, caching, report
src/main/java/legacyconnector/v1_7_10/ 1.7.10 shim (legacy package names, relocated)
src/main/java/org/kylepitcock/legacyconnector/ runtime: bootstrap, bridges to NeoForge, JDK/library compat, synth runtime
testmods/ fixture mod compiled against legacy API stubs, built like a real 2014 jar
tools/ developer scripts: reference data download, signature/hierarchy extraction, API lookup, shim hierarchy audit
tools/docker/, tools/dev/ headless-display container image; dev test scripts (subset server test, client/screenshot tests)
test-mods/ (not committed) local corpus of real legacy mods for checkLegacyMod / runServerTest

Reference data (MCP mappings, Forge userdev sources, Mojang client jars) is downloaded into build/devtools/ by tools/fetch_devtools.sh and the extraction scripts; none of it is committed. Only class names and supertypes (hierarchy.txt) and MCP name tables are shipped.

To regenerate the 1.7.10 hierarchy data:

tools/fetch_devtools.sh
python3 tools/extract_legacy_signatures.py 1.7.10   # needs build/devtools/mojang/client-1.7.10.jar
python3 tools/extract_legacy_hierarchy.py 1.7.10

Test workflow notes

  • ./gradlew compileJava --rerun is the compile check (compileJava is non-incremental: the shim access widener rewrites class files), then ./gradlew generateShimIndex, which catches "phantom field access" errors: shim code must read legacy fields that exist only as virtual fields through the mcc$get$/mcc$set$ accessors.
  • tools/dev/run-corpus.sh [dir] runs the server self test over a whole directory of jars. Run only one server at a time and never compile while one runs.
  • Translation results are cached per jar and mod set (run-*/.legacyconnector/cache). After changing the loader, linker, stubber or shim, delete the cache or the old translation is reused.
  • On a Windows desktop (no Docker) tools/dev/quick-shots.sh runs the graphical test directly in about a minute (superflat world, short render distance, a dozen blocks and items per mod); mods go in run-screenshots/mods.
  • Headless server probing (no window): ./gradlew runServerSubTest -PautostopTicks=0 -PserverScript=probe -PserverScriptMod=<legacy mod id> runs a long-lived server that executes the *.js files dropped into run-subtest/probe on the server thread (Nashorn, core/ServerScript.java); tools/dev/server-script.sh -f file.js submits one and prints the answer (globals persist; a function onTick(n) runs every tick). tools/dev/fisk/lib.js is a library for it: it joins fake clients (a captured connection that answers keep-alives and summarises every packet the server sends, so data syncing and custom packets can be checked), puts a hero suit on, drives data vars and interactions and runs multi-tick scenarios (scenario([step, ...]), a step may return the ticks to wait; tools/dev/fisk/wait.sh waits and prints). The probe-*.js and *-sweep.js files there are the Fisk's Heroes ability checks (damage, beams, web, shield throw, sizes, gravity, healing, recipes, machines, items). A fake client never moves its chunk view by itself: tp() tells the chunk map. tools/dev/fisk/probe-client-keys.js is the client-side counterpart (run with tools/dev/js.sh -f in a control session wearing the Mk50 chest piece): it records, per client tick, every change of the worn suit, the nanites data, sprint/sneak state, the ability keys and the key hint list the HUD draws, and FLK.report() prints them (the key hint flicker and the dead Iron Man blade/cannon of issue #118).
  • Live probing: start a session with SCENARIO=control tools/dev/quick-shots.sh, then tools/dev/js.sh '<JavaScript>' evaluates in the running game (bindings mc, server, log; any class via Java.type(...)).
  • Headless server probing: ./gradlew runServerSubTest -PserverTickScript=<file.js> [-PserverTickScriptFrom=200] evaluates the file on every server tick (bindings server, tick counted from the first run, log; globals persist, so place on one tick and read on the next); output is logged as SERVERSCRIPT:. Example: count a mod's blocks per height over the loaded overworld chunks. tools/dev/server-scripts/lib.js (prepend it: cat lib.js script.js > run.js) adds realPlayer(name) (a real, non-fake ServerPlayer on an embedded channel, so mods that skip fake players keep progression data), placeTile (right-click placement with the item), P(label, fn) (catch + short stack) and legacy(...)/lworld(...) wrappers; chromaticraft-*.js there are worked examples (research walk and a finished casting-table cast, dimension 60 stay, machines).
  • tools/dev/thaumcraft/lib.js (fake player, scanAt = what the Thaumometer computes for a block) and scan-thaumometer.js (real ItemThaumometer.doScan on placed blocks, PASS/FAIL per block) run on a -PserverScript=run-subtest/probe -PserverScriptMod=thaumcraft server (submit lib.js first; the directory is relative to run-subtest). Place blocks inside chunk 0,0 only: a block in an unloaded chunk makes the script block the server thread on chunk generation. SERVERSCRIPT:. Example: count a mod's blocks per height over the loaded overworld chunks. Ready-made gameplay rigs live in tools/dev/server-scripts (lib.js places items through a fake player's useOn, forceloads the area and reads tiles/fields; ElectriCraft generator -> wire -> motor/battery/RF cable chains and ReactorCraft fission reactor, steam turbine -> shaft and radiation rigs). Gotchas: forceload add takes block coordinates, not chunk coordinates (otherwise the rig unloads after one tick); legacy mods that kill entities outside y 0..256 (DragonAPI particle entities such as ReactorCraft neutrons) need rigs at y >= 0; calling methods on a tile whose class mentions an absent optional API (BuildCraft) from Nashorn throws NoClassDefFoundError, so read fields via f().
  • Gameplay scenarios without a window: tools/dev/run-server-script.sh <scenario> [autostopTicks] [firstScriptTick] [--fresh] runs a file of tools/dev/server-scripts/scenarios with the helpers of lib.js (platform, placement through the real item use with a fake player, field access, a task scheduler) and rig.js (engine, shafts, machine in a line, also machines fed from below through a bevel gear). The RotaryCraft set builds real engines, gearboxes, clutches and machines, powers them and asserts power, fluids, items, GUIs (containers and shift-click), capabilities (items, fluids, RF), persistence across a restart (persist, run twice) and the handbook data. Jars go in run-subtest/mods/1.7.10.
  • Legacy mod jars are supplied by the developer and never committed (test-mods/ is gitignored).

Link probe (automated compatibility results)

Like the Sinytra Connector checker, tools/compat/probe.py translates mod jars offline, without starting the game, and records whether every reference links:

export JAVA_HOME=$(/usr/libexec/java_home -v 21)
python3 tools/compat/probe.py test-mods2 --mc 1.7.10 --version 0.3.18      # --dry-run to preview, --json out.json keeps the raw records
python3 tools/compat/probe.py --from-json out.json --mc 1.7.10             # convert an earlier run
  • It runs ./gradlew checkLegacyMod -PlegacyJar=... -PcheckJson=<file> on all jars as ONE mod set (mods that extend each other link), then merges the per-jar records into docs/data/mods.json with source probe.
  • links (every reference resolves; missing legacy API is synthesized) becomes status loads, unresolved becomes fails with the first unresolved references in the note. Engine patches (obsolete) and library jars without a mod are not listed.
  • A probe proves only that a jar translates and links, never that it works in game. The site ranks it below player reports, maintainer, client and startup-test results, and the script never overwrites those. Re-running refreshes earlier probe entries.
  • Every run is also appended to docs/data/probe.json (counts per result plus the per-mod list of the last 10 runs per version), which the pie chart page docs/probe.html ("Automated results" on the compatibility site) shows with a run selector and a history table.
  • Jar sources. tools/compat/fetch_probe_mods.py <mc> <dir> [--limit N] downloads the most downloaded Forge mods of a version from Modrinth's public API (SHA-1 checked). The jars are only for the probe: keep them in a gitignored or temporary folder, never commit or redistribute them.
  • CI. .github/workflows/probe.yml runs fetch + probe for 1.7.10 and 1.12.2 on every published release, weekly and on demand (workflow_dispatch, input limit), then commits docs/data/mods.json and docs/data/probe.json. The jars exist only on the runner. Local runs against your own corpus (test-mods2) work the same way, only the committed results matter.

Clone this wiki locally