chore: add reusable version adaptation scripts

Add tools/ directory with Python scripts for comparing Minecraft version
registries and generating MCC palette files:

- diff_registries.py: Compare Items/EntityTypes/Blocks/DataComponents/
  EntityDataSerializers between two decompiled MC versions, reporting
  which palettes need updating with ID shift analysis.
- gen_item_palette.py: Generate ItemPaletteXXX.cs from Items.java field
  declaration order, with name validation against ItemType.cs.
- gen_entity_metadata_palette.py: Generate EntityMetadataPaletteXXX.cs
  from EntityDataSerializers.java registration order.
- README.md: Usage documentation for all scripts.

Made-with: Cursor
This commit is contained in:
BruceChen 2026-03-20 01:49:14 +08:00
parent e1cb18c6f8
commit 7609832976
6 changed files with 667 additions and 0 deletions

View file

@ -0,0 +1,130 @@
---
name: mcc-version-adaptation
description: Adapt MCC palettes and protocol handling for a new Minecraft version. Use when the user wants to add support for a new MC version, compare version registries, update item/entity/block/metadata palettes, or fix protocol mismatches between MC versions.
---
# MCC Version Adaptation
Systematic workflow for updating Minecraft Console Client to support a new Minecraft version, focusing on palette/registry changes and entity metadata.
## Prerequisites
- Decompiled server source for both the old and new MC versions in `$MCC_REPO/MinecraftOfficial/<version>-decompiled/`
- If missing, decompile first:
```bash
cd $MCC_REPO/MinecraftOfficial
java -jar MinecraftDecompiler.jar --version <ver> --side SERVER \
--decompile --output <ver>-remapped.jar --decompiled-output <ver>-decompiled
```
## Step 1: Run Registry Diff
```bash
python3 $MCC_REPO/tools/diff_registries.py <old_ver> <new_ver>
```
This compares five registries and reports which need palette updates:
| Registry | MCC File | When to Update |
|----------|----------|----------------|
| Items.java | `ItemPalettes/ItemPaletteXXX.cs` | New/removed/reordered items |
| EntityType.java | `EntityPalettes/EntityPaletteXXX.cs` | New/removed/reordered entity types |
| Blocks.java | `BlockPalettes/BlockPaletteXXX.cs` | New/removed/reordered blocks |
| DataComponents.java | `StructuredComponents/StructuredComponentsRegistryXXX.cs` | New/reordered components |
| EntityDataSerializers.java | `EntityMetadataPalettes/EntityMetadataPaletteXXX.cs` | New/reordered serializer types |
## Step 2: Generate Updated Palettes
For registries marked "PALETTE UPDATE NEEDED":
### Item Palette
```bash
python3 $MCC_REPO/tools/gen_item_palette.py <new_ver> <suffix>
# e.g., gen_item_palette.py 1.21.1 121
```
- If new items are reported missing from `ItemType.cs`, add them to the enum in alphabetical order.
- The script auto-generates the C# palette file.
### Entity Metadata Palette
```bash
python3 $MCC_REPO/tools/gen_entity_metadata_palette.py <new_ver> <suffix>
# e.g., gen_entity_metadata_palette.py 1.20.6 1206
```
- If new serializer types appear as UNMAPPED, add them to both:
1. The script's `FIELD_TO_ENUM` dictionary
2. MCC's `EntityMetaDataType.cs` enum
3. `DataTypes.cs` read logic (add a `case` to consume the correct bytes)
### Entity/Block Palettes
No generator script yet — these change rarely. When needed, manually create by following the pattern of existing palette files, using `register("name", ...)` call order from the decompiled source.
### DataComponents / StructuredComponents
Compare `DataComponents.java` registration order. If new components appear, update `StructuredComponentsRegistryXXX.cs`. For new component types, implement corresponding reader in `StructuredComponents/Components/`.
## Step 3: Update Version Routing
After creating palette files, update version selection logic:
| Palette Type | Routing Location |
|-------------|-----------------|
| Item | `Protocol18.cs``itemPalette` switch expression |
| Entity | `Protocol18.cs``entityPalette` switch expression |
| Block | `Protocol18.cs``blockPalette` initialization |
| EntityMetadata | `EntityMetadataPalette.cs``GetPalette()` switch |
| DataComponents | `StructuredComponentsRegistry.cs` → factory/routing |
Pattern: add a new `>= MC_X_Y_Z_Version => new XxxPaletteXYZ()` case.
## Step 4: Check Variant Encoding Changes
For entity types that use variant serializers (Cat, Wolf, Frog, Painting), check if the codec changed between versions by inspecting:
- `EntityDataSerializers.java` — look at how each `*_VARIANT` field is constructed
- Key codecs:
- `ByteBufCodecs.holderRegistry()` → wire format: `VarInt(registry_id)`
- `ByteBufCodecs.holder()` → wire format: `VarInt(id+1)` for registered, `VarInt(0) + inline_data` for direct
- If codec changed, update `DataTypes.cs` entity metadata reading logic accordingly.
## Step 5: Handle New EntityDataSerializer Types
When new serializer types are added (detected in Step 1):
1. Add enum value to `EntityMetaDataType.cs` with XML doc comment
2. Add read logic in `DataTypes.cs` `ReadNextMetadata()`:
- Determine byte consumption from the decompiled codec
- Examples: VarInt read, list of particles, etc.
3. Create the new palette file (Step 2)
4. Update palette routing (Step 3)
## Step 6: Compile and Verify
```bash
dotnet build $MCC_REPO/MinecraftClient.sln -c Release
```
Then connect to a test server of the target version (see `mcc-dev-workflow` skill) and verify:
- Successful connection
- `/give` new items → check inventory
- Summon entities (especially variant types) → no metadata parse errors
- Particle effects → no crashes
## Key Source Files Reference
| Decompiled Java Source | Purpose |
|----------------------|---------|
| `world/item/Items.java` | Item registry (field declaration order = ID) |
| `world/entity/EntityType.java` | Entity type registry (`register()` call order = ID) |
| `world/level/block/Blocks.java` | Block registry (`register()` call order = ID) |
| `core/component/DataComponents.java` | Data component registry |
| `network/syncher/EntityDataSerializers.java` | Entity metadata type registry (static block order = ID) |
## Common Pitfalls
- **ID order matters**: IDs are determined by declaration/registration order, not alphabetical. Always use the decompiled source as ground truth.
- **Cross-version jumps**: When MCC skips versions (e.g., 1.20.4→1.20.6), registries from ALL intermediate versions may have changed. Always diff against the actual last-supported version, not the latest palette.
- **EntityMetadata type shifts**: A single new serializer type shifts all subsequent IDs, causing widespread metadata parse failures. Symptoms: entity rendering glitches, disconnections, or silent data corruption.
- **CUT_STANDSTONE_SLAB**: This is an intentional typo in Minecraft source (should be SANDSTONE). MCC's `ItemType.cs` uses `CutSandstoneSlab` — the gen script handles this via the OVERRIDES dict.
## Reusable Scripts
All scripts are in `$MCC_REPO/tools/`. See `tools/README.md` for detailed usage.

