From 736ef36a44b4a679eadbe17e30dfe22c4eb492b4 Mon Sep 17 00:00:00 2001 From: sasheg dev <152582704+SashegDev@users.noreply.github.com> Date: Mon, 19 Oct 2026 01:03:19 +0300 Subject: [PATCH] =?UTF-8?q?README=20=D0=BF=D1=80=D0=B8=D0=B2=D0=B5=D0=B4?= =?UTF-8?q?=D1=91=D0=BD=20=D0=B2=20=D1=81=D0=BE=D0=BE=D1=82=D0=B2=D0=B5?= =?UTF-8?q?=D1=82=D1=81=D1=82=D0=B2=D0=B8=D0=B5=20=D1=81=20=D1=82=D0=B5?= =?UTF-8?q?=D0=BC,=20=D1=87=D1=82=D0=BE=20=D1=80=D0=B5=D0=B0=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D0=BE=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 90 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c8dbcc5..062c96a 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,22 @@ vanilla client-1.12.2.jar verified Mojang signature -> merge with MCP CSVs 9991 fields, 18715 methods renamed -> SpecialSource 3310 classes in MCP names -> FernFlower 2050 source files - -> hand patches + rules 103 patches, one regex rule - -> javac 3261 classes, clean + -> hand patches + rules 114 patches, one regex rule + -> javac 3268 classes, clean ``` `prepare-game.ps1` runs the whole thing and is idempotent. Hand fixes live in `game-fork\patches\decompiler-fixes.txt` and are re-applied on every run, so regenerating the tree never loses them. +Compiling clean proves nothing. Decompilation quietly breaks code that still +compiles, still has the right methods and still runs: an entity that compares +itself with itself never leaves the ground, a list indexed by entity id +removes the wrong player. Every patch here was checked against the original +bytecode with `javap`, and `tools\javap_diff.py` is the tool that makes that +comparison usable. The self-comparison finder and the remove-by-index finder +both came out of this class of bug. + The fork holds Mojang-derived code and is **not published**. What ships in the open repository is the loader, the mod API and the build tools. @@ -53,12 +61,12 @@ open repository is the loader, the mod API and the build tools. Mods are jars with a descriptor at `META-INF/rsxmod.properties`: ```properties -id=rsxm-lib -name=RustNotMC Library -version=1.0.0 +id=rsxm-demo +name=RustNotMC Demo +version=1.1.0 env=CLIENT -entrypoint=net.rustnotmc.lib.LibMod -access=hud, text, world, player +entrypoint=net.rustnotmc.demo.DemoMod +access=hud,commands,files depends.core=>=1.0 ``` @@ -66,34 +74,84 @@ depends.core=>=1.0 the field exists so a mod that targets both sides does not need a format change when the loader moves to the server. -Each mod gets its own class loader. Mods declare the capabilities they need and -the loader grants them; `reflection` is never granted without explicit operator -consent. +Each mod gets its own class loader. Load order comes from the dependency graph, +not from filenames. Shutdown runs in reverse. -Load order comes from the dependency graph, not from filenames. Shutdown runs -in reverse. +### What a mod may touch + +A client mod changes what the client shows. It never decides what is true about +the world: the server owns the world, the inventory, the player and the +network. Capabilities are only granted when something actually checks them. + +| Capability | Grants | Checked | +|---|---|---| +| `CORE` | lifecycle, logging, own mod directory | always | +| `HUD` | draw overlays through the game's font | granted from descriptor | +| `INPUT` | read keyboard and mouse state | granted from descriptor | +| `COMMANDS` | register commands the player can type | granted from descriptor | +| `FILES` | a directory of its own under `run/mods/` | granted from descriptor | +| `REFLECTION` | reach into game internals | never automatic, operator consent | + +`WORLD`, `PLAYER`, `NETWORK`, `TEXT`, `INPUT` and `COMMAND` were removed: they +were advertised but not enforced, so a descriptor could claim them and receive +nothing. A capability that cannot be granted is worse than an absent one. + +Commands carry a scope. `CLIENT` commands run on the client and are never sent +anywhere. `SERVER` and `BOTH` commands are registered but deliberately not +executed on the client, because the server is what has to see them and decide. + +The client API contains no game types at all, which is what lets the loader +build and test with the game closed: the SPI is implemented on the game side +and the API never depends on it. `tick()` advances on the client's own tick, +not on frames, so a mod counts the same twenty steps a second at 60 fps and at +240. ## Building ```powershell -rsx-client\scripts\build.ps1 # dist\rsx-loader.jar -rsx-client\scripts\test.ps1 # self test, 96 checks -game-fork\scripts\prepare-game.ps1 # rebuild the fork from the vanilla jar -game-fork\scripts\compile-game.ps1 # compile only, fast iteration +rsx-client\scripts\build.ps1 # dist\rsx-loader.jar +rsx-client\scripts\test.ps1 # self test, 128 checks +game-fork\scripts\prepare-game.ps1 # rebuild the fork from the vanilla jar +game-fork\scripts\compile-game.ps1 # compile only, fast iteration +rsx-client\scripts\run-game.ps1 # launch the client ``` Only a JDK is required. No Gradle, no network once the mappings are cached. +### Assets + +The client will not run without a real asset tree, and the launcher on this +machine keeps assets the modern way: `indexes/1.12.json` pointing at +`objects//`. A 1.12.2 client reads neither. It wants +`/minecraft/`. + +Pointing `--assetsDir` straight at the object store does not look broken. Text +and textures come from the jar, so the game looks complete, but every sound has +been an asset since 1.7.3 and none of them are on disk: the sound engine +starts, `play()` is called, nothing is found, and paulscode says nothing. +`tools\materialize_assets.py` lays the tree out and `run-game.ps1` calls it on +every launch, so this cannot be forgotten again. + ## Tools | Script | Purpose | |---|---| | `rsx-client\tools\merge_srg_mcp.py` | vanilla SRG plus MCP CSVs into one obf to MCP SRG | | `rsx-client\tools\apply_patches.py` | re-applies the hand fixes, fails loudly if one misses | +| `rsx-client\tools\add_patch.py` | appends a reviewed patch to the fix file | | `rsx-client\tools\fix_stale_locals.py` | FernFlower's stale local references, scoped per method | | `rsx-client\tools\fix_generic_casts.py` | inserts the cast where a generic loses its type parameter | | `rsx-client\tools\fix_decompiler_rules.py` | systematic rewrites that would be hundreds of patches | | `rsx-client\tools\show_errors.py` | javac errors next to the source line that caused them | +| `rsx-client\tools\struct_diff.py` | class-by-class comparison against the reference, plus orphan analysis | +| `rsx-client\tools\javap_diff.py` | instruction-level comparison, normalising constant pool and offsets | +| `rsx-client\tools\find_self_comparisons.py` | finds `x == x`, the decompiler's signature bug | +| `rsx-client\tools\find_remove_index_bugs.py` | finds `List.remove(int)` where an object was meant | +| `rsx-client\tools\find_callers.py` | who calls a field or method, via the constant pool | +| `rsx-client\tools\materialize_assets.py` | lays the asset tree out from the index and object store | +| `rsx-client\tools\ogg_duration.py` | real length of an ogg, to tell a short file from a cut-off one | +| `rsx-client\tools\stage_run.py` | writes the runtime classpath | +| `rsx-client\tools\fetch_missing.py` | pulls natives into the local run directory | ## Legal