Sign in Sign up
kretrod/lodestone Public
Branches
master
PreviewCode130 lines (100 loc) · 5.6 KB Raw
# Lodestone

An **external** cheat menu for Minecraft Java Edition — single player, for
learning how the JVM lays out its heap.

External means external. Lodestone is a separate process that never puts a
single byte of its own code inside the game:

- no mod, no Fabric/Forge entrypoint, no mixin
- no injected DLL, no `LoadLibrary`, no remote thread
- no JVMTI agent, no JDWP, no launch flags — the game runs completely stock

The only privileged calls it makes are `OpenProcess`, `ReadProcessMemory` and
`WriteProcessMemory`. Everything else is interpretation.

Verified against **Minecraft 26.2** (Fabric, Java 25) on Windows 11.

## The problem with an external for a Java game

A normal game trainer follows a static pointer chain: a fixed offset in the
main module, a few dereferences, a player struct. A JVM gives you none of that.
There are no exported symbols for game objects, the layout of a Java class is
decided at runtime, and the garbage collector *moves objects*, so any address
you write down is stale the moment a GC runs.

What HotSpot does give you is a description of itself.

## VMStructs: the VM describing its own C++ layout

`jvm.dll` exports a table that `jhsdb` and the Serviceability Agent use to
debug a live JVM from outside the process:

```
gHotSpotVMStructs            -> VMStructEntry[]  { typeName, fieldName, isStatic, offset, address }
gHotSpotVMTypes              -> VMTypeEntry[]    { typeName, superclassName, size }
gHotSpotVMStructEntryArrayStride, …Offset        -> how to walk the arrays above
```

Read that table and you know the byte offset of every field of every HotSpot
internal structure *for this exact build* — no hardcoded offsets, nothing to
update when the JDK changes. `lodestone probe` dumps it:

```
VMStructs: 581 fields, 332 types, 346 int consts, 97 long consts
compressed oops   base 0x0  shift 3
compressed klass  base 0x0  shift 0
Klass           _name              +24
InstanceKlass   _fieldinfo_stream  +448
```

## From there to `player.position.x`

```
ClassLoaderDataGraph::_head          static address, straight out of VMStructs
  -> ClassLoaderData::_klasses       linked list of Klass* via _next_link
     -> Klass::_name                 Symbol* -> "net/minecraft/client/Minecraft"
  -> InstanceKlass::_fieldinfo_stream UNSIGNED5 records -> field names + offsets
  -> Klass::_java_mirror             the java.lang.Class object: where statics live
     -> static field `instance`      the Minecraft singleton
        -> instance field `player`   LocalPlayer
           -> `position`             Vec3
              -> `x`                 a double, at a known offset
```

Two details make this work at all:

**Field names.** JDK 21 replaced the old `u2[]` field array with
`_fieldinfo_stream`, a stream of UNSIGNED5-packed records
(`name sig offset access flags Optionals(flags)`). Lodestone decodes it, then
resolves each name/signature index through the class's constant pool — except
for VM-injected fields, which index HotSpot's own `vmSymbols` table instead.

**Objects move, metadata does not.** `Klass` and the field stream live in
Metaspace and never move, so offsets can be cached forever. Object addresses
cannot: every tick re-walks from `Minecraft.instance`, a static field in a class
mirror, which is a stable root.

## What it will not do

**It never writes an object reference.** The JIT emits GC write barriers around
reference stores; forging one from outside without them can leave the collector
with a pointer it does not know about. Lodestone writes primitives only —
doubles, floats, ints, booleans — and refuses anything else. Teleporting works
by overwriting the components of the player's own `Vec3` in place, and it
refuses to do even that to a shared constant like `Vec3.ZERO`.

**It never writes in multiplayer.** Every write is gated on
`Minecraft.singleplayerServer != null`. Attached to a server, it reads and
shows state and nothing more. In single player the integrated server is in the
same JVM, so the trainer edits the authoritative `ServerPlayer` as well as the
client's — which is why flight and noclip stick instead of rubber-banding.

## Use

```
lodestone-menu.exe          the cheat menu (attaches on its own)

lodestone.exe procs                       find the game
lodestone.exe probe                       dump the VMStructs database
lodestone.exe classes minecraft/client    search 45k loaded classes
lodestone.exe class net/minecraft/client/Minecraft        fields + live statics
lodestone.exe get  net/minecraft/client/Minecraft instance player position y
lodestone.exe set  net/minecraft/client/Minecraft instance player abilities flying true
lodestone.exe find net/minecraft/client/Minecraft instance --of ClockState
lodestone.exe obj 0x715774258             identify and dump any object
lodestone.exe vmtype InstanceKlass        what HotSpot says about its own type
lodestone.exe trainer --fly --tp 100 80 100     headless engine driver
```

Path syntax is `<Class> <staticField> [field…]`, with `[n]` to index an array:

```
lodestone.exe get net/minecraft/client/Minecraft \
    instance singleplayerServer playerList players elementData [0] position y
```

Menu hotkeys: **F6** flight · **F7** noclip · **F8** walk speed · **F9** god mode.

## Layout

```
src/win.rs    OpenProcess + RPM/WPM, module list, PE export parsing. Nothing else.
src/vm.rs     the VMStructs table
src/jvm.rs    classes, objects, fields, UNSIGNED5, strings, arrays, HashMaps
src/game.rs   Minecraft's classes and the trainer engine
src/bin/lodestone.rs   CLI explorer
src/bin/menu.rs        egui menu
```

Cross-compiled from Linux: `cargo build --release --target x86_64-pc-windows-gnu`