3
.gitignore vendored
View file

@ -387,6 +387,9 @@ FodyWeavers.xsd
!.vscode/extensions.json
*.code-workspace
# Cursor files
!.cursor/
# Local History for Visual Studio Code
.history/

47
tools/README.md Normal file
View file

@ -0,0 +1,47 @@
# MCC Version Adaptation Tools
Scripts for analyzing Minecraft version differences and generating MCC palette files.
Requires: Python 3.10+, decompiled MC server source in `MinecraftOfficial/<version>-decompiled/`.
## Decompiling a new MC version
```bash
cd MinecraftOfficial
java -jar MinecraftDecompiler.jar --version 1.21.4 --side SERVER \
--decompile --output 1.21.4-remapped.jar --decompiled-output 1.21.4-decompiled
```
## diff_registries.py — Compare registries between versions
Compares Items, EntityTypes, Blocks, DataComponents, and EntityDataSerializers between two MC versions. Reports whether each palette needs updating, lists added/removed entries, and shows ID shift statistics.
```bash
python3 tools/diff_registries.py 1.20.6 1.21.1
```
Output indicates for each registry:
- **IDENTICAL** → reuse existing palette
- **PALETTE UPDATE NEEDED** → create new palette file + update version routing
## gen_item_palette.py — Generate ItemPalette C# file
Reads `Items.java` field declaration order to generate a complete `ItemPaletteXXX.cs`.
```bash
python3 tools/gen_item_palette.py 1.21.1 121
# → MinecraftClient/Inventory/ItemPalettes/ItemPalette121.cs
```
Also validates each item name against `ItemType.cs` and warns about missing enum values.
## gen_entity_metadata_palette.py — Generate EntityMetadataPalette C# file
Reads `EntityDataSerializers.java` static block registration order to generate `EntityMetadataPaletteXXX.cs`.
```bash
python3 tools/gen_entity_metadata_palette.py 1.20.6 1206
# → MinecraftClient/Mapping/EntityMetadataPalettes/EntityMetadataPalette1206.cs
```
The script maps Java field names to MCC's `EntityMetaDataType` enum. If a new serializer type appears that isn't in the mapping table, it will warn you to update both the script's `FIELD_TO_ENUM` dict and MCC's `EntityMetaDataType.cs` enum.

