├── input └── .keep ├── output └── .keep ├── pydofus ├── __init__.py ├── dx.py ├── swl.py ├── _binarystream.py ├── d2i.py ├── d2p.py ├── d2o.py ├── ele.py └── dlm.py ├── .gitignore ├── dx_pack.py ├── dx_unpack.py ├── ele_unpack.py ├── dlm_pack.py ├── d2i_unpack.py ├── dlm_unpack.py ├── d2i_pack.py ├── swl_pack.py ├── swl_unpack.py ├── d2o_unpack.py ├── README.md ├── d2p_unpack.py └── d2p_pack.py /input/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /output/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pydofus/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["d2p", "swl"] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | output/* 4 | input/* 5 | !output/.keep 6 | !input/.keep 7 | -------------------------------------------------------------------------------- /dx_pack.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from pydofus.dx import DX, InvalidDXFile 3 | 4 | # python dx_pack.py file.swf 5 | # file output: file.dx 6 | 7 | file = sys.argv[1] 8 | 9 | swf_input = open(file, "rb") 10 | dx_output = open(file.replace("swf", "dx"), "wb") 11 | 12 | dx = DX(dx_output) 13 | dx.write(swf_input) 14 | 15 | swf_input.close() 16 | dx_output.close() 17 | -------------------------------------------------------------------------------- /dx_unpack.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from pydofus.dx import DX, InvalidDXFile 3 | 4 | # python dx_unpack.py file.dx 5 | # file output: file.swf 6 | 7 | file = sys.argv[1] 8 | 9 | dx_input = open(file, "rb") 10 | swf_output = open(file.replace("dx", "swf"), "wb") 11 | 12 | dx = DX(dx_input) 13 | dx.read(swf_output) 14 | 15 | dx_input.close() 16 | swf_output.close() 17 | -------------------------------------------------------------------------------- /ele_unpack.py: -------------------------------------------------------------------------------- 1 | import sys, json 2 | from pydofus.ele import ELE, InvalidELEFile 3 | 4 | # python ele_unpack.py file.ele 5 | # file output: file.json 6 | 7 | file = sys.argv[1] 8 | 9 | ele_input = open(file, "rb") 10 | json_output = open(file.replace(".ele", ".json"), "w") 11 | 12 | ele = ELE(ele_input) 13 | data = ele.read() 14 | 15 | json.dump(data, json_output, indent=2) 16 | 17 | ele_input.close() 18 | json_output.close() 19 | -------------------------------------------------------------------------------- /dlm_pack.py: -------------------------------------------------------------------------------- 1 | import sys, json 2 | from pydofus.dlm import DLM, InvalidDLMFile 3 | 4 | # python dlm_pack.py file.json 5 | # file output: file.dlm 6 | 7 | file = sys.argv[1] 8 | 9 | json_input = open(file, "r") 10 | dlm_output = open(file.replace("json", "dlm"), "wb") 11 | 12 | dlm = DLM(dlm_output, "649ae451ca33ec53bbcbcc33becf15f4") 13 | data = dlm.write(json.load(json_input)) 14 | 15 | json_input.close() 16 | dlm_output.close() 17 | -------------------------------------------------------------------------------- /d2i_unpack.py: -------------------------------------------------------------------------------- 1 | import sys, json 2 | from pydofus.d2i import D2I, InvalidD2IFile 3 | 4 | # python d2i_unpack.py file.d2i 5 | # file output: file.json 6 | 7 | file = sys.argv[1] 8 | 9 | d2i_input = open(file, "rb") 10 | json_output = open(file.replace("d2i", "json"), "w", encoding="utf-8") 11 | 12 | d2i = D2I(d2i_input) 13 | data = d2i.read() 14 | 15 | json.dump(data, json_output, indent=2, ensure_ascii=False) 16 | 17 | d2i_input.close() 18 | json_output.close() 19 | -------------------------------------------------------------------------------- /dlm_unpack.py: -------------------------------------------------------------------------------- 1 | import sys, json 2 | from pydofus.dlm import DLM, InvalidDLMFile 3 | 4 | # python dlm_unpack.py file.dlm 5 | # file output: file.json 6 | 7 | file = sys.argv[1] 8 | 9 | dlm_input = open(file, "rb") 10 | json_output = open(file.replace("dlm", "json"), "w") 11 | 12 | dlm = DLM(dlm_input, "649ae451ca33ec53bbcbcc33becf15f4") 13 | data = dlm.read() 14 | 15 | json.dump(data, json_output, indent=2) 16 | 17 | dlm_input.close() 18 | json_output.close() 19 | -------------------------------------------------------------------------------- /d2i_pack.py: -------------------------------------------------------------------------------- 1 | import sys, json 2 | from pydofus.d2i import D2I, InvalidD2IFile 3 | from collections import OrderedDict 4 | 5 | # python d2i_pack.py file.json 6 | # file output: file.d2i 7 | 8 | file = sys.argv[1] 9 | 10 | json_input = open(file, "r", encoding="utf-8") 11 | d2i_output = open(file.replace("json", "d2i"), "wb") 12 | 13 | d2i = D2I(d2i_output) 14 | data = d2i.write(json.load(json_input, object_pairs_hook=OrderedDict)) 15 | 16 | json_input.close() 17 | d2i_output.close() 18 | -------------------------------------------------------------------------------- /swl_pack.py: -------------------------------------------------------------------------------- 1 | import sys, json 2 | from pydofus.swl import SWLReader, InvalidSWLFile 3 | 4 | # python swl_pack.py file.swf (require file.json) 5 | # file output: file.swl 6 | 7 | file = sys.argv[1] 8 | 9 | swf_input = open(file, "rb") 10 | json_input = open(file.replace("swf", "json"), "r") 11 | swl_output = open(file.replace("swf", "swl"), "wb") 12 | 13 | swl_data = json.load(json_input) 14 | swl_data["SWF"] = swf_input.read() 15 | swl = SWLBuilder(swl_data, swl_output) 16 | swl.build() 17 | 18 | swl_output.seek(0) 19 | 20 | swl_output.close() 21 | swf_input.close() 22 | json_input.close() 23 | -------------------------------------------------------------------------------- /swl_unpack.py: -------------------------------------------------------------------------------- 1 | import sys, json 2 | from pydofus.swl import SWLReader, InvalidSWLFile 3 | 4 | # python swl_unpack.py file.swl 5 | # file output: file.swf and file.json 6 | 7 | file = sys.argv[1] 8 | 9 | swl_input = open(file, "rb") 10 | swf_output = open(file.replace("swl", "swf"), "wb") 11 | json_output = open(file.replace("swl", "json"), "w") 12 | 13 | swl = SWLReader(swl_input) 14 | swf_output.write(swl.SWF) 15 | swl_data = {'version':swl_reader.version, 'frame_rate':swl_reader.frame_rate, 'classes':swl_reader.classes} 16 | json.dump(swl_data, json_output, indent=4) 17 | 18 | swl_input.close() 19 | swf_output.close() 20 | json_output.close() 21 | -------------------------------------------------------------------------------- /d2o_unpack.py: -------------------------------------------------------------------------------- 1 | import io, sys, os, json 2 | from pydofus.d2o import D2OReader, InvalidD2OFile 3 | 4 | # python d2o_unpack.py (all files in input folder) 5 | # folder output: ./output/{all files} 6 | 7 | path_input = "./input/" 8 | path_output = "./output/" 9 | 10 | for file in os.listdir(path_input): 11 | if file.endswith(".d2o"): 12 | file_name = os.path.basename(file) 13 | d2p_file = open(path_input + file, "rb") 14 | 15 | print("D2O Unpacker for " + file_name) 16 | 17 | try: 18 | d2o_reader = D2OReader(d2p_file) 19 | d2o_data = d2o_reader.get_objects() 20 | 21 | json_output = open(path_output + file_name.replace("d2o", "json"), 22 | "w") 23 | json.dump(d2o_data, json_output, indent=4) 24 | json_output.close() 25 | except InvalidD2OFile: 26 | pass 27 | -------------------------------------------------------------------------------- /pydofus/dx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from ._binarystream import _BinaryStream 5 | 6 | class InvalidDXFile(Exception): 7 | def __init__(self, message): 8 | super(InvalidDXFile, self).__init__(message) 9 | self.message = message 10 | 11 | class DX: 12 | def __init__(self, stream): 13 | self._stream = stream 14 | 15 | def read(self, out_stream): 16 | raw = _BinaryStream(self._stream, True) 17 | _out = _BinaryStream(out_stream, True) 18 | 19 | file = raw.read_char() 20 | version = raw.read_char() 21 | keyLen = raw.read_int16() 22 | key = raw.read_bytes(keyLen) 23 | 24 | swfDataPosition = self._stream.tell() 25 | swfData = raw.read_bytes() 26 | swfLenght = self._stream.tell() - swfDataPosition 27 | 28 | for i in range(0, swfLenght): 29 | _out.write_uchar(swfData[i] ^ key[i % keyLen]) 30 | 31 | def write(self, in_stream): 32 | raw = _BinaryStream(self._stream, True) 33 | _in = _BinaryStream(in_stream, True) 34 | 35 | key = 0 # WE DON'T NEED THIS FUCKING KEY, LET'S XOR WITH 0 !! :D 36 | 37 | raw.write_char(83) 38 | raw.write_char(0) 39 | raw.write_int16(1) 40 | raw.write_char(key) 41 | 42 | swfData = _in.read_bytes() 43 | swfLenght = in_stream.tell() 44 | 45 | for i in range(0, swfLenght): 46 | raw.write_uchar(swfData[i] ^ key) 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PyDofus Tools 2 | ============= 3 | 4 | Python 3 scripts to pack/unpack Dofus files 5 | 6 | - [x] **d2i** 7 | - [x] **d2p** 8 | - [x] **d2o** (unpack only) 9 | - [x] **dlm** 10 | - [x] **dx** 11 | - [x] **ele** (unpack only) 12 | - [x] **swl** 13 | 14 | Usage 15 | ----- 16 | 17 | ###d2i 18 | 19 | ```Shell 20 | $ python d2i_unpack.py file.d2i 21 | # file output: file.json 22 | ``` 23 | 24 | ```Shell 25 | $ python d2i_pack.py file.json 26 | # file output: file.d2i 27 | ``` 28 | 29 | ###d2p 30 | 31 | ```Shell 32 | $ python d2p_unpack.py 33 | # (all files in input folder) 34 | # folder output: ./output/{all files}.d2p 35 | ``` 36 | 37 | ```Shell 38 | $ python d2p_pack.py file.d2p 39 | # require original file in input folder and unpacked file in output folder 40 | # file output: ./output/~generated/file.d2p 41 | ``` 42 | 43 | ###d2o 44 | 45 | ```Shell 46 | $ python d2o_unpack.py 47 | # (all files in input folder) 48 | # folder output: ./output/{all files}.json 49 | ``` 50 | 51 | ###dlm 52 | 53 | ```Shell 54 | $ python dlm_unpack.py file.dlm 55 | # file output: file.json 56 | ``` 57 | 58 | ```Shell 59 | $ python dlm_pack.py file.json 60 | # file output: file.dlm 61 | ``` 62 | 63 | ###dx 64 | 65 | ```Shell 66 | $ python dx_unpack.py file.dx 67 | # file output: file.swf 68 | ``` 69 | 70 | ```Shell 71 | $ python dx_pack.py file.swf 72 | # file output: file.dx 73 | ``` 74 | 75 | ###ele 76 | 77 | ```Shell 78 | $ python ele_unpack.py elements.ele 79 | # file output: elements.json 80 | ``` 81 | 82 | ###swl 83 | 84 | ```Shell 85 | $ python swl_unpack.py file.swl 86 | # file output: file.swf and file.json 87 | ``` 88 | 89 | ```Shell 90 | $ python swl_pack.py file.swf 91 | # require file.json 92 | # file output: file.swl 93 | ``` 94 | 95 | Authors 96 | ------- 97 | 98 | **Marvin Roger** ([marvinroger](https://github.com/marvinroger)) : based on his work 99 | **Yann Guineau** ([LuaxY](https://github.com/LuaxY)) : automated scripts for pack/unpack dofus files 100 | **[nowis13](https://github.com/nowis13)** : add d2o and ele unpack support 101 | -------------------------------------------------------------------------------- /d2p_unpack.py: -------------------------------------------------------------------------------- 1 | import io, sys, os, json 2 | from pydofus.d2p import D2PReader, InvalidD2PFile 3 | from pydofus.swl import SWLReader, InvalidSWLFile 4 | 5 | # python d2p_pack.py (all files in input folder) 6 | # folder output: ./output/{all files}.d2p 7 | 8 | path_input = "./input/" 9 | path_output = "./output/" 10 | 11 | for file in os.listdir(path_input): 12 | if file.endswith(".d2p"): 13 | file_name = os.path.basename(file) 14 | d2p_file = open(path_input + file, "rb") 15 | 16 | try: 17 | os.stat(path_output + file_name) 18 | except: 19 | os.mkdir(path_output + file_name) 20 | 21 | print("D2P Unpacker for " + file_name) 22 | 23 | try: 24 | d2p_reader = D2PReader(d2p_file, False) 25 | d2p_reader.load() 26 | for name, specs in d2p_reader.files.items(): 27 | print("extract file " + file_name + "/" + name) 28 | 29 | try: 30 | os.stat(path_output + file_name + "/" + os.path.dirname(name)) 31 | except: 32 | os.makedirs(path_output + file_name + "/" + os.path.dirname(name)) 33 | 34 | if "swl" in name: 35 | swl = io.BytesIO(specs["binary"]) 36 | swl_reader = SWLReader(swl) 37 | 38 | swf_output = open(path_output + file_name + "/" + name.replace("swl", "swf"), "wb") 39 | json_output = open(path_output + file_name + "/" + name.replace("swl", "json"), "w") 40 | 41 | swf_output.write(swl_reader.SWF) 42 | swl_data = {'version':swl_reader.version, 'frame_rate':swl_reader.frame_rate, 'classes':swl_reader.classes} 43 | json.dump(swl_data, json_output, indent=4) 44 | 45 | swf_output.close() 46 | json_output.close() 47 | else: 48 | file_output = open(path_output + file_name + "/" + name, "wb") 49 | file_output.write(specs["binary"]) 50 | file_output.close() 51 | pass 52 | except InvalidD2PFile: 53 | pass 54 | -------------------------------------------------------------------------------- /d2p_pack.py: -------------------------------------------------------------------------------- 1 | import io, sys, os, tempfile, fnmatch, json 2 | from collections import OrderedDict 3 | from pydofus.d2p import D2PReader, D2PBuilder, InvalidD2PFile 4 | from pydofus.swl import SWLReader, SWLBuilder, InvalidSWLFile 5 | from pydofus._binarystream import _BinaryStream 6 | 7 | # python d2p_pack.py file.d2p (require original file in input folder and unpacked file in output folder) 8 | # file output: ./output/~generated/file.d2p 9 | 10 | path_input = "./input/" 11 | path_output = "./output/" 12 | 13 | try: 14 | file = sys.argv[1] 15 | except: 16 | file = None 17 | 18 | try: 19 | swl_mode = sys.argv[2] 20 | except: 21 | swl_mode = None 22 | 23 | if file is None or swl_mode is None: 24 | print("usage: python d2p_pack.py {file.d2p} {swl ture|false}") 25 | else: 26 | print("D2P Packer for " + file) 27 | 28 | try: 29 | os.stat(path_output + "~generated") 30 | except: 31 | os.mkdir(path_output + "~generated") 32 | 33 | d2p_input = open(path_input + file, "rb") 34 | d2p_template = D2PReader(d2p_input) 35 | 36 | d2p_ouput = open(path_output + "~generated/" + file, "wb") 37 | d2p_builder = D2PBuilder(d2p_template, d2p_ouput) 38 | 39 | list_files = OrderedDict() 40 | 41 | rootPath = path_output + file 42 | 43 | for root, dirs, files in os.walk(rootPath): 44 | for filename in fnmatch.filter(files, "*.*"): 45 | path = os.path.join(root, filename).replace("\\", "/") 46 | file = path.replace(rootPath + "/", "") 47 | object_ = {} 48 | 49 | if "swf" in file and swl_mode == "true": 50 | json_input = open(path.replace("swf", "json"), "r") 51 | swf_input = open(path, "rb") 52 | swl_output = tempfile.TemporaryFile() 53 | 54 | swl_data = json.load(json_input) 55 | swl_data["SWF"] = swf_input.read() 56 | 57 | swl_builder = SWLBuilder(swl_data, swl_output) 58 | swl_builder.build() 59 | 60 | swl_output.seek(0) 61 | object_["binary"] = swl_output.read() 62 | list_files[file.replace("swf", "swl")] = object_ 63 | 64 | json_input.close() 65 | swf_input.close() 66 | swl_output.close() 67 | elif "json" in file and swl_mode == "true": 68 | continue 69 | else: 70 | new_file = open(path, "rb") 71 | object_["binary"] = new_file.read() 72 | new_file.close() 73 | list_files[file] = object_ 74 | 75 | print("pack file " + file) 76 | 77 | d2p_builder.files = list_files 78 | d2p_builder.build() 79 | 80 | d2p_input.close() 81 | d2p_ouput.close() 82 | -------------------------------------------------------------------------------- /pydofus/swl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from ._binarystream import _BinaryStream 5 | 6 | # Exceptions 7 | 8 | 9 | class InvalidSWLFile(Exception): 10 | def __init__(self, message): 11 | super(InvalidSWLFile, self).__init__(message) 12 | self.message = message 13 | 14 | # Classes 15 | 16 | 17 | class SWLReader: 18 | """Read SWL files""" 19 | def __init__(self, stream): 20 | """Load the class with the SWL stream given""" 21 | # Attributes 22 | self._stream = stream 23 | self._version = None 24 | self._frame_rate = None 25 | 26 | self._classes = None 27 | 28 | self._SWF = None 29 | 30 | # Load the SWL 31 | SWL_file_binary = _BinaryStream(self._stream, True) 32 | 33 | byte_header = SWL_file_binary.read_char() 34 | if byte_header == b"": 35 | raise InvalidSWLFile("First byte not found.") 36 | 37 | if byte_header != 76: 38 | raise InvalidSWLFile("The first byte doesn't match" 39 | " the SWL pattern.") 40 | 41 | self._version = SWL_file_binary.read_char() 42 | self._frame_rate = SWL_file_binary.read_uint32() 43 | classes_count = SWL_file_binary.read_int32() 44 | if ((self._version == b"" or self._frame_rate == b"" or 45 | classes_count == b"")): 46 | raise InvalidSWLFile("The file doesn't match the SWL pattern.") 47 | 48 | self._classes = [] 49 | 50 | i = 0 51 | while i < classes_count: 52 | class_ = (SWL_file_binary.read_string()).decode('utf-8') 53 | if class_ == b"": 54 | raise InvalidSWLFile("The file appears to be corrupt.") 55 | self._classes.append(class_) 56 | 57 | i += 1 58 | 59 | self._SWF = SWL_file_binary.read_bytes() 60 | 61 | # Accessors 62 | 63 | def _get_stream(self): 64 | return self._stream 65 | 66 | def _get_version(self): 67 | return self._version 68 | 69 | def _get_frame_rate(self): 70 | return self._frame_rate 71 | 72 | def _get_classes(self): 73 | return self._classes 74 | 75 | def _get_SWF(self): 76 | return self._SWF 77 | 78 | # Properties 79 | 80 | stream = property(_get_stream) 81 | version = property(_get_version) 82 | frame_rate = property(_get_frame_rate) 83 | classes = property(_get_classes) 84 | SWF = property(_get_SWF) 85 | 86 | 87 | class SWLBuilder: 88 | """Build SWL files""" 89 | def __init__(self, template, target): 90 | self._template = template 91 | self._target = target 92 | self._SWF = self._template["SWF"] 93 | 94 | def build(self): 95 | """Create the SWL represented by the class in the given stream.""" 96 | SWL_file_build_binary = _BinaryStream(self._target, True) 97 | 98 | SWL_file_build_binary.write_char(76) 99 | 100 | SWL_file_build_binary.write_char(self._template["version"]) 101 | SWL_file_build_binary.write_uint32(self._template["frame_rate"]) 102 | SWL_file_build_binary.write_int32(len(self._template["classes"])) 103 | 104 | for class_ in self._template["classes"]: 105 | SWL_file_build_binary.write_string((class_).encode()) 106 | 107 | SWL_file_build_binary.write_bytes(self._SWF) 108 | 109 | # Mutators 110 | 111 | def _set_SWF(self, swf): 112 | self._SWF = swf 113 | 114 | # Properties 115 | 116 | SWF = property(None, _set_SWF) 117 | -------------------------------------------------------------------------------- /pydofus/_binarystream.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from struct import * 5 | 6 | class _BinaryStream: 7 | """Allow some binary operations on a stream opened in binary mode""" 8 | def __init__(self, base_stream, big_endian=False): 9 | self._base_stream = base_stream 10 | self._big_endian = big_endian 11 | 12 | # Comment functions 13 | 14 | def position(self, value=None): 15 | if value is None: 16 | return self._base_stream.tell() 17 | else: 18 | self._base_stream.seek(value) 19 | 20 | def bytes_available(self): 21 | position = self._base_stream.tell() 22 | self._base_stream.seek(0, 2) 23 | eof = self._base_stream.tell() 24 | self._base_stream.seek(position, 0) 25 | return eof - position 26 | 27 | # Write functions 28 | 29 | def write_bytes(self, value): 30 | self._base_stream.write(value) 31 | 32 | def write_bool(self, value): 33 | if value: 34 | self.write_char(1) 35 | else: 36 | self.write_char(0) 37 | 38 | def write_char(self, value): 39 | self._pack('b', value) 40 | 41 | def write_uchar(self, value): 42 | self._pack('B', value) 43 | 44 | def write_bool(self, value): 45 | self._pack('?', value) 46 | 47 | def write_int16(self, value): 48 | self._pack('h', value) 49 | 50 | def write_uint16(self, value): 51 | self._pack('H', value) 52 | 53 | def write_int32(self, value): 54 | self._pack('i', value) 55 | 56 | def write_uint32(self, value): 57 | self._pack('I', value) 58 | 59 | def write_int64(self, value): 60 | self._pack('q', value) 61 | 62 | def write_uint64(self, value): 63 | self._pack('Q', value) 64 | 65 | def write_float(self, value): 66 | self._pack('f', value) 67 | 68 | def write_double(self, value): 69 | self._pack('d', value) 70 | 71 | def write_string(self, value): 72 | length = len(value) 73 | self.write_uint16(length) 74 | self._pack(str(length) + 's', value) 75 | 76 | def _pack(self, fmt, data): 77 | if self._big_endian: 78 | fmt = ">" + fmt 79 | else: 80 | fmt = "<" + fmt 81 | return self.write_bytes(pack(fmt, data)) 82 | 83 | # Read functions 84 | 85 | def read_byte(self): 86 | return self._base_stream.read(1) 87 | 88 | def read_bytes(self, length=None): 89 | if length is None: 90 | bytes = self._base_stream.read() 91 | else: 92 | bytes = self._base_stream.read(length) 93 | return bytes 94 | 95 | def read_bool(self): 96 | bool = self._base_stream.read(1)[0] 97 | if bool == 1: 98 | return True 99 | else: 100 | return False 101 | 102 | def read_char(self): 103 | return self._unpack('b') 104 | 105 | def read_uchar(self): 106 | return self._unpack('B') 107 | 108 | def read_bool(self): 109 | return self._unpack('?') 110 | 111 | def read_int16(self): 112 | return self._unpack('h', 2) 113 | 114 | def read_uint16(self): 115 | return self._unpack('H', 2) 116 | 117 | def read_int32(self): 118 | return self._unpack('i', 4) 119 | 120 | def read_uint32(self): 121 | return self._unpack('I', 4) 122 | 123 | def read_int64(self): 124 | return self._unpack('q', 8) 125 | 126 | def read_uint64(self): 127 | return self._unpack('Q', 8) 128 | 129 | def read_float(self): 130 | return self._unpack('f', 4) 131 | 132 | def read_double(self): 133 | return self._unpack('d', 8) 134 | 135 | def read_string(self): 136 | length = self.read_uint16() 137 | return self._unpack(str(length) + 's', length) 138 | 139 | def read_string_bytes(self, length): 140 | return self._unpack(str(length) + 's', length) 141 | 142 | def _unpack(self, fmt, length=1): 143 | bytes = self.read_bytes(length) 144 | if self._big_endian: 145 | fmt = ">" + fmt 146 | else: 147 | fmt = "<" + fmt 148 | return unpack(fmt, bytes)[0] 149 | -------------------------------------------------------------------------------- /pydofus/d2i.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import zlib, tempfile, io, unicodedata 5 | from ._binarystream import _BinaryStream 6 | from collections import OrderedDict 7 | 8 | class InvalidD2IFile(Exception): 9 | def __init__(self, message): 10 | super(InvalidD2IFile, self).__init__(message) 11 | self.message = message 12 | 13 | class D2I: 14 | def __init__(self, stream): 15 | self._stream = stream 16 | self._obj = OrderedDict() 17 | 18 | def read(self): 19 | raw = _BinaryStream(self._stream, True) 20 | 21 | indexs = OrderedDict() 22 | unDiacriticalIndex = OrderedDict() 23 | 24 | self._obj["texts"] = OrderedDict() 25 | self._obj["nameText"] = OrderedDict() 26 | self._obj["idText"] = OrderedDict() 27 | 28 | indexesPointer = raw.read_int32() 29 | self._stream.seek(indexesPointer) 30 | 31 | i = 0 32 | indexesLength = raw.read_int32() 33 | while i < indexesLength: 34 | key = raw.read_int32() 35 | diacriticalText = raw.read_bool() 36 | pointer = raw.read_int32() 37 | indexs[pointer] = key 38 | 39 | if diacriticalText: 40 | i += 4 41 | unDiacriticalIndex[key] = raw.read_int32() 42 | else: 43 | unDiacriticalIndex[key] = pointer 44 | i += 9 45 | 46 | indexesLength = raw.read_int32() 47 | while indexesLength > 0: 48 | position = self._stream.tell() 49 | textKey = raw.read_string().decode("utf-8") 50 | pointer = raw.read_int32() 51 | self._obj["nameText"][textKey] = indexs[pointer] 52 | indexesLength = (indexesLength - (self._stream.tell() - position)) 53 | 54 | i = 0 55 | indexesLength = raw.read_int32() 56 | while indexesLength > 0: 57 | position = self._stream.tell() 58 | i += 1 59 | self._obj["idText"][raw.read_int32()] = i 60 | indexesLength = (indexesLength - (self._stream.tell() - position)) 61 | 62 | for pointer, key in indexs.items(): 63 | self._stream.seek(pointer) 64 | self._obj["texts"][key] = raw.read_string().decode("utf-8") 65 | 66 | return self._obj 67 | 68 | def write(self, obj): 69 | raw = _BinaryStream(self._stream, True) 70 | 71 | indexs = OrderedDict() 72 | 73 | raw.write_int32(0) # indexes offset 74 | 75 | i = 0 76 | for key in obj["texts"]: 77 | data = {"pointer": self._stream.tell(), "diacriticalText": False, } 78 | 79 | raw.write_string(obj["texts"][key].encode()) 80 | if self.needCritical(obj["texts"][key]): 81 | data["diacriticalText"] = True 82 | data["unDiacriticalIndex"] = self._stream.tell() 83 | raw.write_string(self.unicode(obj["texts"][key].lower())) 84 | 85 | i += 1 86 | indexs[key] = data 87 | 88 | indexesSizePosition = self._stream.tell() 89 | raw.write_int32(0) # indexes size 90 | indexesPosition = self._stream.tell() 91 | 92 | for i, data in indexs.items(): 93 | raw.write_int32(data["pointer"]) 94 | raw.write_bool(data["diacriticalText"]) 95 | raw.write_int32(data["pointer"]) 96 | if data["diacriticalText"]: 97 | raw.write_int32(data["unDiacriticalIndex"]) 98 | 99 | indexesLength = (self._stream.tell() - indexesPosition) 100 | 101 | nameTextSizePosition = self._stream.tell() 102 | raw.write_int32(0) # name text size 103 | nameTextPosition = self._stream.tell() 104 | 105 | for name, key in obj["nameText"].items(): 106 | raw.write_string(name.encode()) 107 | raw.write_int32(indexs[str(key)]["pointer"]) 108 | 109 | nameTextLength = (self._stream.tell() - nameTextPosition) 110 | 111 | idTextSizePosition = self._stream.tell() 112 | raw.write_int32(0) # id text size 113 | idTextPosition = self._stream.tell() 114 | 115 | for id in obj["idText"]: 116 | raw.write_int32(int(id)) 117 | 118 | idTextLength = (self._stream.tell() - idTextPosition) 119 | EOF = self._stream.tell() 120 | 121 | self._stream.seek(0) 122 | raw.write_int32(indexesSizePosition) 123 | 124 | self._stream.seek(indexesSizePosition) 125 | raw.write_int32(indexesLength) 126 | 127 | self._stream.seek(nameTextSizePosition) 128 | raw.write_int32(nameTextLength) 129 | 130 | self._stream.seek(idTextSizePosition) 131 | raw.write_int32(idTextLength) 132 | 133 | self._stream.seek(EOF) 134 | 135 | def needCritical(self, str): 136 | return all(ord(char) < 128 for char in str) == False 137 | 138 | def unicode(self, str): 139 | return unicodedata.normalize('NFD', str).encode('ascii', 'ignore') 140 | -------------------------------------------------------------------------------- /pydofus/d2p.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from ._binarystream import _BinaryStream 5 | from collections import OrderedDict 6 | 7 | # Exceptions 8 | 9 | 10 | class InvalidD2PFile(Exception): 11 | def __init__(self, message): 12 | super(InvalidD2PFile, self).__init__(message) 13 | self.message = message 14 | 15 | # Class itself 16 | 17 | 18 | class D2PReader: 19 | """Read D2P files""" 20 | def __init__(self, stream, autoload=True): 21 | """Init the class with the informations about files in the D2P""" 22 | # Attributes 23 | self._stream = stream 24 | 25 | self._base_offset = None 26 | self._base_length = None 27 | self._indexes_offset = None 28 | self._number_indexes = None 29 | self._properties_offset = None 30 | self._number_properties = None 31 | 32 | self._properties = None 33 | 34 | self._files_position = None 35 | 36 | self._files = None 37 | 38 | self._loaded = False 39 | 40 | # Load the D2P 41 | D2P_file_binary = _BinaryStream(self._stream, True) 42 | 43 | bytes_header = D2P_file_binary.read_bytes(2) 44 | if bytes_header == b"": 45 | raise InvalidD2PFile("First bytes not found.") 46 | 47 | if bytes_header != b"\x02\x01": 48 | raise InvalidD2PFile("The first bytes don't match the" 49 | " SWL pattern.") 50 | 51 | self._stream.seek(-24, 2) # Set position to end - 24 bytes 52 | 53 | self._base_offset = D2P_file_binary.read_uint32() 54 | self._base_length = D2P_file_binary.read_uint32() 55 | self._indexes_offset = D2P_file_binary.read_uint32() 56 | self._number_indexes = D2P_file_binary.read_uint32() 57 | self._properties_offset = D2P_file_binary.read_uint32() 58 | self._number_properties = D2P_file_binary.read_uint32() 59 | 60 | if ((self._base_offset == b"" or self._base_length == b"" or 61 | self._indexes_offset == b"" or self._number_indexes == b"" or 62 | self._properties_offset == b"" or 63 | self._number_properties == b"")): 64 | raise InvalidD2PFile("The file doesn't match the D2P pattern.") 65 | 66 | self._stream.seek(self._indexes_offset, 0) 67 | 68 | # Read indexes 69 | 70 | self._files_position = OrderedDict() 71 | 72 | i = 0 73 | while i < self._number_indexes: 74 | file_name = (D2P_file_binary.read_string()).decode() 75 | offset = D2P_file_binary.read_int32() 76 | length = D2P_file_binary.read_int32() 77 | if file_name == b"" or offset == b"" or length == b"": 78 | raise InvalidD2PFile("The file appears to be corrupt.") 79 | self._files_position[file_name] = { 80 | "offset": offset + self._base_offset, 81 | "length": length 82 | } 83 | 84 | i += 1 85 | 86 | self._stream.seek(self._properties_offset, 0) 87 | 88 | # Read properties 89 | 90 | self._properties = OrderedDict() 91 | 92 | i = 0 93 | while i < self._number_properties: 94 | try: 95 | property_type = (D2P_file_binary.read_string()).decode() 96 | property_value = (D2P_file_binary.read_string()).decode() 97 | except ValueError: 98 | property_type = (D2P_file_binary.read_string()).decode('latin-1') 99 | property_value = (D2P_file_binary.read_string()).decode('latin-1') 100 | 101 | if property_type == b"" or property_value == b"": 102 | raise InvalidD2PFile("The file appears to be corrupt.") 103 | self._properties[property_type] = property_value 104 | 105 | i += 1 106 | 107 | if autoload: 108 | self.load() 109 | 110 | def load(self): 111 | """Load the class with the actual D2P files in it""" 112 | # Populate _Files 113 | 114 | if self._loaded: 115 | raise Exception("D2P instance is already populated.") 116 | 117 | D2P_file_binary = _BinaryStream(self._stream, True) 118 | 119 | self._files = OrderedDict() 120 | 121 | for file_name, position in self._files_position.items(): 122 | self._stream.seek(position["offset"], 0) 123 | 124 | self._files[file_name] = (D2P_file_binary. 125 | read_bytes(position["length"])) 126 | 127 | self._loaded = True 128 | 129 | # Accessors 130 | 131 | def _get_stream(self): 132 | return self._stream 133 | 134 | def _get_properties(self): 135 | return self._properties 136 | 137 | def _get_files(self): 138 | to_return = OrderedDict() 139 | for file_name, position in self._files_position.items(): 140 | object_ = {"position": position} 141 | if self._files: 142 | object_["binary"] = self._files[file_name] 143 | to_return[file_name] = object_ 144 | 145 | return to_return 146 | 147 | def _get_loaded(self): 148 | return self._loaded 149 | 150 | # Properties 151 | 152 | stream = property(_get_stream) 153 | properties = property(_get_properties) 154 | files = property(_get_files) 155 | loaded = property(_get_loaded) 156 | 157 | 158 | class D2PBuilder: 159 | """Build D2P files""" 160 | def __init__(self, template, target): 161 | self._template = template 162 | self._stream = target 163 | 164 | self._base_offset = None 165 | self._base_length = None 166 | self._indexes_offset = None 167 | self._number_indexes = None 168 | self._properties_offset = None 169 | self._number_properties = None 170 | 171 | self._files_position = None 172 | 173 | self._files = None 174 | self._set_files(self._template.files) # To update files and position 175 | 176 | def build(self): 177 | """Create the D2P represented by the class in the given stream.""" 178 | if self._template is None: 179 | raise RuntimeError("Template must be defined to build a D2P file") 180 | 181 | D2P_file_build_binary = _BinaryStream(self._stream, True) 182 | 183 | D2P_file_build_binary.write_bytes(b"\x02\x01") 184 | 185 | self._base_offset = self._stream.tell() 186 | 187 | for file_name, specs in self._files.items(): 188 | D2P_file_build_binary.write_bytes(specs["binary"]) 189 | 190 | self._base_length = self._stream.tell() - self._base_offset 191 | 192 | self._indexes_offset = self._stream.tell() 193 | self._number_indexes = 0 194 | 195 | for file_name, position in self._files_position.items(): 196 | D2P_file_build_binary.write_string(file_name.encode()) 197 | D2P_file_build_binary.write_int32(position["offset"]) 198 | D2P_file_build_binary.write_int32(position["length"]) 199 | self._number_indexes += 1 200 | 201 | self._properties_offset = self._stream.tell() 202 | self._number_properties = 0 203 | 204 | for ppty_type, ppty_value in self._template._properties.items(): 205 | D2P_file_build_binary.write_string(ppty_type.encode()) 206 | D2P_file_build_binary.write_string(ppty_value.encode()) 207 | self._number_properties += 1 208 | 209 | D2P_file_build_binary.write_uint32(self._base_offset) 210 | D2P_file_build_binary.write_uint32(self._base_length) 211 | D2P_file_build_binary.write_uint32(self._indexes_offset) 212 | D2P_file_build_binary.write_uint32(self._number_indexes) 213 | D2P_file_build_binary.write_uint32(self._properties_offset) 214 | D2P_file_build_binary.write_uint32(self._number_properties) 215 | 216 | # Mutators 217 | 218 | def _set_files(self, files): 219 | self._files = files 220 | self._files_position = OrderedDict() 221 | 222 | # Update positions 223 | actual_offset = 0 224 | 225 | for file_name, specs in self._files.items(): 226 | self._files_position[file_name] = { 227 | "offset": actual_offset, 228 | "length": len(specs["binary"]) 229 | } 230 | actual_offset += self._files_position[file_name]["length"] 231 | 232 | # Properties 233 | 234 | files = property(None, _set_files) 235 | -------------------------------------------------------------------------------- /pydofus/d2o.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from ._binarystream import _BinaryStream 5 | from collections import OrderedDict 6 | 7 | # Exceptions 8 | 9 | 10 | class InvalidD2OFile(Exception): 11 | def __init__(self, message): 12 | super(InvalidD2OFile, self).__init__(message) 13 | self.message = message 14 | 15 | # Class itself 16 | 17 | 18 | class D2OReader: 19 | """Read D2O files""" 20 | def __init__(self, stream): 21 | """Init the class with the informations about files in the D2P""" 22 | # Attributes 23 | self._stream = stream 24 | 25 | self._stream_start_index = 7 26 | self._classes = OrderedDict() 27 | self._counter = 0 28 | 29 | # Load the D2O 30 | D2O_file_binary = _BinaryStream(self._stream, True) 31 | self._D2O_file_binary = D2O_file_binary 32 | 33 | string_header = D2O_file_binary.read_bytes(3) 34 | base_offset = 0 35 | if string_header != b'D2O': 36 | self._stream.seek(0) 37 | string_header = D2O_file_binary.read_string() 38 | if string_header != "AKSF": 39 | raise InvalidD2OFile("Malformated game data file.") 40 | D2O_file_binary.read_short() 41 | base_offset = D2O_file_binary.read_int32() 42 | self._stream.seek(base_offset, 1) 43 | self._stream_start_index = self._stream.position + 7 44 | string_header = D2O_file_binary.read_bytes(3) 45 | if string_header != b'D2O': 46 | raise InvalidD2OFile("Malformated game data file.") 47 | 48 | offset = D2O_file_binary.read_int32() 49 | self._stream.seek(base_offset + offset) 50 | index_number = D2O_file_binary.read_int32() 51 | index = 0 52 | index_dict = OrderedDict() 53 | 54 | while index < index_number: 55 | index_id = D2O_file_binary.read_int32() 56 | offset = D2O_file_binary.read_int32() 57 | index_dict[index_id] = base_offset + offset 58 | self._counter += 1 59 | index = index + 8 60 | 61 | class_number = D2O_file_binary.read_int32() 62 | class_index = 0 63 | 64 | while class_index < class_number: 65 | class_id = D2O_file_binary.read_int32() 66 | self._read_class_definition(class_id, D2O_file_binary) 67 | class_index += 1 68 | 69 | if D2O_file_binary.bytes_available(): 70 | self._game_data_processor = _GameDataProcess(D2O_file_binary) 71 | 72 | def get_objects(self): 73 | if not self._counter: 74 | return None 75 | counter = self._counter 76 | classes = self._classes 77 | D2O_file_binary = self._D2O_file_binary 78 | D2O_file_binary.position(self._stream_start_index) 79 | objects = list() 80 | i = 0 81 | while i < counter: 82 | objects.append( 83 | classes[D2O_file_binary.read_int32()].read(D2O_file_binary)) 84 | i += 1 85 | return objects 86 | 87 | def get_class_definition(self, object_id): 88 | return self._classes[object_id] 89 | 90 | def _read_class_definition(self, class_id, D2O_file_binary): 91 | class_name = D2O_file_binary.read_string() 92 | class_pkg = D2O_file_binary.read_string() 93 | class_def = _GameDataClassDefinition(class_pkg, class_name, self) 94 | field_number = D2O_file_binary.read_int32() 95 | field_index = 0 96 | 97 | while field_index < field_number: 98 | field = D2O_file_binary.read_string() 99 | class_def.add_field(field, D2O_file_binary) 100 | field_index += 1 101 | 102 | self._classes[class_id] = class_def 103 | 104 | 105 | class _GameDataClassDefinition: 106 | def __init__(self, class_pkg, class_name, d2o_reader): 107 | self._class = class_pkg.decode('utf-8') + '.' + \ 108 | class_name.decode('utf-8') 109 | self._fields = list() 110 | self._d2o_reader = d2o_reader 111 | 112 | def fields(self): 113 | return self._fields 114 | 115 | def read(self, D2O_file_binary): 116 | obj = OrderedDict() 117 | for field in self._fields: 118 | obj[field.name] = field.read_data(D2O_file_binary) 119 | return obj 120 | 121 | def add_field(self, name, D2O_file_binary): 122 | field = _GameDataField(name, self._d2o_reader) 123 | field.read_type(D2O_file_binary) 124 | self._fields.append(field) 125 | 126 | 127 | class _GameDataField: 128 | def __init__(self, name, d2o_reader): 129 | self.name = name.decode('utf-8') 130 | self._inner_read_methods = list() 131 | self._inner_type_names = list() 132 | self._d2o_reader = d2o_reader 133 | 134 | def read_type(self, D2O_file_binary): 135 | read_id = D2O_file_binary.read_int32() 136 | self.read_data = self._get_read_method(read_id, D2O_file_binary) 137 | 138 | def _get_read_method(self, read_id, D2O_file_binary): 139 | if read_id == -1: 140 | return self._read_integer 141 | elif read_id == -2: 142 | return self._read_boolean 143 | elif read_id == -3: 144 | return self._read_string 145 | elif read_id == -4: 146 | return self._read_number 147 | elif read_id == -5: 148 | return self._read_i18n 149 | elif read_id == -6: 150 | return self._read_unsigned_integer 151 | elif read_id == -99: 152 | self._inner_type_names.append(D2O_file_binary.read_string()) 153 | self._inner_read_methods = [self._get_read_method( 154 | D2O_file_binary.read_int32(), 155 | D2O_file_binary)] + self._inner_read_methods 156 | return self._read_vector 157 | else: 158 | if read_id > 0: 159 | return self._read_object 160 | raise Exception("Unknown type \'" + read_id + "\'.") 161 | 162 | def _read_integer(self, D2O_file_binary, vec_index=0): 163 | return D2O_file_binary.read_int32() 164 | 165 | def _read_boolean(self, D2O_file_binary, vec_index=0): 166 | return D2O_file_binary.read_bool() 167 | 168 | def _read_string(self, D2O_file_binary, vec_index=0): 169 | string = D2O_file_binary.read_string() 170 | if string == 'null': 171 | string = None 172 | return string.decode('utf-8') 173 | 174 | def _read_number(self, D2O_file_binary, vec_index=0): 175 | return D2O_file_binary.read_double() 176 | 177 | def _read_i18n(self, D2O_file_binary, vec_index=0): 178 | return D2O_file_binary.read_int32() 179 | 180 | def _read_unsigned_integer(self, D2O_file_binary, vec_index=0): 181 | return D2O_file_binary.read_uint32() 182 | 183 | def _read_vector(self, D2O_file_binary, vec_index=0): 184 | vector_size = D2O_file_binary.read_int32() 185 | vector = list() 186 | i = 0 187 | while i < vector_size: 188 | vector.append(self._inner_read_methods[vec_index](D2O_file_binary, 189 | vec_index + 1)) 190 | i += 1 191 | return vector 192 | 193 | def _read_object(self, D2O_file_binary, vec_index=0): 194 | object_id = D2O_file_binary.read_int32() 195 | if object_id == -1431655766: 196 | return None 197 | obj = self._d2o_reader.get_class_definition(object_id) 198 | return obj.read(D2O_file_binary) 199 | 200 | 201 | class _GameDataProcess: 202 | def __init__(self, D2O_file_binary): 203 | self._stream = D2O_file_binary 204 | self._sort_index = OrderedDict() 205 | self._queryable_field = list() 206 | self._search_field_index = OrderedDict() 207 | self._search_field_type = OrderedDict() 208 | self._search_field_count = OrderedDict() 209 | self._parse_stream() 210 | 211 | def _parse_stream(self): 212 | length = self._stream.read_int32() 213 | off = self._stream.position() + length + 4 214 | while length: 215 | available = self._stream.bytes_available() 216 | string = self._stream.read_string() 217 | self._queryable_field.append(string) 218 | self._search_field_index[string] = self._stream.read_int32() + off 219 | self._search_field_type[string] = self._stream.read_int32() 220 | self._search_field_count[string] = self._stream.read_int32() 221 | length = length - (available - self._stream.bytes_available()) 222 | -------------------------------------------------------------------------------- /pydofus/ele.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import zlib, tempfile, io 5 | from ._binarystream import _BinaryStream 6 | from collections import OrderedDict 7 | 8 | class InvalidELEFile(Exception): 9 | def __init__(self, message): 10 | super(InvalidELEFile, self).__init__(message) 11 | self.message = message 12 | 13 | class ELE: 14 | def __init__(self, stream): 15 | self._stream = stream 16 | 17 | def read(self): 18 | ele_uncompressed = tempfile.TemporaryFile() 19 | ele_uncompressed.write(zlib.decompress(self._stream.read())) 20 | ele_uncompressed.seek(0) 21 | 22 | raw = _BinaryStream(ele_uncompressed, True) 23 | 24 | ele = Element(raw) 25 | ele.read() 26 | 27 | ele_uncompressed.close() 28 | 29 | return ele.get_dict() 30 | 31 | 32 | class Element: 33 | def __init__(self, raw): 34 | self._raw = raw 35 | self.file_version = 0 36 | self.elements_count = 0 37 | self._elements_map = OrderedDict() 38 | self._elements_index = OrderedDict() 39 | self._jpg_map = OrderedDict() 40 | 41 | def read(self): 42 | header = self._raw.read_char() 43 | if header != 69: 44 | raise InvalidELEFile("Unknown file format") 45 | self.file_version = self._raw.read_char() 46 | self.elements_count = self._raw.read_uint32() 47 | 48 | skip_len = 0 49 | for i in range(0, self.elements_count): 50 | if self.file_version >= 9: 51 | skip_len = self._raw.read_uint16() 52 | ed_id = self._raw.read_int32() 53 | if self.file_version <= 8: 54 | self._elements_index[ed_id] = self._raw.position() 55 | self._read_element(ed_id) 56 | else: 57 | self._elements_index[ed_id] = self._raw.position() 58 | self._raw.position(self._raw.position() + (skip_len - 4)) 59 | if self.file_version >= 8: 60 | gfx_count = self._raw.read_int32() 61 | for i in range(0, gfx_count): 62 | gfx_id = self._raw.read_int32() 63 | self._jpg_map[gfx_id] = True 64 | for key in self._elements_index.keys(): 65 | self._read_element(key) 66 | 67 | def get_dict(self): 68 | ret = OrderedDict() 69 | ret['file_version'] = self.file_version 70 | ret['elements_count'] = self.elements_count 71 | ret['elements_map'] = OrderedDict((key, value.get_dict()) for key, value 72 | in self._elements_map.items()) 73 | return ret 74 | 75 | def _read_element(self, ele_id): 76 | self._raw.position(self._elements_index[ele_id]) 77 | ele_type = self._raw.read_char() 78 | gfx_ele_data = _GraphicalElementFactory.get_graphical_element_data( 79 | ele_id, ele_type) 80 | if not gfx_ele_data: 81 | return None 82 | gfx_ele_data.read(self._raw, self.file_version) 83 | self._elements_map[ele_id] = gfx_ele_data 84 | return gfx_ele_data 85 | 86 | 87 | class _GraphicalElementFactory: 88 | @classmethod 89 | def get_graphical_element_data(cls, ele_id, ele_type): 90 | if ele_type == 0: 91 | return _NormalGraphicalElementData(ele_id, ele_type) 92 | elif ele_type == 1: 93 | return _BoundingBoxGraphicalElementData(ele_id, ele_type) 94 | elif ele_type == 2: 95 | return _AnimatedGraphicalElementData(ele_id, ele_type) 96 | elif ele_type == 3: 97 | return _EntityGraphicalElementData(ele_id, ele_type) 98 | elif ele_type == 4: 99 | return _ParticlesGraphicalElementData(ele_id, ele_type) 100 | elif ele_type == 5: 101 | return _BlendedGraphicalElementData(ele_id, ele_type) 102 | else: 103 | return None 104 | 105 | 106 | class _GraphicalElementData: 107 | def __init__(self, id, type): 108 | self.id = id 109 | self.type = type 110 | 111 | def get_dict(self): 112 | ret = OrderedDict() 113 | ret['id'] = self.id 114 | ret['type'] = self.type 115 | return ret 116 | 117 | 118 | class _NormalGraphicalElementData(_GraphicalElementData): 119 | def __init__(self, id, type): 120 | super(_NormalGraphicalElementData, self).__init__(id, type) 121 | self.gfx_id = 0 122 | self.height = 0 123 | self.horizontal_symetry = False 124 | self.origin = OrderedDict() 125 | self.size = OrderedDict() 126 | 127 | def read(self, raw, file_version): 128 | self.gfx_id = raw.read_int32() 129 | self.height = raw.read_char() 130 | self.horizontal_symetry = raw.read_bool() 131 | self.origin['x'] = raw.read_int16() 132 | self.origin['y'] = raw.read_int16() 133 | self.size['x'] = raw.read_int16() 134 | self.size['y'] = raw.read_int16() 135 | 136 | def get_dict(self): 137 | ret = super(_NormalGraphicalElementData, self).get_dict() 138 | ret['gfx_id'] = self.gfx_id 139 | ret['height'] = self.height 140 | ret['horizontal_symetry'] = self.horizontal_symetry 141 | ret['origin'] = OrderedDict() 142 | ret['origin']['x'] = self.origin['x'] 143 | ret['origin']['y'] = self.origin['y'] 144 | ret['size'] = OrderedDict() 145 | ret['size']['x'] = self.size['x'] 146 | ret['size']['y'] = self.size['y'] 147 | return ret 148 | 149 | 150 | class _BoundingBoxGraphicalElementData(_NormalGraphicalElementData): 151 | def __init__(self, id, type): 152 | super(_BoundingBoxGraphicalElementData, self).__init__(id, type) 153 | 154 | 155 | class _AnimatedGraphicalElementData(_NormalGraphicalElementData): 156 | def __init__(self, id, type): 157 | super(_AnimatedGraphicalElementData, self).__init__(id, type) 158 | self.min_delay = 0 159 | self.max_delay = 0 160 | 161 | def read(self, raw, file_version): 162 | super(_AnimatedGraphicalElementData, self).read(raw, file_version) 163 | if file_version >= 4: 164 | self.min_delay = raw.read_int32() 165 | self.max_delay = raw.read_int32() 166 | 167 | def get_dict(self): 168 | ret = super(_AnimatedGraphicalElementData, self).get_dict() 169 | ret['min_delay'] = self.min_delay 170 | ret['max_delay'] = self.max_delay 171 | return ret 172 | 173 | 174 | class _EntityGraphicalElementData(_GraphicalElementData): 175 | def __init__(self, id, type): 176 | super(_EntityGraphicalElementData, self).__init__(id, type) 177 | self.entity_look = "" 178 | self.horizontal_symetry = False 179 | self.play_animation = False 180 | self.play_anim_static = False 181 | self.min_delay = 0 182 | self.max_delay = 0 183 | 184 | def read(self, raw, file_version): 185 | look_length = raw.read_int32() 186 | self.entity_look = raw.read_string_bytes(look_length).decode('utf-8') 187 | self.horizontal_symetry = raw.read_bool() 188 | if file_version >= 7: 189 | self.play_animation = raw.read_bool() 190 | if file_version >= 6: 191 | self.play_anim_static = raw.read_bool() 192 | if file_version >= 5: 193 | self.min_delay = raw.read_int32() 194 | self.max_delay = raw.read_int32() 195 | 196 | def get_dict(self): 197 | ret = super(_EntityGraphicalElementData, self).get_dict() 198 | ret['entity_look'] = self.entity_look 199 | ret['horizontal_symetry'] = self.horizontal_symetry 200 | ret['play_animation'] = self.play_animation 201 | ret['play_anim_static'] = self.play_anim_static 202 | ret['min_delay'] = self.min_delay 203 | ret['max_delay'] = self.max_delay 204 | return ret 205 | 206 | 207 | class _ParticlesGraphicalElementData(_GraphicalElementData): 208 | def __init__(self, id, type): 209 | super(_ParticlesGraphicalElementData, self).__init__(id, type) 210 | self.script_id = 0 211 | 212 | def read(self, raw, file_version): 213 | self.script_id = raw.read_int16() 214 | 215 | def get_dict(self): 216 | ret = super(_ParticlesGraphicalElementData, self).get_dict() 217 | ret['script_id'] = self.script_id 218 | return ret 219 | 220 | 221 | class _BlendedGraphicalElementData(_NormalGraphicalElementData): 222 | def __init__(self, id, type): 223 | super(_BlendedGraphicalElementData, self).__init__(id, type) 224 | self.blend_mode = "" 225 | 226 | def read(self, raw, file_version): 227 | super(_BlendedGraphicalElementData, self).read(raw, file_version) 228 | mode_length = raw.read_int32() 229 | self.blend_mode = raw.read_string_bytes(mode_length).decode('utf-8') 230 | 231 | def get_dict(self): 232 | ret = super(_BlendedGraphicalElementData, self).get_dict() 233 | ret['blend_mode'] = self.blend_mode 234 | return ret 235 | -------------------------------------------------------------------------------- /pydofus/dlm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import zlib, tempfile, io 5 | from ._binarystream import _BinaryStream 6 | from collections import OrderedDict 7 | 8 | 9 | class InvalidDLMFile(Exception): 10 | def __init__(self, message): 11 | super(InvalidDLMFile, self).__init__(message) 12 | self.message = message 13 | 14 | 15 | class DLM: 16 | def __init__(self, stream, key=None): 17 | if key == None: 18 | raise InvalidDLMFile("Map decryption key is empty.") 19 | 20 | self._stream = stream 21 | self._key = key 22 | 23 | def read(self): 24 | dlm_uncompressed = tempfile.TemporaryFile() 25 | dlm_uncompressed.write(zlib.decompress(self._stream.read())) 26 | dlm_uncompressed.seek(0) 27 | 28 | DLM_file_binary = _BinaryStream(dlm_uncompressed, True) 29 | 30 | map = Map(DLM_file_binary, self._key) 31 | map.read() 32 | 33 | dlm_uncompressed.close() 34 | 35 | return map.getObj() 36 | 37 | def write(self, obj): 38 | buffer = tempfile.TemporaryFile() 39 | buffer_stream = _BinaryStream(buffer, True) 40 | 41 | map = Map(buffer_stream, self._key) 42 | map.setObj(obj) 43 | map.write() 44 | 45 | buffer.seek(0) 46 | 47 | self._stream.write(zlib.compress(buffer.read())) 48 | 49 | buffer.close() 50 | 51 | 52 | class Map: 53 | def __init__(self, raw, key): 54 | self._raw = raw 55 | self._key = key 56 | self._obj = OrderedDict() 57 | 58 | self.topArrowCell = [] 59 | self.bottomArrowCell = [] 60 | self.leftArrowCell = [] 61 | self.rightArrowCell = [] 62 | 63 | def raw(self): 64 | return self._raw 65 | 66 | def read(self): 67 | self._obj["header"] = self.raw().read_char() 68 | self._obj["mapVersion"] = self.raw().read_char() 69 | self._obj["mapId"] = self.raw().read_uint32() 70 | 71 | if self._obj["mapVersion"] >= 7: 72 | self._obj["encrypted"] = self.raw().read_bool() 73 | self._obj["encryptionVersion"] = self.raw().read_char() 74 | self.dataLen = self.raw().read_int32() 75 | 76 | if self._obj["encrypted"]: 77 | self.encryptedData = self.raw().read_bytes(self.dataLen) 78 | decryptedData = bytearray() 79 | for i in range(0, self.dataLen): 80 | decryptedData.append(self.encryptedData[i] ^ ord(self._key[i % len(self._key)])) 81 | 82 | cleanData = io.BytesIO(decryptedData) 83 | self._raw = _BinaryStream(cleanData, True) 84 | 85 | self._obj["relativeId"] = self.raw().read_uint32() 86 | self._obj["mapType"] = self.raw().read_char() 87 | self._obj["subareaId"] = self.raw().read_int32() 88 | self._obj["topNeighbourId"] = self.raw().read_int32() 89 | self._obj["bottomNeighbourId"] = self.raw().read_int32() 90 | self._obj["leftNeighbourId"] = self.raw().read_int32() 91 | self._obj["rightNeighbourId"] = self.raw().read_int32() 92 | self._obj["shadowBonusOnEntities"] = self.raw().read_uint32() 93 | 94 | if self._obj["mapVersion"] >= 9: 95 | read_color = self.raw().read_int32() 96 | self._obj["backgroundAlpha"] = (read_color & 4278190080) >> 32 97 | self._obj["backgroundRed"] = (read_color & 16711680) >> 16 98 | self._obj["backgroundGreen"] = (read_color & 65280) >> 8 99 | self._obj["backgroundBlue"] = read_color & 255 100 | read_color = self.raw().read_uint32() 101 | grid_alpha = (read_color & 4278190080) >> 32 102 | grid_red = (read_color & 16711680) >> 16 103 | grid_green = (read_color & 65280) >> 8 104 | grid_blue = read_color & 255 105 | self._obj["gridColor"] = (grid_alpha & 255) << 32 | (grid_red & 255) << 16 | (grid_green & 255) << 8 | grid_blue & 255 106 | elif self._obj["mapVersion"] >= 3: 107 | self._obj["backgroundRed"] = self.raw().read_char() 108 | self._obj["backgroundGreen"] = self.raw().read_char() 109 | self._obj["backgroundBlue"] = self.raw().read_char() 110 | 111 | self._obj["backgroundColor"] = (self._obj["backgroundRed"] & 255) << 16 | (self._obj["backgroundGreen"] & 255) << 8 | self._obj["backgroundBlue"] & 255 112 | 113 | if self._obj["mapVersion"] >= 4: 114 | self._obj["zoomScale"] = self.raw().read_uint16() / 100 115 | self._obj["zoomOffsetX"] = self.raw().read_int16() 116 | self._obj["zoomOffsetY"] = self.raw().read_int16() 117 | if self._obj["zoomScale"] < 1: 118 | self._obj["zoomScale"] = 1 119 | self._obj["zoomOffsetX"] = 0 120 | self._obj["zoomOffsetY"] = 0 121 | 122 | if self._obj["mapVersion"] > 10: 123 | self._obj["tacticalModeTemplateId"] = self.raw().read_int32() 124 | 125 | self._obj["backgroundsCount"] = self.raw().read_char() 126 | self._obj["backgroundFixtures"] = [] 127 | for i in range(0, self._obj["backgroundsCount"]): 128 | bg = Fixture(self) 129 | bg.read() 130 | self._obj["backgroundFixtures"].append(bg.getObj()) 131 | 132 | self._obj["foregroundsCount"] = self.raw().read_char() 133 | self._obj["foregroundsFixtures"] = [] 134 | for i in range(0, self._obj["foregroundsCount"]): 135 | fg = Fixture(self) 136 | fg.read() 137 | self._obj["foregroundsFixtures"].append(fg.getObj()) 138 | 139 | self.raw().read_int32() 140 | self._obj["groundCRC"] = self.raw().read_int32() 141 | self._obj["layersCount"] = self.raw().read_char() 142 | 143 | self._obj["layers"] = [] 144 | for i in range(0, self._obj["layersCount"]): 145 | la = Layer(self, self._obj["mapVersion"]) 146 | la.read() 147 | self._obj["layers"].append(la.getObj()) 148 | 149 | self._obj["cellsCount"] = 560 # MAP_CELLS_COUNT 150 | self._obj["cells"] = [] 151 | for i in range(0, self._obj["cellsCount"]): 152 | cd = CellData(self, i, self._obj["mapVersion"]) 153 | cd.read() 154 | self._obj["cells"].append(cd.getObj()) 155 | 156 | def write(self): 157 | output_stream = self._raw 158 | cleanData = io.BytesIO() #tempfile.TemporaryFile() 159 | self._raw = _BinaryStream(cleanData, True) 160 | 161 | self.raw().write_uint32(self._obj["relativeId"]) 162 | self.raw().write_char(self._obj["mapType"]) 163 | self.raw().write_int32(self._obj["subareaId"]) 164 | self.raw().write_int32(self._obj["topNeighbourId"]) 165 | self.raw().write_int32(self._obj["bottomNeighbourId"]) 166 | self.raw().write_int32(self._obj["leftNeighbourId"]) 167 | self.raw().write_int32(self._obj["rightNeighbourId"]) 168 | self.raw().write_int32(self._obj["shadowBonusOnEntities"]) 169 | 170 | if self._obj["mapVersion"] >= 9: 171 | write_color = ((self._obj["backgroundAlpha"] << 32) & 4278190080 | 172 | (self._obj["backgroundRed"] << 16) & 16711680 | 173 | (self._obj["backgroundGreen"] << 8) & 65280 | 174 | self._obj["backgroundBlue"] & 255) 175 | self.raw().write_int32(write_color) 176 | write_color = self._obj["gridColor"] 177 | self.raw().write_uint32(write_color) 178 | elif self._obj["mapVersion"] >= 3: 179 | self.raw().write_char(self._obj["backgroundRed"]) 180 | self.raw().write_char(self._obj["backgroundGreen"]) 181 | self.raw().write_char(self._obj["backgroundBlue"]) 182 | 183 | if self._obj["mapVersion"] >= 4: 184 | self.raw().write_int16(self._obj["zoomScale"]) 185 | self.raw().write_int16(self._obj["zoomOffsetX"]) 186 | self.raw().write_int16(self._obj["zoomOffsetY"]) 187 | 188 | if self._obj["mapVersion"] > 10: 189 | self.raw().write_int32(self._obj["tacticalModeTemplateId"]) 190 | 191 | self.raw().write_char(self._obj["backgroundsCount"]) 192 | for i in range(0, self._obj["backgroundsCount"]): 193 | self._obj["backgroundFixtures"][i].write() 194 | 195 | self.raw().write_char(self._obj["foregroundsCount"]) 196 | for i in range(0, self._obj["foregroundsCount"]): 197 | self._obj["foregroundsFixtures"][i].write() 198 | 199 | self.raw().write_int32(self._obj["unknown_1"]) 200 | self.raw().write_int32(self._obj["groundCRC"]) 201 | 202 | self.raw().write_char(self._obj["layersCount"]) 203 | for i in range(0, self._obj["layersCount"]): 204 | self._obj["layers"][i].write() 205 | 206 | for i in range(0, self._obj["cellsCount"]): 207 | self._obj["cells"][i].write() 208 | 209 | input_stram = self._raw 210 | self._raw = output_stream 211 | cleanData.seek(0) 212 | 213 | self.raw().write_char(self._obj["header"]) 214 | self.raw().write_char(self._obj["mapVersion"]) 215 | self.raw().write_int32(self._obj["mapId"]) 216 | 217 | if self._obj["mapVersion"] >= 7: 218 | self.raw().write_bool(self._obj["encrypted"]) 219 | self.raw().write_char(self._obj["encryptionVersion"]) 220 | self.raw().write_int32(len(cleanData.getbuffer())) # TODO: check len with getBuffer 221 | encryptedData = input_stram.read_bytes() 222 | for i in range(0, len(cleanData.getbuffer())): 223 | self.raw().write_uchar(encryptedData[i] ^ ord(self._key[i % len(self._key)][0])) 224 | else: 225 | self.raw().write_bytes(input_stram.read_bytes()) 226 | 227 | def getObj(self): 228 | return self._obj 229 | 230 | def setObj(self, obj): 231 | self._obj = obj 232 | 233 | for i in range(0, self._obj["backgroundsCount"]): 234 | bg = Fixture(self) 235 | ce.setObj(self._obj["backgroundFixtures"][i]) 236 | self._obj["backgroundFixtures"][i] = bg 237 | 238 | for i in range(0, self._obj["foregroundsCount"]): 239 | fg = Fixture(self) 240 | fg.setObj(self._obj["foregroundsFixtures"][i]) 241 | self._obj["foregroundsFixtures"][i] = fg 242 | 243 | for i in range(0, self._obj["layersCount"]): 244 | la = Layer(self, self._obj["mapVersion"]) 245 | la.setObj(self._obj["layers"][i]) 246 | self._obj["layers"][i] = la 247 | 248 | for i in range(0, self._obj["cellsCount"]): 249 | ce = CellData(self, i, self._obj["mapVersion"]) 250 | ce.setObj(self._obj["cells"][i]) 251 | self._obj["cells"][i] = ce 252 | 253 | 254 | class Fixture: 255 | def __init__(self, parrent): 256 | self._parrent = parrent 257 | self._obj = OrderedDict() 258 | 259 | def raw(self): 260 | return self._parrent.raw() 261 | 262 | def read(self): 263 | self._obj["fixtureId"] = self.raw().read_int32() 264 | self._obj["offsetX"] = self.raw().read_int16() 265 | self._obj["offsetY"] = self.raw().read_int16() 266 | self._obj["rotation"] = self.raw().read_int16() 267 | self._obj["xScale"] = self.raw().read_int16() 268 | self._obj["yScale"] = self.raw().read_int16() 269 | self._obj["redMultiplier"] = self.raw().read_char() 270 | self._obj["greenMultiplier"] = self.raw().read_char() 271 | self._obj["blueMultiplier"] = self.raw().read_char() 272 | self._obj["hue"] = self._obj["redMultiplier"] | self._obj["greenMultiplier"] | self._obj["blueMultiplier"] 273 | self._obj["alpha"] = self.raw().read_uchar() 274 | 275 | def write(self): 276 | self.raw().write_int32(self._obj["fixtureId"]) 277 | self.raw().write_int16(self._obj["offsetX"]) 278 | self.raw().write_int16(self._obj["offsetY"]) 279 | self.raw().write_int16(self._obj["rotation"]) 280 | self.raw().write_int16(self._obj["xScale"]) 281 | self.raw().write_int16(self._obj["yScale"]) 282 | self.raw().write_char(self._obj["redMultiplier"]) 283 | self.raw().write_char(self._obj["greenMultiplier"]) 284 | self.raw().write_char(self._obj["blueMultiplier"]) 285 | self.raw().write_uchar(self._obj["alpha"]) 286 | 287 | def getObj(self): 288 | return self._obj 289 | 290 | def setObj(self, obj): 291 | self._obj = obj 292 | 293 | 294 | class Layer: 295 | def __init__(self, parrent, mapVersion): 296 | self._parrent = parrent 297 | self._obj = OrderedDict() 298 | self.mapVersion = mapVersion 299 | 300 | def raw(self): 301 | return self._parrent.raw() 302 | 303 | def read(self): 304 | if self.mapVersion >= 9: 305 | self._obj["layerId"] = self.raw().read_char() 306 | else: 307 | self._obj["layerId"] = self.raw().read_int32() 308 | self._obj["cellsCount"] = self.raw().read_int16() 309 | self._obj["cells"] = [] 310 | for i in range(0, self._obj["cellsCount"]): 311 | ce = Cell(self, self.mapVersion) 312 | ce.read() 313 | self._obj["cells"].append(ce.getObj()) 314 | 315 | def write(self): 316 | if self.mapVersion >= 9: 317 | self.raw().write_byte(self._obj["layerId"]) 318 | else: 319 | self.raw().write_int32(self._obj["layerId"]) 320 | self.raw().write_int16(self._obj["cellsCount"]) 321 | for i in range(0, self._obj["cellsCount"]): 322 | self._obj["cells"][i].write() 323 | 324 | def getObj(self): 325 | return self._obj 326 | 327 | def setObj(self, obj): 328 | self._obj = obj 329 | 330 | for i in range(0, self._obj["cellsCount"]): 331 | ce = Cell(self, self.mapVersion) 332 | ce.setObj(self._obj["cells"][i]) 333 | self._obj["cells"][i] = ce 334 | 335 | 336 | class Cell: 337 | def __init__(self, parrent, mapVersion): 338 | self._parrent = parrent 339 | self._obj = OrderedDict() 340 | self.mapVersion = mapVersion 341 | 342 | def raw(self): 343 | return self._parrent.raw() 344 | 345 | def read(self): 346 | self._obj["cellId"] = self.raw().read_int16() 347 | self._obj["elementsCount"] = self.raw().read_int16() 348 | self._obj["elements"] = [] 349 | for i in range(0, self._obj["elementsCount"]): 350 | el = BasicElement().GetElementFromType(self, self.raw().read_char(), self.mapVersion) 351 | el.read() 352 | self._obj["elements"].append(el.getObj()) 353 | 354 | def write(self): 355 | self.raw().write_int16(self._obj["cellId"]) 356 | self.raw().write_int16(self._obj["elementsCount"]) 357 | 358 | for i in range(0, self._obj["elementsCount"]): 359 | if self._obj["elements"][i]._obj["elementName"] == "Graphical": 360 | self.raw().write_char(2) 361 | elif self._obj["elements"][i]._obj["elementName"] == "Sound": 362 | self.raw().write_char(33) 363 | else: 364 | raise InvalidDLMFile("Invalid element type.") 365 | 366 | self._obj["elements"][i].write() 367 | 368 | def getObj(self): 369 | return self._obj 370 | 371 | def setObj(self, obj): 372 | self._obj = obj 373 | 374 | for i in range(0, self._obj["elementsCount"]): 375 | if self._obj["elements"][i]["elementName"] == "Graphical": 376 | el = GraphicalElement(self, self.mapVersion) 377 | elif self._obj["elements"][i]["elementName"] == "Sound": 378 | el = SoundElement(self, self.mapVersion) 379 | else: 380 | raise InvalidDLMFile("Invalid element type.") 381 | 382 | el.setObj(self._obj["elements"][i]) 383 | self._obj["elements"][i] = el 384 | 385 | 386 | class CellData: 387 | def __init__(self, parrent, id, mapVersion): 388 | self._parrent = parrent 389 | self._obj = OrderedDict() 390 | self.cellId = id 391 | self.mapVersion = mapVersion 392 | 393 | def raw(self): 394 | return self._parrent.raw() 395 | 396 | def read(self): 397 | self._obj["floor"] = self.raw().read_char() * 10 398 | if self._obj["floor"] == -1280: 399 | return 400 | if self.mapVersion >= 9: 401 | tmp_bytes = self.raw().read_int16() 402 | self._obj["mov"] = (tmp_bytes & 1) == 0 403 | self._obj["nonWalkableDuringFight"] = (tmp_bytes & 2) != 0 404 | self._obj["nonWalkableDuringRP"] = (tmp_bytes & 4) != 0 405 | self._obj["los"] = (tmp_bytes & 8) == 0 406 | self._obj["blue"] = (tmp_bytes & 16) != 0 407 | self._obj["red"] = (tmp_bytes & 32) != 0 408 | self._obj["visible"] = (tmp_bytes & 64) != 0 409 | self._obj["farmCell"] = (tmp_bytes & 128) != 0 410 | if self.mapVersion >= 10: 411 | self._obj["havenbagCell"] = (tmp_bytes & 256) != 0 412 | top_arrow = (tmp_bytes & 512) != 0 413 | bottom_arrow = (tmp_bytes & 1024) != 0 414 | right_arrow = (tmp_bytes & 2048) != 0 415 | left_arrow = (tmp_bytes & 4096) != 0 416 | else: 417 | top_arrow = (tmp_bytes & 256) != 0 418 | bottom_arrow = (tmp_bytes & 512) != 0 419 | right_arrow = (tmp_bytes & 1024) != 0 420 | left_arrow = (tmp_bytes & 2048) != 0 421 | if top_arrow: 422 | self._parrent.topArrowCell.append(self.cellId) 423 | if bottom_arrow: 424 | self._parrent.bottomArrowCell.append(self.cellId) 425 | if right_arrow: 426 | self._parrent.rightArrowCell.append(self.cellId) 427 | if left_arrow: 428 | self._parrent.leftArrowCell.append(self.cellId) 429 | else: 430 | self._obj["losmov"] = self.raw().read_uchar() 431 | self._obj["los"] = (self._obj["losmov"] & 2) >> 1 == 1 432 | self._obj["mov"] = (self._obj["losmov"] & 1) == 1 433 | self._obj["visible"] = (self._obj["losmov"] & 64) >> 6 == 1 434 | self._obj["farmCell"] = (self._obj["losmov"] & 32) >> 5 == 1 435 | self._obj["blue"] = (self._obj["losmov"] & 16) >> 4 == 1 436 | self._obj["red"] = (self._obj["losmov"] & 8) >> 3 == 1 437 | self._obj["nonWalkableDuringRP"] = (self._obj["losmov"] & 128) >> 7 == 1 438 | self._obj["nonWalkableDuringFight"] = (self._obj["losmov"] & 4) 439 | self._obj["speed"] = self.raw().read_char() 440 | self._obj["mapChangeData"] = self.raw().read_char() 441 | 442 | if self.mapVersion > 5: 443 | self._obj["moveZone"] = self.raw().read_uchar() 444 | 445 | if self.mapVersion > 10 and (self.hasLinkedZoneRP() or self.hasLinkedZoneFight()): 446 | self._obj["_linkedZone"] = self.raw().read_uchar() 447 | 448 | if self.mapVersion > 7 and self.mapVersion < 9: 449 | self._obj["tmpBits"] = self.raw().read_char() 450 | self.arrow = 15 & self._obj["tmpBits"] 451 | 452 | if self.useTopArrow(): 453 | self._parrent.topArrowCell.append(self.cellId) 454 | 455 | if self.useBottomArrow(): 456 | self._parrent.bottomArrowCell.append(self.cellId) 457 | 458 | if self.useLeftArrow(): 459 | self._parrent.leftArrowCell.append(self.cellId) 460 | 461 | if self.useRightArrow(): 462 | self._parrent.rightArrowCell.append(self.cellId) 463 | 464 | def write(self): 465 | if self._obj["floor"] == -1280: 466 | return 467 | self.raw().write_char(self._obj["floor"]) 468 | if self.mapVersion >= 9: 469 | tmp_bytes = self.raw().read_int16() 470 | tmp_bytes |= 1 if self._obj["mov"] else 0 471 | tmp_bytes |= 2 if self._obj["nonWalkableDuringFight"] else 0 472 | tmp_bytes |= 4 if self._obj["nonWalkableDuringRP"] else 0 473 | tmp_bytes |= 8 if self._obj["los"] else 0 474 | tmp_bytes |= 16 if self._obj["blue"] else 0 475 | tmp_bytes |= 32 if self._obj["red"] else 0 476 | tmp_bytes |= 64 if self._obj["visible"] else 0 477 | tmp_bytes |= 128 if self._obj["farmCell"] else 0 478 | if self.mapVersion >= 10: 479 | tmp_bytes |= 256 if self._obj["havenbagCell"] else 0 480 | self.raw().write_int16(tmp_bytes) 481 | else: 482 | self.raw().write_uchar(self._obj["losmov"]) 483 | self.raw().write_char(self._obj["speed"]) 484 | self.raw().write_uchar(self._obj["mapChangeData"]) 485 | 486 | if self.mapVersion > 5: 487 | self.raw().write_uchar(self._obj["moveZone"]) 488 | if self.mapVersion > 7 and self.mapVersion < 9: 489 | self.raw().write_char(self._obj["tmpBits"]) 490 | 491 | def getObj(self): 492 | return self._obj 493 | 494 | def setObj(self, obj): 495 | self._obj = obj 496 | 497 | def useTopArrow(self): 498 | if (self.arrow & 1) == 0: 499 | return False 500 | else: 501 | return True 502 | 503 | def useBottomArrow(self): 504 | if (self.arrow & 2) == 0: 505 | return False 506 | else: 507 | return True 508 | 509 | def useLeftArrow(self): 510 | if (self.arrow & 4) == 0: 511 | return False 512 | else: 513 | return True 514 | 515 | def useRightArrow(self): 516 | if (self.arrow & 8) == 0: 517 | return False 518 | else: 519 | return True 520 | 521 | def hasLinkedZoneRP(self): 522 | return self._obj["mov"] and not self._obj["farmCell"] 523 | 524 | def hasLinkedZoneFight(self): 525 | return self._obj["mov"] \ 526 | and not self._obj["nonWalkableDuringFight"]\ 527 | and not self._obj["farmCell"]\ 528 | and not self._obj["havenbagCell"] 529 | 530 | 531 | class BasicElement: 532 | def GetElementFromType(self, parrent, type, mapVersion): 533 | if type == 2: # GRAPHICAL 534 | return GraphicalElement(parrent, mapVersion) 535 | elif type == 33: # SOUND 536 | return SoundElement(parrent, mapVersion) 537 | else: 538 | raise InvalidDLMFile("Invalid element type.") 539 | 540 | 541 | class GraphicalElement: 542 | def __init__(self, parrent, mapVersion): 543 | self._parrent = parrent 544 | self._obj = OrderedDict() 545 | self.mapVersion = mapVersion 546 | self._obj["elementName"] = "Graphical" 547 | 548 | def raw(self): 549 | return self._parrent.raw() 550 | 551 | def read(self): 552 | self._obj["elementId"] = self.raw().read_uint32() 553 | 554 | # hue 555 | self._obj["hue_1"] = self.raw().read_char() 556 | self._obj["hue_2"] = self.raw().read_char() 557 | self._obj["hue_3"] = self.raw().read_char() 558 | 559 | # shadow 560 | self._obj["shadow_1"] = self.raw().read_char() 561 | self._obj["shadow_2"] = self.raw().read_char() 562 | self._obj["shadow_3"] = self.raw().read_char() 563 | 564 | if self.mapVersion <= 4: 565 | self._obj["offsetX"] = self.raw().read_char() 566 | self._obj["offsetY"] = self.raw().read_char() 567 | else: 568 | self._obj["offsetX"] = self.raw().read_int16() 569 | self._obj["offsetY"] = self.raw().read_int16() 570 | 571 | self._obj["altitude"] = self.raw().read_char() 572 | self._obj["identifier"] = self.raw().read_uint32() 573 | 574 | def write(self): 575 | self.raw().write_uint32(self._obj["elementId"]) 576 | self.raw().write_char(self._obj["hue_1"]) 577 | self.raw().write_char(self._obj["hue_2"]) 578 | self.raw().write_char(self._obj["hue_3"]) 579 | self.raw().write_char(self._obj["shadow_1"]) 580 | self.raw().write_char(self._obj["shadow_2"]) 581 | self.raw().write_char(self._obj["shadow_3"]) 582 | 583 | if self.mapVersion <= 4: 584 | self.raw().write_char(self._obj["offsetX"]) 585 | self.raw().write_char(self._obj["offsetY"]) 586 | else: 587 | self.raw().write_int16(self._obj["offsetX"]) 588 | self.raw().write_int16(self._obj["offsetY"]) 589 | 590 | self.raw().write_char(self._obj["altitude"]) 591 | self.raw().write_uint32(self._obj["identifier"]) 592 | 593 | def getObj(self): 594 | return self._obj 595 | 596 | def setObj(self, obj): 597 | self._obj = obj 598 | 599 | 600 | class SoundElement: 601 | def __init__(self, parrent, mapVersion): 602 | self._parrent = parrent 603 | self._obj = OrderedDict() 604 | self.mapVersion = mapVersion 605 | self._obj["elementName"] = "Sound" 606 | 607 | def raw(self): 608 | return self._parrent.raw() 609 | 610 | def read(self): 611 | self._obj["soundId"] = self.raw().read_int32() 612 | self._obj["baseVolume"] = self.raw().read_int16() 613 | self._obj["fullVolumeDistance"] = self.raw().read_int32() 614 | self._obj["nullVolumeDistance"] = self.raw().read_int32() 615 | self._obj["minDelayBetweenLoops"] = self.raw().read_int16() 616 | self._obj["maxDelayBetweenLoops"] = self.raw().read_int16() 617 | 618 | def write(self): 619 | self.raw().write_int32(self._obj["soundId"]) 620 | self.raw().write_int16(self._obj["baseVolume"]) 621 | self.raw().write_int32(self._obj["fullVolumeDistance"]) 622 | self.raw().write_int32(self._obj["nullVolumeDistance"]) 623 | self.raw().write_int16(self._obj["minDelayBetweenLoops"]) 624 | self.raw().write_int16(self._obj["maxDelayBetweenLoops"]) 625 | 626 | def getObj(self): 627 | return self._obj 628 | 629 | def setObj(self, obj): 630 | self._obj = obj 631 | --------------------------------------------------------------------------------