turbine-lib 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- turbine_lib/__init__.py +0 -0
- turbine_lib/binarydata.py +133 -0
- turbine_lib/database.py +148 -0
- turbine_lib/datexportapi.py +307 -0
- turbine_lib/datfile.py +338 -0
- turbine_lib/ddssubfile.py +305 -0
- turbine_lib/font_convert.py +353 -0
- turbine_lib/fontsubfile.py +34 -0
- turbine_lib/libs/datexport.dll +0 -0
- turbine_lib/libs/msvcp71.dll +0 -0
- turbine_lib/libs/msvcp90.dll +0 -0
- turbine_lib/libs/msvcr71.dll +0 -0
- turbine_lib/libs/zlib1T.dll +0 -0
- turbine_lib/subfile.py +88 -0
- turbine_lib/subfiledata.py +30 -0
- turbine_lib/textsubfile.py +382 -0
- turbine_lib/textutils.py +29 -0
- turbine_lib/utils.py +117 -0
- turbine_lib-0.1.0.dist-info/METADATA +10 -0
- turbine_lib-0.1.0.dist-info/RECORD +22 -0
- turbine_lib-0.1.0.dist-info/WHEEL +5 -0
- turbine_lib-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
from subfile import Subfile, FILE_TYPE
|
|
2
|
+
from binarydata import BinaryData
|
|
3
|
+
from subfiledata import SubfileData
|
|
4
|
+
import textutils
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TextFragment:
|
|
8
|
+
"""Represents a text fragment in a text subfile"""
|
|
9
|
+
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.fragment_id = 0
|
|
12
|
+
self.text = ""
|
|
13
|
+
self.args = ""
|
|
14
|
+
|
|
15
|
+
def __lt__(self, other):
|
|
16
|
+
return self.fragment_id < other.fragment_id
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TextSubfile(Subfile):
|
|
20
|
+
"""Implementation of Subfile for TEXT type files"""
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def build_for_export(file_data):
|
|
24
|
+
result = SubfileData()
|
|
25
|
+
offset = 9 # first 4 bytes - file_id, then 4 bytes - unknown, then 1 byte - unknown
|
|
26
|
+
|
|
27
|
+
text_fragment_num = file_data.to_number(1, offset)
|
|
28
|
+
if (text_fragment_num & 0x80) != 0:
|
|
29
|
+
text_fragment_num = (((text_fragment_num ^ 0x80) << 8) | file_data[offset + 1])
|
|
30
|
+
offset += 1
|
|
31
|
+
offset += 1
|
|
32
|
+
|
|
33
|
+
for i in range(text_fragment_num):
|
|
34
|
+
fragment_id = file_data.to_number(8, offset)
|
|
35
|
+
offset += 8
|
|
36
|
+
|
|
37
|
+
# Making pieces
|
|
38
|
+
pieces, offset = TextSubfile.make_pieces(file_data, offset)
|
|
39
|
+
text = "["
|
|
40
|
+
for j in range(len(pieces) - 1):
|
|
41
|
+
text += pieces[j] + "<--DO_NOT_TOUCH!-->"
|
|
42
|
+
if pieces:
|
|
43
|
+
text += pieces[-1]
|
|
44
|
+
text += "]"
|
|
45
|
+
|
|
46
|
+
# Making argument references
|
|
47
|
+
arg_refs, offset = TextSubfile.make_argument_references(file_data, offset)
|
|
48
|
+
arguments = ""
|
|
49
|
+
for j in range(len(arg_refs) - 1):
|
|
50
|
+
arguments += textutils.to_utf16(arg_refs[j]) + "-"
|
|
51
|
+
if arg_refs:
|
|
52
|
+
arguments += textutils.to_utf16(arg_refs[-1])
|
|
53
|
+
|
|
54
|
+
# Through argument strings are not used, we need to call this function to correctly move offset
|
|
55
|
+
_result, offset = TextSubfile.make_argument_strings(file_data, offset)
|
|
56
|
+
|
|
57
|
+
if len(result.text_data) > 0:
|
|
58
|
+
result.text_data += "|||"
|
|
59
|
+
|
|
60
|
+
result.text_data += textutils.to_utf16(fragment_id) + ":::"
|
|
61
|
+
result.text_data += arguments + ":::"
|
|
62
|
+
result.text_data += text
|
|
63
|
+
|
|
64
|
+
result.options = {"ext": ".txt"}
|
|
65
|
+
return result
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def build_for_import(old_data, data):
|
|
69
|
+
new_file_data = BinaryData()
|
|
70
|
+
offset = 9 # first 8 bytes - file_info. After them:
|
|
71
|
+
# first 4 bytes - file_id, then 4 bytes - unknown, then 1 byte - unknown
|
|
72
|
+
|
|
73
|
+
text_fragment_num = old_data.to_number(1, offset)
|
|
74
|
+
if (text_fragment_num & 0x80) != 0:
|
|
75
|
+
text_fragment_num = (((text_fragment_num ^ 0x80) << 8) | old_data[offset + 1])
|
|
76
|
+
offset += 1
|
|
77
|
+
offset += 1
|
|
78
|
+
|
|
79
|
+
# Adding file info
|
|
80
|
+
new_file_data = new_file_data + old_data.cut_data(0, offset)
|
|
81
|
+
|
|
82
|
+
patch_fragments = TextSubfile.parse_patch_fragments(data)
|
|
83
|
+
|
|
84
|
+
for i in range(text_fragment_num):
|
|
85
|
+
fragment_id = old_data.to_number(8, offset)
|
|
86
|
+
offset += 8
|
|
87
|
+
|
|
88
|
+
new_file_data = new_file_data + BinaryData.from_number(8, fragment_id)
|
|
89
|
+
|
|
90
|
+
id_comp = TextFragment()
|
|
91
|
+
id_comp.fragment_id = fragment_id
|
|
92
|
+
|
|
93
|
+
# Find matching fragment
|
|
94
|
+
fragment_iterator = None
|
|
95
|
+
for frag in patch_fragments:
|
|
96
|
+
if frag.fragment_id == id_comp.fragment_id:
|
|
97
|
+
fragment_iterator = frag
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
if fragment_iterator is None:
|
|
101
|
+
# Retrieving old pieces
|
|
102
|
+
result_data, offset = TextSubfile.get_piece_data(old_data, offset)
|
|
103
|
+
new_file_data = new_file_data + result_data
|
|
104
|
+
# Retrieving old references
|
|
105
|
+
result_data, offset = TextSubfile.get_argument_reference_data(old_data, offset)
|
|
106
|
+
new_file_data = new_file_data + result_data
|
|
107
|
+
# Retrieving old ref_strings
|
|
108
|
+
result_data, offset = TextSubfile.get_argument_strings_data(old_data, offset)
|
|
109
|
+
new_file_data = new_file_data + result_data
|
|
110
|
+
else:
|
|
111
|
+
# Making and adding new pieces
|
|
112
|
+
result_data, offset = TextSubfile.build_pieces(old_data, fragment_iterator, offset)
|
|
113
|
+
new_file_data = new_file_data + result_data
|
|
114
|
+
# Making and adding new references
|
|
115
|
+
result_data, offset = TextSubfile.build_argument_references(old_data, fragment_iterator, offset)
|
|
116
|
+
new_file_data = new_file_data + result_data
|
|
117
|
+
# Making and adding new strings
|
|
118
|
+
result_data, offset = TextSubfile.build_argument_strings(old_data, fragment_iterator, offset)
|
|
119
|
+
new_file_data = new_file_data + result_data
|
|
120
|
+
|
|
121
|
+
# Adding elapsed file data
|
|
122
|
+
new_file_data = new_file_data + old_data.cut_data(offset)
|
|
123
|
+
|
|
124
|
+
return new_file_data
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def parse_patch_fragments(data):
|
|
128
|
+
result = []
|
|
129
|
+
pointer = 0
|
|
130
|
+
|
|
131
|
+
text_data = data.text_data
|
|
132
|
+
while pointer < len(text_data):
|
|
133
|
+
# Parsing fragment_id
|
|
134
|
+
pointer1 = text_data.find(":::", pointer)
|
|
135
|
+
if pointer1 == -1:
|
|
136
|
+
break
|
|
137
|
+
|
|
138
|
+
fragment_id = textutils.from_utf16(text_data[pointer:pointer1])
|
|
139
|
+
pointer = pointer1 + 3
|
|
140
|
+
|
|
141
|
+
fragment = TextFragment()
|
|
142
|
+
fragment.fragment_id = fragment_id
|
|
143
|
+
|
|
144
|
+
# Parsing arguments
|
|
145
|
+
pointer1 = text_data.find(":::", pointer)
|
|
146
|
+
if pointer1 == -1:
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
arguments = text_data[pointer:pointer1]
|
|
150
|
+
pointer = pointer1 + 3
|
|
151
|
+
|
|
152
|
+
if len(arguments) > 0:
|
|
153
|
+
fragment.args = textutils.arguments_from_utf16(arguments)
|
|
154
|
+
|
|
155
|
+
# Parsing text
|
|
156
|
+
pointer1 = text_data.find("|||", pointer)
|
|
157
|
+
if pointer1 == -1:
|
|
158
|
+
pointer1 = len(text_data)
|
|
159
|
+
|
|
160
|
+
fragment.text = text_data[pointer:pointer1]
|
|
161
|
+
pointer = pointer1 + 3
|
|
162
|
+
|
|
163
|
+
result.append(fragment)
|
|
164
|
+
|
|
165
|
+
result.sort(key=lambda x: x.fragment_id)
|
|
166
|
+
return result
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def make_pieces(data, offset):
|
|
170
|
+
# This is a simplified implementation - would need to be expanded based on actual C++ code
|
|
171
|
+
result = []
|
|
172
|
+
|
|
173
|
+
num_pieces = data.to_number(4, offset)
|
|
174
|
+
offset += 4
|
|
175
|
+
|
|
176
|
+
for j in range(num_pieces):
|
|
177
|
+
piece_size = data.to_number(1, offset)
|
|
178
|
+
if (piece_size & 128) != 0:
|
|
179
|
+
piece_size = (((piece_size ^ 128) << 8) | data[offset + 1])
|
|
180
|
+
offset += 1
|
|
181
|
+
offset += 1
|
|
182
|
+
|
|
183
|
+
piece_data = data.cut_data(offset, offset + piece_size * 2)
|
|
184
|
+
piece = ""
|
|
185
|
+
|
|
186
|
+
for k in range(piece_size):
|
|
187
|
+
c = (piece_data[2 * k + 1] << 8) # First byte
|
|
188
|
+
c |= piece_data[2 * k] # Second byte
|
|
189
|
+
piece += chr(c)
|
|
190
|
+
|
|
191
|
+
result.append(piece)
|
|
192
|
+
offset += piece_size * 2
|
|
193
|
+
|
|
194
|
+
return result, offset
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def make_argument_references(data, offset):
|
|
198
|
+
result = []
|
|
199
|
+
|
|
200
|
+
num_references = data.to_number(4, offset)
|
|
201
|
+
offset += 4
|
|
202
|
+
|
|
203
|
+
for j in range(num_references):
|
|
204
|
+
result.append(data.to_number(4, offset))
|
|
205
|
+
offset += 4
|
|
206
|
+
|
|
207
|
+
return result, offset
|
|
208
|
+
|
|
209
|
+
@staticmethod
|
|
210
|
+
def make_argument_strings(data, offset):
|
|
211
|
+
result = []
|
|
212
|
+
|
|
213
|
+
num_arg_strings = data.to_number(1, offset)
|
|
214
|
+
offset += 1
|
|
215
|
+
|
|
216
|
+
for j in range(num_arg_strings):
|
|
217
|
+
num_args = data.to_number(4, offset)
|
|
218
|
+
offset += 4
|
|
219
|
+
|
|
220
|
+
result.append([])
|
|
221
|
+
for k in range(num_args):
|
|
222
|
+
string_size = data.to_number(1, offset)
|
|
223
|
+
if (string_size & 0x80) != 0:
|
|
224
|
+
string_size = (((string_size ^ 0x80) << 8) | data[offset + 1])
|
|
225
|
+
offset += 1
|
|
226
|
+
offset += 1
|
|
227
|
+
|
|
228
|
+
result[j].append(data.cut_data(offset, offset + string_size * 2))
|
|
229
|
+
offset += string_size * 2
|
|
230
|
+
|
|
231
|
+
return result, offset
|
|
232
|
+
|
|
233
|
+
@staticmethod
|
|
234
|
+
def build_pieces(data, new_data, offset):
|
|
235
|
+
# Moving offset pointer in data
|
|
236
|
+
old_offset = offset
|
|
237
|
+
num_pieces = data.to_number(4, offset)
|
|
238
|
+
offset += 4
|
|
239
|
+
|
|
240
|
+
for j in range(num_pieces):
|
|
241
|
+
piece_size = data.to_number(1, offset)
|
|
242
|
+
if (piece_size & 128) != 0:
|
|
243
|
+
piece_size = (((piece_size ^ 128) << 8) | data[offset + 1])
|
|
244
|
+
offset += 1
|
|
245
|
+
offset += 1
|
|
246
|
+
offset += piece_size * 2
|
|
247
|
+
|
|
248
|
+
# Deleting '[' and ']' brackets
|
|
249
|
+
text_data = new_data.text[1:-1] if len(new_data.text) > 2 else ""
|
|
250
|
+
|
|
251
|
+
text_pieces = []
|
|
252
|
+
|
|
253
|
+
DNT = "<--DO_NOT_TOUCH!-->"
|
|
254
|
+
prev = 0
|
|
255
|
+
next_pos = text_data.find(DNT, prev)
|
|
256
|
+
|
|
257
|
+
while next_pos != -1:
|
|
258
|
+
piece = " " if next_pos - prev == 0 else text_data[prev:next_pos]
|
|
259
|
+
text_pieces.append(piece)
|
|
260
|
+
prev = next_pos + len(DNT)
|
|
261
|
+
next_pos = text_data.find(DNT, prev)
|
|
262
|
+
|
|
263
|
+
text_pieces.append(text_data[prev:])
|
|
264
|
+
|
|
265
|
+
# Building BinaryData from pieces
|
|
266
|
+
result_data = BinaryData()
|
|
267
|
+
result_data = result_data + BinaryData.from_number(4, len(text_pieces))
|
|
268
|
+
|
|
269
|
+
for piece in text_pieces:
|
|
270
|
+
piece_size = len(piece)
|
|
271
|
+
if piece_size < 128:
|
|
272
|
+
result_data = result_data + BinaryData.from_number(1, piece_size)
|
|
273
|
+
else:
|
|
274
|
+
result_data = result_data + BinaryData.from_number_raw(2, (piece_size | 32768))
|
|
275
|
+
|
|
276
|
+
for j in range(piece_size):
|
|
277
|
+
result_data = result_data + BinaryData.from_number(2, ord(piece[j]))
|
|
278
|
+
|
|
279
|
+
return result_data, offset
|
|
280
|
+
|
|
281
|
+
@staticmethod
|
|
282
|
+
def build_argument_references(data, new_data, offset):
|
|
283
|
+
# Moving offset pointer in data
|
|
284
|
+
old_offset = offset
|
|
285
|
+
num_references = data.to_number(4, offset)
|
|
286
|
+
offset += 4
|
|
287
|
+
offset += 4 * num_references
|
|
288
|
+
|
|
289
|
+
# If there are no args - making 4 null-bytes and return;
|
|
290
|
+
if not new_data.args:
|
|
291
|
+
result = BinaryData.from_number(4, 0)
|
|
292
|
+
return result, offset
|
|
293
|
+
|
|
294
|
+
# Parsing arguments from list in options["args"]
|
|
295
|
+
args_list = new_data.args
|
|
296
|
+
argument_references = []
|
|
297
|
+
|
|
298
|
+
prev = 0
|
|
299
|
+
next_pos = args_list.find('-', prev)
|
|
300
|
+
while next_pos != -1:
|
|
301
|
+
argument = args_list[prev:next_pos]
|
|
302
|
+
argument_references.append(int(argument))
|
|
303
|
+
prev = next_pos + 1
|
|
304
|
+
next_pos = args_list.find('-', prev)
|
|
305
|
+
|
|
306
|
+
argument = args_list[prev:]
|
|
307
|
+
argument_references.append(int(argument))
|
|
308
|
+
|
|
309
|
+
result = BinaryData()
|
|
310
|
+
result = result + BinaryData.from_number(4, len(argument_references))
|
|
311
|
+
for arg_reference in argument_references:
|
|
312
|
+
result = result + BinaryData.from_number(4, arg_reference)
|
|
313
|
+
|
|
314
|
+
return result, offset
|
|
315
|
+
|
|
316
|
+
@staticmethod
|
|
317
|
+
def build_argument_strings(data, new_data, offset):
|
|
318
|
+
# TODO: IMPLEMENT (never user)
|
|
319
|
+
# Moving offset pointer in data
|
|
320
|
+
old_offset = offset
|
|
321
|
+
num_arg_strings = data.to_number(1, offset)
|
|
322
|
+
offset += 1
|
|
323
|
+
|
|
324
|
+
for j in range(num_arg_strings):
|
|
325
|
+
num_args = data.to_number(4, offset)
|
|
326
|
+
offset += 4
|
|
327
|
+
|
|
328
|
+
for k in range(num_args):
|
|
329
|
+
string_size = data.to_number(1, offset)
|
|
330
|
+
if (string_size & 0x80) != 0:
|
|
331
|
+
string_size = (((string_size ^ 0x80) << 8) | data[offset + 1])
|
|
332
|
+
offset += 1
|
|
333
|
+
offset += 1
|
|
334
|
+
offset += string_size * 2
|
|
335
|
+
|
|
336
|
+
return BinaryData.from_number(1, 0), offset
|
|
337
|
+
|
|
338
|
+
@staticmethod
|
|
339
|
+
def get_piece_data(data, offset):
|
|
340
|
+
old_offset = offset
|
|
341
|
+
|
|
342
|
+
num_pieces = data.to_number(4, offset)
|
|
343
|
+
offset += 4
|
|
344
|
+
|
|
345
|
+
for j in range(num_pieces):
|
|
346
|
+
piece_size = data.to_number(1, offset)
|
|
347
|
+
if (piece_size & 128) != 0:
|
|
348
|
+
piece_size = (((piece_size ^ 128) << 8) | data[offset + 1])
|
|
349
|
+
offset += 1
|
|
350
|
+
offset += 1
|
|
351
|
+
offset += piece_size * 2
|
|
352
|
+
|
|
353
|
+
return data.cut_data(old_offset, offset), offset
|
|
354
|
+
|
|
355
|
+
@staticmethod
|
|
356
|
+
def get_argument_reference_data(data, offset):
|
|
357
|
+
old_offset = offset
|
|
358
|
+
num_references = data.to_number(4, offset)
|
|
359
|
+
offset += 4
|
|
360
|
+
offset += 4 * num_references
|
|
361
|
+
return data.cut_data(old_offset, offset), offset
|
|
362
|
+
|
|
363
|
+
@staticmethod
|
|
364
|
+
def get_argument_strings_data(data, offset):
|
|
365
|
+
old_offset = offset
|
|
366
|
+
|
|
367
|
+
num_arg_strings = data.to_number(1, offset)
|
|
368
|
+
offset += 1
|
|
369
|
+
|
|
370
|
+
for j in range(num_arg_strings):
|
|
371
|
+
num_args = data.to_number(4, offset)
|
|
372
|
+
offset += 4
|
|
373
|
+
|
|
374
|
+
for k in range(num_args):
|
|
375
|
+
string_size = data.to_number(1, offset)
|
|
376
|
+
if (string_size & 0x80) != 0:
|
|
377
|
+
string_size = (((string_size ^ 0x80) << 8) | data[offset + 1])
|
|
378
|
+
offset += 1
|
|
379
|
+
offset += 1
|
|
380
|
+
offset += string_size * 2
|
|
381
|
+
|
|
382
|
+
return data.cut_data(old_offset, offset), offset
|
turbine_lib/textutils.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
def to_utf16(x):
|
|
2
|
+
"""Convert integer to UTF-16 string"""
|
|
3
|
+
if x == 0:
|
|
4
|
+
return chr(0x30) # '0' character
|
|
5
|
+
res = ""
|
|
6
|
+
while x > 0:
|
|
7
|
+
res += chr(0x30 + x % 10) # '0' + digit
|
|
8
|
+
x //= 10
|
|
9
|
+
return res[::-1] # Reverse string
|
|
10
|
+
|
|
11
|
+
def from_utf16(num_str):
|
|
12
|
+
"""Convert UTF-16 string to integer"""
|
|
13
|
+
res = 0
|
|
14
|
+
for c in num_str:
|
|
15
|
+
res = res * 10 + (ord(c) - 0x30) # digit - '0'
|
|
16
|
+
return res
|
|
17
|
+
|
|
18
|
+
def arguments_from_utf16(args_str):
|
|
19
|
+
"""Parse arguments from UTF-16 string"""
|
|
20
|
+
if not args_str:
|
|
21
|
+
return ""
|
|
22
|
+
|
|
23
|
+
parts = args_str.split('-')
|
|
24
|
+
result_parts = []
|
|
25
|
+
for part in parts:
|
|
26
|
+
if part: # Skip empty parts
|
|
27
|
+
result_parts.append(str(from_utf16(part)))
|
|
28
|
+
|
|
29
|
+
return '-'.join(result_parts)
|
turbine_lib/utils.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
def compare_files_byte_by_byte(file1_path, file2_path):
|
|
2
|
+
"""
|
|
3
|
+
比较两个文件的每个字节是否完全相同
|
|
4
|
+
|
|
5
|
+
Args:
|
|
6
|
+
file1_path (str): 第一个文件路径
|
|
7
|
+
file2_path (str): 第二个文件路径
|
|
8
|
+
|
|
9
|
+
Returns:
|
|
10
|
+
bool: 如果两个文件完全相同返回True,否则返回False
|
|
11
|
+
"""
|
|
12
|
+
try:
|
|
13
|
+
with open(file1_path, 'rb') as file1, open(file2_path, 'rb') as file2:
|
|
14
|
+
# 按块读取文件进行比较,避免大文件占用过多内存
|
|
15
|
+
while True:
|
|
16
|
+
chunk1 = file1.read(8192) # 每次读取8KB
|
|
17
|
+
chunk2 = file2.read(8192)
|
|
18
|
+
|
|
19
|
+
# 如果两个文件都到达末尾,则文件相同
|
|
20
|
+
if not chunk1 and not chunk2:
|
|
21
|
+
return True
|
|
22
|
+
|
|
23
|
+
# 如果其中一个文件结束而另一个没有,则文件不同
|
|
24
|
+
if not chunk1 or not chunk2:
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
# 如果当前块不相同,则文件不同
|
|
28
|
+
if chunk1 != chunk2:
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
except FileNotFoundError as e:
|
|
32
|
+
print(f"文件未找到: {e}")
|
|
33
|
+
return False
|
|
34
|
+
except Exception as e:
|
|
35
|
+
print(f"比较文件时出错: {e}")
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def compare_files_detailed(file1_path, file2_path):
|
|
40
|
+
"""
|
|
41
|
+
比较两个文件的每个字节,并提供详细的差异信息
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
file1_path (str): 第一个文件路径
|
|
45
|
+
file2_path (str): 第二个文件路径
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
dict: 包含比较结果和详细信息的字典
|
|
49
|
+
"""
|
|
50
|
+
result = {
|
|
51
|
+
'same': False,
|
|
52
|
+
'size1': 0,
|
|
53
|
+
'size2': 0,
|
|
54
|
+
'first_diff_pos': -1,
|
|
55
|
+
'error': None
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
with open(file1_path, 'rb') as file1, open(file2_path, 'rb') as file2:
|
|
60
|
+
# 获取文件大小
|
|
61
|
+
file1.seek(0, 2) # 移动到文件末尾
|
|
62
|
+
file2.seek(0, 2)
|
|
63
|
+
result['size1'] = file1.tell()
|
|
64
|
+
result['size2'] = file2.tell()
|
|
65
|
+
|
|
66
|
+
# 重置文件指针
|
|
67
|
+
file1.seek(0)
|
|
68
|
+
file2.seek(0)
|
|
69
|
+
|
|
70
|
+
# 如果文件大小不同,则肯定不相同
|
|
71
|
+
if result['size1'] != result['size2']:
|
|
72
|
+
return result
|
|
73
|
+
|
|
74
|
+
position = 0
|
|
75
|
+
while True:
|
|
76
|
+
byte1 = file1.read(1)
|
|
77
|
+
byte2 = file2.read(1)
|
|
78
|
+
|
|
79
|
+
# 如果两个文件都到达末尾,则文件相同
|
|
80
|
+
if not byte1 and not byte2:
|
|
81
|
+
result['same'] = True
|
|
82
|
+
break
|
|
83
|
+
|
|
84
|
+
# 如果字节不同,记录位置并退出
|
|
85
|
+
if byte1 != byte2:
|
|
86
|
+
result['first_diff_pos'] = position
|
|
87
|
+
break
|
|
88
|
+
|
|
89
|
+
position += 1
|
|
90
|
+
|
|
91
|
+
except FileNotFoundError as e:
|
|
92
|
+
result['error'] = f"文件未找到: {e}"
|
|
93
|
+
except Exception as e:
|
|
94
|
+
result['error'] = f"比较文件时出错: {e}"
|
|
95
|
+
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
def compare_files(file_path1, file_path2):
|
|
99
|
+
# 简单比较
|
|
100
|
+
are_same = compare_files_byte_by_byte(file_path1, file_path2)
|
|
101
|
+
print(f"文件是否相同: {are_same}")
|
|
102
|
+
|
|
103
|
+
# 详细比较
|
|
104
|
+
result = compare_files_detailed(file_path1, file_path2)
|
|
105
|
+
if result['error']:
|
|
106
|
+
print(f"错误: {result['error']}")
|
|
107
|
+
elif result['same']:
|
|
108
|
+
print("文件完全相同")
|
|
109
|
+
else:
|
|
110
|
+
if result['size1'] != result['size2']:
|
|
111
|
+
print(f"文件大小不同: {result['size1']} vs {result['size2']}")
|
|
112
|
+
else:
|
|
113
|
+
print(f"文件大小相同但内容不同,第一个差异在位置: {result['first_diff_pos']}")
|
|
114
|
+
if __name__ == '__main__':
|
|
115
|
+
# 简单比较
|
|
116
|
+
compare_files(r'./font/standard/1107296298.fontbin', r'./font/output_17-11-2025-14-01/1107296298.fontbin')
|
|
117
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: turbine_lib
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 仅支持Windows 32位Python的库
|
|
5
|
+
Classifier: Programming Language :: Python :: 3
|
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
8
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
9
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
10
|
+
Requires-Python: <3.13,>=3.7
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
turbine_lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
turbine_lib/binarydata.py,sha256=lu1XSkqGIyLASJPbbf0UJuZ-E2rrLw0EC6Oq7P8DeBs,4505
|
|
3
|
+
turbine_lib/database.py,sha256=4a_Zzcdr_O5-GZK8M2FcmyA06TqYkGiaLZafQv7jRRU,5112
|
|
4
|
+
turbine_lib/datexportapi.py,sha256=680sjmTDXq8WIEIEuIs48wm6BfhF4JaOgXnT_ep_h6Q,12745
|
|
5
|
+
turbine_lib/datfile.py,sha256=-2LM1hwHymfk8RkhqJEO18bq-iD__MIa3yKGaODMYys,13218
|
|
6
|
+
turbine_lib/ddssubfile.py,sha256=k1afrh7aZb03wgHis8M7mRSxAE9o4UsQaEyOI21QliQ,10399
|
|
7
|
+
turbine_lib/font_convert.py,sha256=tuIkq84h6BOSUDMDLRVBK6PV_OulSrbziII9o2hWYzA,12954
|
|
8
|
+
turbine_lib/fontsubfile.py,sha256=gezfd9-iloZ6YHF_mUYijnKYTj_PtpJHvW61qIj5uf8,930
|
|
9
|
+
turbine_lib/subfile.py,sha256=AYJaJR9FYUcLKaUpHgHu4HXU6m81S4BVb9naugws4ko,2767
|
|
10
|
+
turbine_lib/subfiledata.py,sha256=JJJedNBOOtSa_UoztXwqDrnykXDi79BwLuXg0b95VmE,1068
|
|
11
|
+
turbine_lib/textsubfile.py,sha256=ClS5XtbAHNhErcBvgqPxGmxqp9voK0Hajcqw30nDBQk,13310
|
|
12
|
+
turbine_lib/textutils.py,sha256=JcJKd4gjdvR023wTMSCOhyTWs5_kHp3W1sAY0uMPe1Q,759
|
|
13
|
+
turbine_lib/utils.py,sha256=DlQWk15XqDsQYASL7pD6TB5JO8gXyyyu_AJ16ombKn8,3856
|
|
14
|
+
turbine_lib/libs/datexport.dll,sha256=ycZSi4e7juJSVU49Ap23IQ49jXY1uzU2MvX7uRdJha8,238896
|
|
15
|
+
turbine_lib/libs/msvcp71.dll,sha256=35YVb2pUj9b-VnKRjeWuRQnTyBCle__SqR3kWj7Vsjs,499712
|
|
16
|
+
turbine_lib/libs/msvcp90.dll,sha256=BpGM-ZrSbNbPEGiBwNW9shLcC6xFSYBcn1kG49A9FSw,569680
|
|
17
|
+
turbine_lib/libs/msvcr71.dll,sha256=gJSvXuMQcUyuvMru53af-wgEhQO6R4uHnt_vXxok_v4,348160
|
|
18
|
+
turbine_lib/libs/zlib1T.dll,sha256=fiPxt97AbyHE_GVYpIDVldSFPUFjV8xVQRRkhxwenBQ,63000
|
|
19
|
+
turbine_lib-0.1.0.dist-info/METADATA,sha256=NSZ0syR4Jn9hhqNkET6FbyZHFIcRqYm3-pFwBlfuPBM,417
|
|
20
|
+
turbine_lib-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
+
turbine_lib-0.1.0.dist-info/top_level.txt,sha256=LVLiMIuAzr6r6OUWftVdJcXucgUlW6Y6XWw4gUoUWQI,12
|
|
22
|
+
turbine_lib-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
turbine_lib
|