221
tools/diff_registries.py Normal file
View file

@ -0,0 +1,221 @@
#!/usr/bin/env python3
"""
Compare Minecraft registry data between two decompiled server versions.
Compares Items, EntityTypes, Blocks, DataComponents, and EntityDataSerializers
to determine which MCC palettes need updating for a new MC version.
Usage:
python3 tools/diff_registries.py <old_version> <new_version>
Example:
python3 tools/diff_registries.py 1.20.6 1.21.1
"""
import re
import sys
import os
from pathlib import Path
DECOMPILED_ROOT = Path(__file__).resolve().parent.parent / "MinecraftOfficial"
def find_java_file(version_dir: Path, *possible_paths: str) -> Path | None:
for p in possible_paths:
full = version_dir / p
if full.exists():
return full
return None
def extract_field_names(filepath: Path, pattern: str) -> list[str]:
"""Extract field names from public static final declarations."""
results = []
with open(filepath) as f:
for line in f:
m = re.match(pattern, line)
if m:
results.append(m.group(1))
return results
def extract_register_multiline(filepath: Path) -> list[str]:
"""Extract register("name", ...) calls, handling multiline Java formatting."""
with open(filepath) as f:
content = f.read()
flat = re.sub(r'\s+', ' ', content)
return re.findall(r'(?:= |return )register\(\s*"([^"]+)"', flat)
def extract_static_register_order(filepath: Path) -> list[str]:
"""Extract registerSerializer(FIELD_NAME) calls from the static {} block."""
results = []
in_static = False
with open(filepath) as f:
for line in f:
if 'static {' in line:
in_static = True
continue
if in_static and 'registerSerializer(' in line:
m = re.search(r'registerSerializer\((\w+)\)', line)
if m:
results.append(m.group(1))
if in_static and '}' in line and 'registerSerializer' not in line:
break
return results
def compare_lists(old: list[str], new: list[str], label: str):
"""Compare two ordered lists and report differences."""
set_old, set_new = set(old), set(new)
added = sorted(set_new - set_old)
removed = sorted(set_old - set_new)
print(f"\n{'='*60}")
print(f" {label}")
print(f"{'='*60}")
print(f" Old: {len(old)} entries, New: {len(new)} entries")
if not added and not removed:
if old == new:
print(f" Result: IDENTICAL — no palette update needed")
else:
print(f" Result: Same set but DIFFERENT ORDER — palette update needed!")
for i, (a, b) in enumerate(zip(old, new)):
if a != b:
print(f" First diff at index {i}: old={a}, new={b}")
break
return False
if added:
print(f" Added ({len(added)}): {added}")
for item in added:
idx = new.index(item)
prev_name = new[idx - 1] if idx > 0 else "(start)"
next_name = new[idx + 1] if idx < len(new) - 1 else "(end)"
print(f" \"{item}\" at index {idx}, between \"{prev_name}\" and \"{next_name}\"")
if removed:
print(f" Removed ({len(removed)}): {removed}")
common_old = [x for x in old if x in set_new]
common_new = [x for x in new if x in set_old]
if common_old != common_new:
print(f" Common items REORDERED — palette update needed!")
else:
print(f" Common items have same relative order")
# ID shift analysis
id_old = {name: i for i, name in enumerate(old)}
id_new = {name: i for i, name in enumerate(new)}
shifted = [(n, id_old[n], id_new[n]) for n in sorted(set_old & set_new) if id_old[n] != id_new[n]]
if shifted:
from collections import Counter
deltas = Counter(new_id - old_id for _, old_id, new_id in shifted)
print(f" {len(shifted)} entries with changed IDs, delta distribution: {sorted(deltas.items())}")
print(f" Result: PALETTE UPDATE NEEDED")
return True
def diff_items(old_dir: Path, new_dir: Path):
old_f = find_java_file(old_dir, "net/minecraft/world/item/Items.java")
new_f = find_java_file(new_dir, "net/minecraft/world/item/Items.java")
if not old_f or not new_f:
print(" [SKIP] Items.java not found")
return
pattern = r'\s+public static final Item (\w+)\s*='
old = extract_field_names(old_f, pattern)
new = extract_field_names(new_f, pattern)
compare_lists(old, new, "Items.java (Item registry)")
def diff_entity_types(old_dir: Path, new_dir: Path):
old_f = find_java_file(old_dir, "net/minecraft/world/entity/EntityType.java")
new_f = find_java_file(new_dir, "net/minecraft/world/entity/EntityType.java")
if not old_f or not new_f:
print(" [SKIP] EntityType.java not found")
return
old = extract_register_multiline(old_f)
new = extract_register_multiline(new_f)
compare_lists(old, new, "EntityType.java (Entity registry)")
def diff_blocks(old_dir: Path, new_dir: Path):
old_f = find_java_file(old_dir, "net/minecraft/world/level/block/Blocks.java")
new_f = find_java_file(new_dir, "net/minecraft/world/level/block/Blocks.java")
if not old_f or not new_f:
print(" [SKIP] Blocks.java not found")
return
old = extract_register_multiline(old_f)
new = extract_register_multiline(new_f)
compare_lists(old, new, "Blocks.java (Block registry)")
def diff_data_components(old_dir: Path, new_dir: Path):
old_f = find_java_file(old_dir, "net/minecraft/core/component/DataComponents.java")
new_f = find_java_file(new_dir, "net/minecraft/core/component/DataComponents.java")
if not old_f or not new_f:
print(" [SKIP] DataComponents.java not found")
return
old = extract_register_multiline(old_f)
new = extract_register_multiline(new_f)
needs_update = compare_lists(old, new, "DataComponents.java (StructuredComponents registry)")
if needs_update or True:
print("\n Registration order (new version):")
for i, name in enumerate(new):
marker = " <-- NEW" if name not in set(old) else ""
print(f" {i}: {name}{marker}")
def diff_entity_data_serializers(old_dir: Path, new_dir: Path):
old_f = find_java_file(old_dir, "net/minecraft/network/syncher/EntityDataSerializers.java")
new_f = find_java_file(new_dir, "net/minecraft/network/syncher/EntityDataSerializers.java")
if not old_f or not new_f:
print(" [SKIP] EntityDataSerializers.java not found")
return
old = extract_static_register_order(old_f)
new = extract_static_register_order(new_f)
needs_update = compare_lists(old, new, "EntityDataSerializers.java (EntityMetadata palette)")
print("\n Registration order (new version):")
for i, name in enumerate(new):
marker = " <-- NEW" if name not in set(old) else ""
print(f" {i}: {name}{marker}")
def main():
if len(sys.argv) != 3:
print(__doc__)
sys.exit(1)
old_ver, new_ver = sys.argv[1], sys.argv[2]
old_dir = DECOMPILED_ROOT / f"{old_ver}-decompiled"
new_dir = DECOMPILED_ROOT / f"{new_ver}-decompiled"
for d, v in [(old_dir, old_ver), (new_dir, new_ver)]:
if not d.exists():
print(f"Error: {d} not found. Decompile {v} first:")
print(f" cd MinecraftOfficial && java -jar MinecraftDecompiler.jar "
f"--version {v} --side SERVER --decompile "
f"--output {v}-remapped.jar --decompiled-output {v}-decompiled")
sys.exit(1)
print(f"Comparing MC {old_ver}{new_ver}")
print(f"Old: {old_dir}")
print(f"New: {new_dir}")
diff_items(old_dir, new_dir)
diff_entity_types(old_dir, new_dir)
diff_blocks(old_dir, new_dir)
diff_data_components(old_dir, new_dir)
diff_entity_data_serializers(old_dir, new_dir)
print(f"\n{'='*60}")
print(" Summary")
print(f"{'='*60}")
print(" Review each section above. For any marked 'PALETTE UPDATE NEEDED',")
print(" create a new palette file in MCC and update the version routing.")
print(" For 'IDENTICAL' sections, the existing palette can be reused.")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,142 @@
#!/usr/bin/env python3
"""
Generate an MCC EntityMetadataPalette C# file from decompiled EntityDataSerializers.java.
Reads the static {} block registration order to determine serializer IDs,
then maps Java field names to MCC's EntityMetaDataType enum values.
Usage:
python3 tools/gen_entity_metadata_palette.py <mc_version> <class_suffix>
Example:
python3 tools/gen_entity_metadata_palette.py 1.20.6 1206
# Generates EntityMetadataPalette1206.cs
"""
import re
import sys
from pathlib import Path
DECOMPILED_ROOT = Path(__file__).resolve().parent.parent / "MinecraftOfficial"
OUTPUT_DIR = (Path(__file__).resolve().parent.parent /
"MinecraftClient" / "Mapping" / "EntityMetadataPalettes")
# Java field name → MCC EntityMetaDataType enum name
FIELD_TO_ENUM = {
"BYTE": "Byte",
"INT": "VarInt",
"LONG": "VarLong",
"FLOAT": "Float",
"STRING": "String",
"COMPONENT": "Chat",
"OPTIONAL_COMPONENT": "OptionalChat",
"ITEM_STACK": "Slot",
"BOOLEAN": "Boolean",
"ROTATIONS": "Rotation",
"BLOCK_POS": "Position",
"OPTIONAL_BLOCK_POS": "OptionalPosition",
"DIRECTION": "Direction",
"OPTIONAL_UUID": "OptionalUuid",
"BLOCK_STATE": "BlockId",
"OPTIONAL_BLOCK_STATE": "OptionalBlockId",
"COMPOUND_TAG": "Nbt",
"PARTICLE": "Particle",
"PARTICLES": "Particles",
"VILLAGER_DATA": "VillagerData",
"OPTIONAL_UNSIGNED_INT": "OptionalVarInt",
"POSE": "Pose",
"CAT_VARIANT": "CatVariant",
"WOLF_VARIANT": "WolfVariant",
"FROG_VARIANT": "FrogVariant",
"OPTIONAL_GLOBAL_POS": "OptionalGlobalPosition",
"PAINTING_VARIANT": "PaintingVariant",
"SNIFFER_STATE": "SnifferState",
"ARMADILLO_STATE": "ArmadilloState",
"VECTOR3": "Vector3",
"QUATERNION": "Quaternion",
}
def extract_static_register_order(filepath: Path) -> list[str]:
results = []
in_static = False
with open(filepath) as f:
for line in f:
if 'static {' in line:
in_static = True
continue
if in_static and 'registerSerializer(' in line:
m = re.search(r'registerSerializer\((\w+)\)', line)
if m:
results.append(m.group(1))
if in_static and '}' in line and 'registerSerializer' not in line:
break
return results
def main():
if len(sys.argv) != 3:
print(__doc__)
sys.exit(1)
mc_version = sys.argv[1]
class_suffix = sys.argv[2]
version_dir = DECOMPILED_ROOT / f"{mc_version}-decompiled"
eds_java = version_dir / "net" / "minecraft" / "network" / "syncher" / "EntityDataSerializers.java"
if not eds_java.exists():
print(f"Error: {eds_java} not found")
sys.exit(1)
fields = extract_static_register_order(eds_java)
print(f"Found {len(fields)} entity data serializers in MC {mc_version}:")
unmapped = []
mappings = []
for i, field in enumerate(fields):
if field in FIELD_TO_ENUM:
enum_name = FIELD_TO_ENUM[field]
mappings.append((i, enum_name))
print(f" {i}: {field} -> EntityMetaDataType.{enum_name}")
else:
unmapped.append((i, field))
print(f" {i}: {field} -> ??? UNMAPPED")
if unmapped:
print(f"\nWARNING: {len(unmapped)} unmapped fields:")
for idx, field in unmapped:
print(f" [{idx}] {field}")
print("\nAdd entries to FIELD_TO_ENUM in this script and to EntityMetaDataType.cs enum.")
class_name = f"EntityMetadataPalette{class_suffix}"
output_path = OUTPUT_DIR / f"{class_name}.cs"
lines = [
"using System.Collections.Generic;",
"",
f"namespace MinecraftClient.Mapping.EntityMetadataPalettes;",
"",
f"public class {class_name} : EntityMetadataPalette",
"{",
" private readonly Dictionary<int, EntityMetaDataType> entityMetadataMappings = new()",
" {",
]
for idx, enum_name in mappings:
lines.append(f" {{ {idx}, EntityMetaDataType.{enum_name} }},")
lines += [
" };",
"",
" public override Dictionary<int, EntityMetaDataType> GetEntityMetadataMappingsList()",
" {",
" return entityMetadataMappings;",
" }",
"}",
"",
]
output_path.write_text("\n".join(lines))
print(f"\nGenerated {output_path} with {len(mappings)} mappings")
if __name__ == "__main__":
main()

