Config, Code, and Crypto in One .mdix File
Documentation Site https://dixscript-docs.pages.dev
"I built this because I was tired of copy-pasting the same JSON config blocks 500 times. Turns out other people hate that too."
β Mid-D-Man, Creator
The Rust port is feature-complete, API stable, and dixscript v1.0.0 is now published. It's a fresh release with no real-world mileage yet β install it, use it in your own projects, and file issues for anything you hit. mdix-cli and the language wrappers below are still build-from-source until they get their own publish pass.
| Package | Status |
|---|---|
dixscript (core Rust library) |
β
Published on crates.io β dixscript = "1.0" |
mdix-cli |
β All commands implemented β build from source |
mdix-ffi / C# bindings |
β³ Bindings generated, packaging pending |
mdix-go |
β³ Header generated, Go wrapper pending |
mdix-java / mdix-python |
β³ Pending runtime wrappers |
mdix-wasm |
β³ Pending wasm-bindgen annotations |
mdix-c |
β³ Header stable, examples pending |
The C# prototype (https://github.com/Mid-D-Man/DixScript) remains the reference implementation for the language itself.
Started as: A quick hack to make a mobile game's remote config less painful.
The Problem: My game had weapons, camos, attainment missions, and shop data spread across Unity Remote Config as massive nested JSON blobs. Adding a single new field to a camo definition meant updating it in dozens of places. One typo and suddenly every weapon in the game was broken at runtime β with no error until the player hit that screen.
Here's a real slice of what that looked like:
{
"EquipableItemCamoClassId": "ALL_SMG_CAMOS_CONFIG",
"InventoryItemCamos": [
{
"MainItemId": "ALIYAHOO419",
"MainItemClass": "BASIC_SMG",
"CamoRaritySubClass": [
{
"RaritySubClassId": "Aliyahoo419_Basic_Camos",
"MainItemCamos": [
{
"CamoId": "ALIYAHOO419",
"CamoIndex": 0,
"CamoAvailableInSeason": "1",
"CamoRarity": "Basic",
"CamoAtlasSpriteName": "Aliyahoo419(Clone)",
"CamoInGameName": "Aliyahoo419",
"CamoType": "Sprite",
"MaterialAddress": "Null"
}
]
}
]
}
]
}This is just the camo config for one weapon class. Three separate blobs, hundreds of lines, all duplicating the same structure.
The Solution: "What if the shape of a camo was defined once, and I just filled in the data?"
The Result: The same config in DixScript:
@ENUMS(
WeaponClass { BASIC_SMG, RUNIC_SMG, LEGENDARY_SMG }
CamoRarity { Basic, Rare, Epic, Legendary, Runic }
CamoType { Sprite, SpriteAndMaterial }
)
@QUICKFUNCS(
~camo<object>(id, index<int>, rarity<enum>, sprite, inGameName, type<enum>) {
return {
CamoId = id
CamoIndex = index
CamoAvailableInSeason = "1"
CamoRarity = rarity
CamoAtlasSpriteName = $"{sprite}(Clone)"
CamoInGameName = inGameName
CamoType = type
MaterialAddress = "Null"
}
}
~rarityClass<object>(subClassId, camos) {
return { RaritySubClassId = subClassId, MainItemCamos = camos }
}
~weapon<object>(itemId, class<enum>, rarityClasses) {
return { MainItemId = itemId, MainItemClass = class, CamoRaritySubClass = rarityClasses }
}
)
@DATA(
EquipableItemCamoClassId = "ALL_SMG_CAMOS_CONFIG"
InventoryItemCamos::
weapon("ALIYAHOO419", WeaponClass.BASIC_SMG, [
rarityClass("Aliyahoo419_Basic_Camos", [
camo("ALIYAHOO419", 0, CamoRarity.Basic, "Aliyahoo419", "Aliyahoo419", CamoType.Sprite)
]),
rarityClass("Aliyahoo419_Epic_Camos", [
camo("ALIYAHOO419_HORIZON", 0, CamoRarity.Rare, "Aliyahoo419_Horizon", "Aliyahoo419-Horizon", CamoType.Sprite),
camo("ALIYAHOO419_ROSE", 1, CamoRarity.Epic, "Aliyahoo419_Rose", "Aliyahoo419-Rose", CamoType.Sprite)
])
])
)
| Config | JSON (formatted) | DixScript | Reduction |
|---|---|---|---|
ALL_SMG_CAMOS_CONFIG |
~350 lines | ~110 lines | 69% |
ALL_SMG_EQ_AND_CAMO_ATTAINMENT_CONFIGS |
~280 lines | ~90 lines | 68% |
ALL_SMG_UNIQUE_CAMOS_MISSIONS_CONFIG |
~230 lines | ~75 lines | 67% |
| All 3 configs combined | ~860 lines | ~275 lines | ~68% |
DixScript is a data interchange format that combines:
- π¦ Configuration (like TOML)
- π§ Compile-time functions (like Jsonnet, but less cryptic)
- π Built-in encryption (AES-256-GCM, not an afterthought)
- ποΈ Automatic compression (gzip/bzip2/lzma)
- π Type safety (enums, strong typing when you want it)
- π― Zero runtime dependencies (pure Rust, or C# in the original)
All in one file with a .mdix extension.
Modern projects have config sprawl: /config βββ base.json # 500 lines βββ development.json # 300 lines (80% duplicated from base) βββ production.json # 400 lines (90% duplicated) βββ secrets.env # Separate encryption βββ validation.js # Separate validation logic βββ build.sh # Compresses everything βββ deploy.yaml # References all of the above You change one setting β Update 3 files β Run 2 scripts β Hope you didn't break something.
@DLM(DCompressor.gzip, DEncryptor.aes256)
@QUICKFUNCS(
~createEnemy<object>(name, health, damage) {
return {
name = name,
health = health,
damage = damage,
armor = health / 10,
xp = health / 2,
gold = health / 4
}
}
)
@DATA(
environment<enum> = Environment.PROD
enemies::
createEnemy("Goblin", 50, 10),
createEnemy("Orc", 100, 20),
createEnemy("Troll", 200, 40)
)
@SECURITY(
encryption -> { mode = "keyfile", algorithm = "aes256-gcm" }
)
One file. Config, logic, encryption, compression. Done.
Write logic once, execute at compile time. No runtime overhead.
@QUICKFUNCS(
~calculateDamage<int>(base, difficulty<enum>) {
multiplier = difficulty == Difficulty.HARD ? 2.0 : 1.0
return Math.round(base * multiplier)
}
)
@DATA(
easy_enemy = { damage = calculateDamage(50, Difficulty.EASY) },
hard_enemy = { damage = calculateDamage(50, Difficulty.HARD) }
)
@DATA(
// Flat properties (single equals)
app_name = "MyApp"
version = "1.0.0"
port = 8080
// Table properties (single colon)
server: host = "localhost", port = 8080, ssl = true
// Group arrays (double colon)
admins:: "alice", "bob", "charlie"
)
Commas are optional between entries. Use horizontal style with commas or vertical style without β your choice.
@DLM(DCompressor.gzip, DEncryptor.aes256)
@SECURITY(
encryption -> { mode = "password" }
)
@DATA(
api_key = "super_secret_key"
)
Compile: mdix compile secrets.mdix --password
Output: secrets.mdix.enc (compressed + encrypted)
@ENUMS(
LogLevel { DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3 }
Environment { DEV = 1, STAGING = 2, PROD = 3 }
)
@DATA(
log_level<enum> = LogLevel.INFO
current_env<enum> = Environment.PROD
)
@DATA(
// Inferred
count = 42
price = 19.99
enabled = true
// Explicit
max_users<int> = 1000
tax_rate<float> = 0.15f
color<hex> = #FF5733
avatar = b:("base64data...")
email_regex = r:("^[a-z@.]+$")
release_date = 2025-12-31
)
| Feature | JSON | YAML | TOML | Jsonnet | DixScript |
|---|---|---|---|---|---|
| Deduplication via functions | β | β | β | β | β |
| Built-in encryption | β | β | β | β | β |
| Built-in compression | β | β | β | β | β |
| Enums | β | β | β | β | |
| Optional commas | β | β | β | β | β |
| Compile-time execution | β | β | β | β | β |
| Zero runtime dependencies | β | β | β | β | β |
| Human-readable | β | β | β | β |
@CONFIG( // Compiler settings, metadata
version -> "1.0.0"
)
@IMPORTS( // Import from other .mdix files
Utils from "common/utils.mdix"
)
@DLM( // Data Lifecycle Modules β compression + encryption
DCompressor.gzip
DEncryptor.aes256
)
@ENUMS( // Named constants
Status { ACTIVE, INACTIVE, PENDING }
)
@QUICKFUNCS( // Compile-time functions
~calculate<int>(x, y) {
return x + y
}
)
@DATA( // Your actual data
result = calculate(10, 20)
)
@SECURITY( // Security configuration
encryption -> { mode = "password" }
)
All sections are optional. Use what you need.
Original: Written in C# (.NET 8), fully functional, available at https://github.com/Mid-D-Man/DixScript.
This Repo: Rust port β feature-complete, API stable, actively tested via CI.
| Component | Status | Notes |
|---|---|---|
| Utilities | β Complete | Logger, keywords, helpers |
| ErrorManager | β Complete | All 10 error types |
| Lexer | β Complete | All token types |
| Parser | β Complete | All 6 sections |
| Semantic Analyzer | β Complete | All 8 analysis phases |
| QuickFuncs Resolver | β Complete | Full function interpreter |
| Binary Serialization | β Complete | Packer + Unpacker |
| DLM Pipeline | β Complete | Forward and reverse |
| Runtime API | β Complete | Load, access, build, convert |
| LSP Server | β Complete | Full IDE integration |
| CLI | β Complete | All commands |
| FFI / C | β Complete | C header + 40+ exported functions |
All wrappers bind to the Rust runtime via FFI. The core is complete β wrappers are pending packaging and publishing.
| Package | Language | Status |
|---|---|---|
mdix-ffi + C# NuGet |
C# / Unity | β³ Bindings generated, NuGet packaging pending |
mdix-go |
Go | β³ C header generated, Go wrapper pending |
mdix-java |
Java / Kotlin | β³ Pending JNI wrappers |
mdix-python |
Python | β³ Pending PyO3 wrappers |
mdix-wasm |
JS / Browser | β³ Pending wasm-bindgen annotations |
mdix-c |
C / C++ | β³ Header stable, examples pending |
Why Rust?
- π Performance: The C# prototype is fast. Rust is faster.
- π§ Portability: Native binaries, WASM, embedded.
- π¦ Safety: Ownership model catches bugs at compile time.
- π FFI: One Rust core, wrappers for every major language.
cargo add dixscriptor in Cargo.toml:
[dependencies]
dixscript = "1.0"use dixscript::Runtime::DixLoader;
use dixscript::Runtime::DixLoadOptions;
fn main() {
let loader = DixLoader::new();
let data = loader.load_text("config.mdix", &DixLoadOptions::new()).unwrap();
let port: i32 = data.get("server.port").unwrap_or(8080);
println!("Server on port {}", port);
}Not yet published to crates.io β build from source:
git clone https://github.com/Mid-D-Man/DixScript-Rust
cd DixScript-Rust
cargo build -p mdix-cli --release
mdix validate config.mdix
mdix compile config.mdix
mdix compile secrets.mdix --password
mdix convert config.json --to mdix
mdix inspect config.mdix --keysGreat for: Game data configs (weapons, items, enemies, levels), multi-environment server configs, encrypted secrets bundles, any schema where you're copy-pasting structure repeatedly.
Maybe not for: Tiny configs under 50 lines with no repetition, simple key-value stores (TOML is simpler), situations requiring maximum existing tooling support.
The rule of thumb: If changing one field currently means editing it in more than three places, DixScript will help.
Contributions welcome. The Rust port is feature-complete β areas where help is most useful: language wrapper packaging (C#, Go, Python), LSP editor extensions, documentation, and test coverage.
Code style: Follow rustfmt.toml in the repo.
- API docs:
https://docs.rs/dixscript - Grammar spec:
others/midx.ebnf - C# reference implementation:
https://github.com/Mid-D-Man/DixScript - CI results:
https://mid-d-man.github.io/DixScript-Rust/
MIT β use it however you want, commercial or personal. See LICENSE.
Creator: Mid-D-Man
GitHub: https://github.com/Mid-D-Man/DixScript-Rust
Original (C#): https://github.com/Mid-D-Man/DixScript
Questions? Found a bug? Open an issue.
"Config files shouldn't require a PhD to maintain." β The DixScript Philosophy