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(
+ "