add tjs/ns0 export script
This commit is contained in:
123
tjs_ns0.py
Normal file
123
tjs_ns0.py
Normal file
@@ -0,0 +1,123 @@
|
||||
import sys
|
||||
import struct
|
||||
from argparse import ArgumentParser
|
||||
from os.path import splitext
|
||||
from json import dump
|
||||
|
||||
|
||||
TJS_NS0_MAGIC = b'TJS/ns0\x00TJS\x00\x00\x00\x00\x00'
|
||||
sys.setrecursionlimit(200000)
|
||||
|
||||
|
||||
class TJS_NS0:
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
self.obj = None
|
||||
self.pos = 0
|
||||
|
||||
def check_magic(self):
|
||||
if self.data[:16] != TJS_NS0_MAGIC:
|
||||
raise ValueError("Invalid TJS/ns0 magic header")
|
||||
self.pos += 16
|
||||
|
||||
def parse(self):
|
||||
self.check_magic()
|
||||
self.obj = self.parse_obj()
|
||||
|
||||
def read_bytes(self, length):
|
||||
if self.pos + length > len(self.data):
|
||||
raise ValueError("Unexpected end of data")
|
||||
result = self.data[self.pos:self.pos + length]
|
||||
self.pos += length
|
||||
return result
|
||||
|
||||
def peek_bytes(self, length):
|
||||
if self.pos + length > len(self.data):
|
||||
raise ValueError("Unexpected end of data")
|
||||
return self.data[self.pos:self.pos + length]
|
||||
|
||||
def read_u16(self):
|
||||
return struct.unpack('<H', self.read_bytes(2))[0]
|
||||
|
||||
def peek_u16(self):
|
||||
return struct.unpack('<H', self.peek_bytes(2))[0]
|
||||
|
||||
def read_u32(self):
|
||||
return struct.unpack('<I', self.read_bytes(4))[0]
|
||||
|
||||
def peek_u32(self):
|
||||
return struct.unpack('<I', self.peek_bytes(4))[0]
|
||||
|
||||
def read_u64(self):
|
||||
return struct.unpack('<Q', self.read_bytes(8))[0]
|
||||
|
||||
def peek_u64(self):
|
||||
return struct.unpack('<Q', self.peek_bytes(8))[0]
|
||||
|
||||
# def parse_obj(self):
|
||||
# obj_type = self.peek_u16()
|
||||
# first_byte = obj_type & 0xFF
|
||||
# if first_byte == 0xC1:
|
||||
# return self.parse_dict()
|
||||
# elif first_byte == 0x81:
|
||||
# self.pos += 2
|
||||
# return self.parse_array()
|
||||
# else:
|
||||
# return self.parse_array()
|
||||
|
||||
def parse_string(self):
|
||||
str_len = self.read_u32()
|
||||
#print(f"String length: {str_len}, pos={self.pos - 4:08X}")
|
||||
return self.read_bytes(str_len * 2).decode('utf-16-le')
|
||||
|
||||
def parse_obj(self):
|
||||
value_type = self.read_u16()
|
||||
first_byte = value_type & 0xFF
|
||||
if first_byte == 0x02:
|
||||
return self.parse_string()
|
||||
elif first_byte == 0x04:
|
||||
return self.read_u64()
|
||||
elif first_byte == 0x00:
|
||||
return value_type >> 8
|
||||
elif first_byte == 0xC1:
|
||||
return self.parse_dict()
|
||||
elif first_byte == 0x81:
|
||||
return self.parse_array()
|
||||
else:
|
||||
raise ValueError(f"Unknown object type: {value_type:04X}, pos={self.pos - 2:08X}")
|
||||
|
||||
def parse_dict(self):
|
||||
kv_count = self.read_u32()
|
||||
obj = {}
|
||||
for _ in range(kv_count):
|
||||
key = self.parse_string()
|
||||
value = self.parse_obj()
|
||||
# print(f"Key: {key}, Value: {value}")
|
||||
obj[key] = value
|
||||
return obj
|
||||
|
||||
def parse_array(self):
|
||||
arr_len = self.read_u32()
|
||||
#print(f"Array length: {arr_len}, pos={self.pos - 4:08X}")
|
||||
arr = []
|
||||
for _ in range(arr_len):
|
||||
obj = self.parse_obj()
|
||||
arr.append(obj)
|
||||
return arr
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = ArgumentParser(description="Parse TJS/ns0 files")
|
||||
parser.add_argument("input", help="Input TJS/ns0 file")
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.input, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
tjs_ns0 = TJS_NS0(data)
|
||||
tjs_ns0.parse()
|
||||
print("TJS/ns0 file parsed successfully.")
|
||||
json_path = splitext(args.input)[0] + ".json"
|
||||
with open(json_path, "w", encoding="utf-8") as f:
|
||||
dump(tjs_ns0.obj, f, ensure_ascii=False, indent=4)
|
||||
print(f"Output written to {json_path}")
|
||||
Reference in New Issue
Block a user