From dfbfe2e30963d2dddc915fac4b1ff7981b75ada3 Mon Sep 17 00:00:00 2001 From: Bruno Barros Date: Fri, 11 Jul 2025 15:39:56 -0300 Subject: [PATCH] Converting otbm2json to Python with to_json and to_otbm example scripts --- .gitignore | 9 ++ README.md | 17 ++- py/headers.py | 36 +++++ py/otbm2json.py | 385 ++++++++++++++++++++++++++++++++++++++++++++++++ py/to_json.py | 13 ++ py/to_otbm.py | 12 ++ 6 files changed, 469 insertions(+), 3 deletions(-) create mode 100644 .gitignore create mode 100644 py/headers.py create mode 100644 py/otbm2json.py create mode 100644 py/to_json.py create mode 100644 py/to_otbm.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f91850 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# PyInstaller +dist/ +build/ +*.spec diff --git a/README.md b/README.md index 2773880..3a5d53e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ # OTBM2JSON + NodeJS library for programmatically modifying Open Tibia Binary Mapping files. This framework reads .otbm files and parses them to an intermediary JSON format. This JSON structure can be changed programatically to make generic modifications. Once a change has been committed to the structure, it can be encoded back to an .otbm file. # JSON Structure + The structure of the intermediary JSON format read from and to `.otbm` can be seen in the example `OTBM.json`. # Usage + Import the library in your script: const otbm2json = require("./otbm2json.js"); @@ -12,15 +15,16 @@ Import the library in your script: The library provides two functions for reading and writing OTBM: data = otbm2json.read(filename); - + ** Modify the data object here ** - + otbm2json.write(filename, data); For an example see below. # Example -An example script `examples/example.js` is provided. This script uses the `examples/void.otbm` (8x8 void area) in this repository and replaces all void tiles with chessboard tiles and writes the result to `examples/chess.otbm`. + +An example script `examples/example.js` is provided. This script uses the `examples/void.otbm` (8x8 void area) in this repository and replaces all void tiles with chessboard tiles and writes the result to `examples/chess.otbm`.

@@ -28,5 +32,12 @@ An example script `examples/example.js` is provided. This script uses the `examp

+# Python + +There is also a Python version of this script in `/py` directory. +You can run `python ./py/to_json.py` or `python ./py/to_otbm.py` as example. +This is very handy for handling large files. + # Version + Current version 0.2.0. This is a work in progress. diff --git a/py/headers.py b/py/headers.py new file mode 100644 index 0000000..4680dd3 --- /dev/null +++ b/py/headers.py @@ -0,0 +1,36 @@ +HEADERS = { + "OTBM_MAP_HEADER": 0x00, + "OTBM_MAP_DATA": 0x02, + "OTBM_TILE_AREA": 0x04, + "OTBM_TILE": 0x05, + "OTBM_ITEM": 0x06, + "OTBM_TOWNS": 0x0C, + "OTBM_TOWN": 0x0D, + "OTBM_HOUSETILE": 0x0E, + "OTBM_WAYPOINTS": 0x0F, + "OTBM_WAYPOINT": 0x10, + + "OTBM_ATTR_DESCRIPTION": 0x01, + "OTBM_ATTR_EXT_FILE": 0x02, + "OTBM_ATTR_TILE_FLAGS": 0x03, + "OTBM_ATTR_ACTION_ID": 0x04, + "OTBM_ATTR_UNIQUE_ID": 0x05, + "OTBM_ATTR_TEXT": 0x06, + "OTBM_ATTR_DESC": 0x07, + "OTBM_ATTR_TELE_DEST": 0x08, + "OTBM_ATTR_ITEM": 0x09, + "OTBM_ATTR_DEPOT_ID": 0x0A, + "OTBM_ATTR_EXT_SPAWN_FILE": 0x0B, + "OTBM_ATTR_EXT_HOUSE_FILE": 0x0D, + "OTBM_ATTR_HOUSEDOORID": 0x0E, + "OTBM_ATTR_COUNT": 0x0F, + "OTBM_ATTR_RUNE_CHARGES": 0x16, + + "TILESTATE_NONE": 0x0000, + "TILESTATE_PROTECTIONZONE": 0x0001, + "TILESTATE_DEPRECATED": 0x0002, + "TILESTATE_NOPVP": 0x0004, + "TILESTATE_NOLOGOUT": 0x0008, + "TILESTATE_PVPZONE": 0x0010, + "TILESTATE_REFRESH": 0x0020, +} diff --git a/py/otbm2json.py b/py/otbm2json.py new file mode 100644 index 0000000..92f56bf --- /dev/null +++ b/py/otbm2json.py @@ -0,0 +1,385 @@ +import struct +from headers import HEADERS + +NODE_ESC = 0xFD +NODE_INIT = 0xFE +NODE_TERM = 0xFF +__VERSION__ = "1.0.1" + + +def write(outfile, data): + with open(outfile, "wb") as f: + f.write(serialize(data)) + + +def serialize(data): + # OTBM Header (4 bytes zeros) + result = bytearray([0x00, 0x00, 0x00, 0x00]) + result.extend(write_node(data["data"])) + return bytes(result) + + +def write_node(node): + return b"".join( + [ + bytes([NODE_INIT]), + write_element(node), + b"".join(write_node(child) for child in get_child_nodes(node)), + bytes([NODE_TERM]), + ] + ) + + +def get_child_nodes(node): + t = node.get("type") + if t == HEADERS["OTBM_TILE_AREA"]: + return node.get("tiles", []) + elif t == HEADERS["OTBM_TILE"] or t == HEADERS["OTBM_HOUSETILE"]: + return node.get("items", []) + elif t == HEADERS["OTBM_TOWNS"]: + return node.get("towns", []) + elif t == HEADERS["OTBM_ITEM"]: + return node.get("content", []) + elif t == HEADERS["OTBM_MAP_DATA"]: + return node.get("features", []) + else: + return node.get("nodes", []) + + +def write_element(node): + t = node.get("type") + if t == HEADERS["OTBM_MAP_HEADER"]: + buf = struct.pack( + "