Reads and writes the binary formats RPG Maker 2000 and 2003 use: LDB (database), LMU (map), LMT (map tree) and LSD (save). A from-scratch Rust rewrite of liblcf, built around three goals: run free-standing under no_std, be perfectly round-trip, be BLAZINGLY 🦀 FAST 🔥🔥
Round-trip correctness is verified byte-for-byte against real RPG Maker games - see Verification below.
This crate has some ports:
cargo add rm2k-lib| Feature | Pulls in | Gives you |
|---|---|---|
| (none) | nothing | The wire codec, chunk framing, and allocation-free event iteration. Runs on no_std with no allocator at all. |
alloc |
alloc |
Typed structs - Database, Map, TreeMap, Save - and loading/saving them. |
std (default) |
std |
IoSink over std::io::Write. |
serde |
alloc, serde |
Serialize/Deserialize on every one of the 69 schema structs. |
use rm2k::file;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let bytes = std::fs::read("RPG_RT.ldb")?;
let loaded = file::load_database(&bytes)?;
for actor in loaded.value.actors.iter() {
println!("{}", actor.name.as_utf8().unwrap_or("<non-UTF-8 name>"));
}
Ok(())
}This crate never transcodes text, and never guesses an encoding. RPG Maker 2k/3 stores strings in whatever legacy codepage the authoring machine used - Shift-JIS for Japanese games, CP1252 for western ones - and an LCF file contains no marker recording which. So strings come out as &[u8], exactly as they sit in the file. Decode with encoding_rs, iconv, ICU uconv, Microsoft's ICU or whatever you prefer once you know the codepage, typically from RPG_RT.ini's [EasyRPG] Encoding key.
Real files are frequently malformed and RPG Maker itself loads them anyway, so this crate does too. The difference is that recoveries are reported: pass a DiagSink and you get a structured list of what was wrong and where. Pass StrictSink to turn the first problem into an error.
Chunks this schema does not know are kept, not dropped. RPG Maker's forward-compatibility story is "write chunks the reader may not understand", so real files carry ids from later RPG_RT builds, EasyRPG extensions and third-party editors. Each one is stored verbatim in its struct's unknown_chunks, anchored next to the last field that was recognised, and written back in that position - so byte-exactness does not depend on the schema being complete. They are still reported as DiagKind::UnknownChunk; preserved is not understood.
| Example | What it shows |
|---|---|
examples/fixture_check.rs |
Byte-exact round-trip checking against real game files on disk. |
examples/dump_text.rs |
Extracting every displayed string using only the allocation-free core tier. |
examples/to_yaml.rs |
Converting a database to YAML with the serde tier (cargo run --example to_yaml --features serde -- game.ldb). |
crates/rm2k-capi/examples/to_json.c |
Converting a database's named lists to JSON from C (via yyjson, vendored next to the example), using only the C API's owned/"alloc" tier. |
Each example has its own README with usage, output semantics, and (for the C ones) build steps: examples/README.md for the Rust examples, crates/rm2k-capi/examples/README.md for to_json.c. Run a Rust example with cargo run --release --example <name> [--features ...] -- <args>; the C example is not wired into Cargo.
examples/fixture_check.rs (usage) loads every .ldb/.lmu/.lmt/.lsd file in a directory, writes it straight back out, and compares bytes. Run against three real, unmodified RPG Maker games (RPG Maker 2000 and 2003, 419 files across all four formats): 419/419 byte-exact, zero diagnostics. The C API gets the same treatment independently in crates/rm2k-capi/tests/test_ffi.c (build steps), round-tripping a real 262 KB database through the FFI boundary byte-for-byte.
The games in question:
benches/ has a real, measured comparison against liblcf: parsing RPG_RT.ldb is ~6.2x faster, and parsing a .lmu map file is ~8.8x faster (AMD Ryzen 5 2600, in-memory parse only, see benches/README.md for the full methodology, build steps, and raw numbers). Not that it matters much, the goal was not to surpass liblcf by performance or something.
- Zero-copy core tier. Parsing borrows slices out of the input buffer; nothing is copied unless you ask for it (e.g. via
IntoOwned::into_owned()). - Cleaner codebase. No legacy C++ shit. Boilerplate enums are generated by declarative
macro_rules!macros, not some external Python pipelines. Setupis opt-in, not automatic.RPG_RT's post-load fixups (resolving-1sentinels, padding stat curves) change the byte representation, so running them automatically on load would make a load/save cycle non-idempotent. CallSetup::setupexplicitly when you want them.- Diagnostics instead of a global error string. Recoverable problems are reported through a
DiagSinkyou provide, not a process-global mutable buffer. - Round-trip fidelity does not depend on schema completeness. Every struct keeps the chunks no field claimed and re-emits them where they were found, so a file using ids this schema has never seen still round-trips byte-for-byte. See Leniency.
Most of the RPG Maker 2k/3 format handling was written referencing liblcf, a part of the EasyRPG project, a MIT-licensed library. However, the implementation that handles the format itself is very different from liblcf, both because Rust enforces different idioms and because the logic was made with free-standing usage in mind. Credit to the EasyRPG project for that work.
yyjson (yyjson.h/yyjson.c, MIT licensed) is vendored in crates/rm2k-capi/examples/yyjson for the to_json.c example, its license is alongside it.
Me, the maintainer of this project, is a poor college student from Eastern Europe.
If you could, please consider supporting us through:
Even if you don't, it's fine. We'll continue to do as we right now.
Project is licensed under WTFPL.