sasheg dev cda7198c76 Модель предметов по базе ремейка: узлы, деревья, знание
Протокол:
- ItemData читает таблицу items/items.csv (218 предметов ремейка) и отдает
  числа: урон, дальность, темп, прочность, сколько ресурса за удар.
- ItemType: поля rid, researchScrap, techTreeBench, techTreeScrap,
  techTreeParent, admin. Урон по частям тела делится как в ремейке: голова
  вдвое, руки и ноги четыре пятых. Число в исходнике главнее таблицы.
- ItemRegistry: 218 предметов с рецептами, ценой исследования и веткой
  дерева. Новые вещи: salvage-тесак, бронедвери, гаражи, взрывчатка,
  компоненты, еда, мусор, верстаки и печи.
- NodeType: узлы с запасом (дерево 500/750/1000, камень 750, металл 500,
  сера 200) и меткой: крестик на дереве до двух, горячая точка на руде до
  трех.
- TechTree: ветки верстаков с предками, 125/250/500 скрапа.
- Новые пакеты: Dig, BreakProgress, CraftStart, CraftUpdate, ResearchStart,
  ResearchUpdate, TechTreeUnlock, KnownRecipes, TechTreeState, ContainerOpen,
  ContainerTake, LearnBlueprint.

Сервер:
- Knowledge: что игрок знает - рецепты и открытые узлы деревьев.
- Crafter: проверка рецепта, уровня верстака и ингредиентов; время крафта
  по формуле плана.
- ResearchTable: предмет плюс скрап, десять секунд, предмет превращается в
  чертёж; чертёж изучается и открывает рецепт.
- ResourceNode: запас, метка, последний удар отдает остаток.
- JunkPile: две-четыре бочки (серая - скрап и мусор, синяя - компонент,
  красная - нефть и топливо) и ящик с едой; пустая мусорка исчезает через
  60-180 секунд.
- RoadNetwork: сетка дорог и кольцо, точки спавна мусорки; генератор кладет
  асфальт и ровняет дорогу по ширине.
- Проверка досягаемости вынесена из changeBlock, игрок получил benchLevel.
- Сессия отвечает на новые пакеты, при входе шлет известные рецепты и
  открытые узлы.

Тесты: протокол 24 + 10, сервер 74 (было 57). Форк собирается чисто.
2026-10-21 19:54:22 +03:00

RustNotMC

A survival game built on a Minecraft 1.12.2 client as the rendering platform, with its own server, its own protocol and its own accounts.

Подробности по оригиналу (Rust), наши решения и план работ: PLAN.md.

Repositories

Repository License What lives there
rsx-client MIT Loader, mod API, client hooks, deobfuscation tools
rsx-protocol MIT Wire format, packet schemas, shared vocabulary
rsx-plugin-api MIT Interfaces for server-side content
rsx-server-core proprietary Tick loop, world, entities, scaling
rsx-auth proprietary The server's own accounts, used when there is no launcher

Target

Base platform is Minecraft 1.12.2, forked into our own source tree so the game is moddable from the start rather than patched from outside.

  • game fork: Java 17, compiled by game-fork\scripts\prepare-game.ps1
  • loader and mod API: Java 17
  • mods: Java only

Capacity target is 200 players on 4 cores and 8 GB of RAM, scaling to 500 on larger hardware.

The fork

The client is not wrapped around the game jar. game-fork rebuilds it from the verified vanilla jar into readable source, and the loader is an ordinary part of that game rather than an agent injected into someone else's process.

vanilla client-1.12.2.jar   verified Mojang signature
  -> mcp-1.12.2-srg.zip    3344 classes, 20817 members
  -> merge with MCP CSVs   9991 fields, 18715 methods renamed
  -> SpecialSource          3310 classes in MCP names
  -> FernFlower            2050 source files
  -> 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.

The server

The server owns the world. The client draws it and believes nothing.

The wire

The protocol is ours, not the vanilla one. A vanilla 1.12.2 client and our server cannot talk to each other at all, and that is the point: the game is ours, so the wire is ours, and every packet in it is one we wrote down.

The client's own game code is not rewritten. The client reads our packets and translates them into the game's, and the game does what it already knows how to do: build a world, spawn entities, draw them. The transport is ours; the renderer is the game's. The other direction is the same: the game says "the player moved" and "the player broke a block", and those two become our packets.

The world