124
tools/gen_item_palette.py Normal file
View file

@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""
Generate an MCC ItemPalette C# file from decompiled Items.java.
Reads public static final Item field declarations (which define item IDs
by declaration order) and generates a complete C# palette class.
Usage:
python3 tools/gen_item_palette.py <mc_version> <class_suffix>
Example:
python3 tools/gen_item_palette.py 1.21.1 121
# Generates ItemPalette121.cs
The <class_suffix> determines the class name (ItemPalette<suffix>) and
should match MCC's naming convention (e.g., 121 for 1.21, 1206 for 1.20.6).
"""
import re
import sys
from pathlib import Path
DECOMPILED_ROOT = Path(__file__).resolve().parent.parent / "MinecraftOfficial"
OUTPUT_DIR = Path(__file__).resolve().parent.parent / "MinecraftClient" / "Inventory" / "ItemPalettes"
# Java field name → C# ItemType enum name
# Most conversions are automatic (SCREAMING_SNAKE → PascalCase).
# Add manual overrides here for irregular names.
OVERRIDES = {
"CUT_STANDSTONE_SLAB": "CutSandstoneSlab", # Mojang typo in source
}
def java_to_csharp_name(java_name: str) -> str:
"""Convert SCREAMING_SNAKE_CASE Java field name to PascalCase C# enum name."""
if java_name in OVERRIDES:
return OVERRIDES[java_name]
return "".join(word.capitalize() for word in java_name.lower().split("_"))
def main():
if len(sys.argv) != 3:
print(__doc__)
sys.exit(1)
mc_version = sys.argv[1]
class_suffix = sys.argv[2]
version_dir = DECOMPILED_ROOT / f"{mc_version}-decompiled"
items_java = version_dir / "net" / "minecraft" / "world" / "item" / "Items.java"
if not items_java.exists():
print(f"Error: {items_java} not found")
sys.exit(1)
pattern = re.compile(r'\s+public static final Item (\w+)\s*=')
field_names = []
with open(items_java) as f:
for line in f:
m = pattern.match(line)
if m:
field_names.append(m.group(1))
print(f"Found {len(field_names)} items in MC {mc_version}")
# Verify enum name conversion against existing ItemType.cs
item_type_cs = OUTPUT_DIR.parent / "ItemType.cs"
known_enums = set()
if item_type_cs.exists():
with open(item_type_cs) as f:
for line in f:
m = re.match(r'\s+(\w+),?\s*$', line)
if m and m.group(1) not in ("Null", "Unknown"):
known_enums.add(m.group(1))
missing = []
mappings = []
for i, name in enumerate(field_names):
cs_name = java_to_csharp_name(name)
mappings.append((i, cs_name))
if known_enums and cs_name not in known_enums:
missing.append((i, name, cs_name))
if missing:
print(f"\nWARNING: {len(missing)} items not found in ItemType.cs enum:")
for idx, java_name, cs_name in missing:
print(f" [{idx}] {java_name} -> {cs_name}")
print("\nYou need to add these to ItemType.cs before the palette will compile.")
print("Insert them in alphabetical order within the enum.")
class_name = f"ItemPalette{class_suffix}"
output_path = OUTPUT_DIR / f"{class_name}.cs"
lines = [
"using System.Collections.Generic;",
"",
"namespace MinecraftClient.Inventory.ItemPalettes",
"{",
f" public class {class_name} : ItemPalette",
" {",
" private static readonly Dictionary<int, ItemType> mappings = new();",
"",
f" static {class_name}()",
" {",
]
for idx, cs_name in mappings:
lines.append(f" mappings[{idx}] = ItemType.{cs_name};")
lines += [
" }",
"",
" protected override Dictionary<int, ItemType> GetDict()",
" {",
" return mappings;",
" }",
" }",
"}",
"",
]
output_path.write_text("\n".join(lines))
print(f"Generated {output_path} with {len(mappings)} mappings")
if __name__ == "__main__":
main()