A chunk is sixteen by sixteen columns, two hundred and fifty six high, one byte per block. Generation is a pure function of the seed and the coordinates: no shared state, no java.util.Random carried between calls, no dependence on the order chunks are asked for. A chunk may be generated months after its neighbour, on another machine, and has to agree with it exactly, because a save file can only record what changed and not the terrain underneath it.

Chunks are written one file per chunk, to a temporary name and moved into place, with a version and a checksum. A chunk that is half written when the power goes is a chunk the server refuses to load, rather than one that loads with half a world in it.

Who gets in

The launcher is where accounts live: it has the passwords, the roles, the bans and the friends. The game server has none of that and does not want a second copy, so it asks the launcher whether a player is one of ours, and gets the ban list in the same answer.

This is the part Yggdrasil does in vanilla, done with our own launcher. The launcher already logs the player in and already hands the game a name, a uuid and a token; the game hands that token to the server, and the server asks the launcher about it. The token is used once and dropped.

The server's own accounts exist for a server of our own and for development. When a launcher is configured they are shut, because a server with two doors has no doors.

client                  game server                launcher
  |  Hello                   |                        |
  |------------------------->|                        |
  |  HelloAck                |                        |
  |<-------------------------|                        |
  |  LauncherAuth            |                        |
  |------------------------->|--- POST /auth/validate ->|
  |                          |<-- {valid, reason} -----|
  |  AuthAck                 |                        |
  |<-------------------------|                        |
  |  Login / LoginAck        |                        |
  |==========================>|                        |
  |  JoinWorld / WorldJoin   |                        |
  |<==========================|                        |
  |  ChunkData ...           |                        |
  |<==========================|                        |

Modding

Mods are jars with a descriptor at META-INF/rsxmod.properties:

id=rsxm-demo
name=RustNotMC Demo
version=1.1.0
env=CLIENT
entrypoint=net.rustnotmc.demo.DemoMod
access=hud,commands,files
depends.core=>=1.0

env is CLIENT, SERVER or BOTH. The loader runs on the client today; 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. 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/<id> 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

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

rsx-protocol\scripts\build.ps1        # dist\rsx-protocol.jar
rsx-protocol\scripts\test.ps1         # 22 checks
rsx-server-core\scripts\build.ps1     # dist\rsx-server-core.jar
rsx-server-core\scripts\test.ps1      # 49 checks
rsx-server-core\scripts\run-server.ps1

Only a JDK is required. No Gradle, no network once the mappings are cached.

Running the server

run-server.ps1 --port 25565 --world world --seed 0
run-server.ps1 --launcher-api https://launcher.example/api

--launcher-api points the server at the launcher's API. With it, the launcher decides who gets in and the server's own accounts are shut. --allow-local-accounts opens them again on purpose.

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/<aa>/<sha1>. A 1.12.2 client reads neither. It wants <assetsDir>/minecraft/<path>.

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.

The base from the remake

The item models, textures, sounds and the whole item database are taken from the Rustex remake as a temporary base. tools\extract-rustex-assets.py unpacks them into assets\rustex (1.65 GB, not in the repository) and decodes the one-byte XOR the remake uses. What we derived from that and keep in the repository:

File What
rsx-protocol\src\main\resources\items\items.csv 218 items: damage, range, tempo, durability, harvest per hit, material, icon, model
rsx-protocol\src\main\resources\items\sounds.csv 505 sound events with their files
rsx-protocol\src\main\resources\items\item-texts.json 543 item titles and descriptions

These numbers are the balance we build on, and they are the ones the official wiki contradicts (the wiki describes original Rust, the remake is faster). The item icons come from the atlas of the remake and are placeholders: replace them with our own.

Tools

Script Purpose
tools\extract-rustex-assets.py unpacks the assets of the Rustex remake and decodes them: every file is XOR-ed with one byte, the key comes from the format signature (PNG ^0x89, OGG ^0x4F, JSON ^0x7B)
tools\extract-items.py turns models/stacks.json of the remake into rsx-protocol\...\items\items.csv: 218 items with damage, range, tempo, durability, harvest per hit, material, icon, model
tools\extract-sounds.py the sound registry of the remake into sounds.csv: 505 events with their files
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<Integer>.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

Minecraft is a trademark of Mojang Studios. This project is not affiliated with or endorsed by Mojang or Microsoft. Game assets are not redistributed here.

S
Description
Tschhh... Do not go there
Readme 22 MiB
Languages
Java 87.5%
Python 8.5%
PowerShell 4%