hacktools 0.38.7__cp314-cp314t-win_amd64.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.
hacktools/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.38.7"
hacktools/arch.py ADDED
@@ -0,0 +1,255 @@
1
+ import os
2
+ from collections import Counter
3
+ from hacktools import common
4
+
5
+
6
+ class ARCHArchive:
7
+ def __init__(self):
8
+ self.filenum = 0
9
+ self.tableoff = 0
10
+ self.fatoff = 0
11
+ self.nameindexoff = 0
12
+ self.dataoff = 0
13
+ self.files = []
14
+
15
+
16
+ class ARCHFile:
17
+ def __init__(self):
18
+ self.name = ""
19
+ self.length = 0
20
+ self.declength = 0
21
+ self.offset = 0
22
+ self.nameoffset = 0
23
+ self.encoded = False
24
+
25
+
26
+ def read(f):
27
+ f.seek(4) # Magic: ARCH
28
+ archive = ARCHArchive()
29
+ archive.filenum = f.readUInt()
30
+ archive.tableoff = f.readUInt()
31
+ archive.fatoff = f.readUInt()
32
+ archive.nameindexoff = f.readUInt()
33
+ archive.dataoff = f.readUInt()
34
+ common.logDebug("Archive:", vars(archive))
35
+ for i in range(archive.filenum):
36
+ f.seek(archive.fatoff + i * 16)
37
+ subfile = ARCHFile()
38
+ subfile.length = f.readUInt()
39
+ subfile.declength = f.readUInt()
40
+ subfile.offset = f.readUInt()
41
+ subfile.nameoffset = f.readUShort()
42
+ subfile.encoded = f.readUShort() == 1
43
+ f.seek(archive.tableoff + subfile.nameoffset)
44
+ subfile.name = f.readNullString()
45
+ common.logDebug("File", i, vars(subfile))
46
+ archive.files.append(subfile)
47
+ return archive
48
+
49
+
50
+ def repack(fin, f, archive, infolder):
51
+ # Copy everything up to dataoff
52
+ fin.seek(0)
53
+ f.seek(0)
54
+ f.write(fin.read(archive.dataoff))
55
+ # Loop the files
56
+ dataoff = 0
57
+ for i in range(archive.filenum):
58
+ subfile = archive.files[i]
59
+ filepath = infolder + subfile.name
60
+ if not os.path.isfile(filepath):
61
+ # Just update the offset and copy the file
62
+ f.seek(archive.fatoff + i * 16)
63
+ f.seek(8, 1)
64
+ f.writeUInt(dataoff)
65
+ fin.seek(archive.dataoff + subfile.offset)
66
+ f.seek(archive.dataoff + dataoff)
67
+ f.write(fin.read(subfile.length))
68
+ else:
69
+ size = os.path.getsize(filepath)
70
+ f.seek(archive.fatoff + i * 16)
71
+ # If the file was not compressed, just copy it
72
+ if not subfile.encoded:
73
+ f.writeUInt(size)
74
+ f.writeUInt(size)
75
+ f.writeUInt(dataoff)
76
+ f.seek(2, 1)
77
+ f.writeUShort(0)
78
+ f.seek(archive.dataoff + dataoff)
79
+ with common.Stream(filepath, "rb") as subf:
80
+ f.write(subf.read())
81
+ else:
82
+ common.logDebug("Compressing", subfile.name)
83
+ with common.Stream(filepath, "rb") as subf:
84
+ filedata = subf.read()
85
+ compdata = compress(filedata)
86
+ f.writeUInt(len(compdata))
87
+ f.writeUInt(size)
88
+ f.writeUInt(dataoff)
89
+ f.seek(2, 1)
90
+ f.writeUShort(1)
91
+ f.seek(archive.dataoff + dataoff)
92
+ f.write(compdata)
93
+ # Align with 0s
94
+ if f.tell() % 16 > 0:
95
+ f.writeZero(16 - (f.tell() % 16))
96
+ dataoff = f.tell() - archive.dataoff
97
+
98
+
99
+ def extract(f, archive, outfolder):
100
+ for subfile in archive.files:
101
+ with common.Stream(outfolder + subfile.name, "wb") as fout:
102
+ f.seek(archive.dataoff + subfile.offset)
103
+ if not subfile.encoded:
104
+ fout.write(f.read(subfile.length))
105
+ else:
106
+ fout.write(decompress(f.read(subfile.length), subfile.declength))
107
+
108
+
109
+ def compress(data):
110
+ # Find unused bytes in the data
111
+ dictkeys = []
112
+ for i in range(1, 0x100):
113
+ dictkeys.append(i)
114
+ for b in data:
115
+ if b in dictkeys:
116
+ dictkeys.remove(b)
117
+ dictvalues = {}
118
+ # Recursively find the most used pair and replace it in the copied data
119
+ content = bytearray(data)
120
+ while True:
121
+ if len(dictkeys) == 0:
122
+ break
123
+ # Write all the pairs in a list, for simplicity will just stick to halfwords
124
+ allpairs = []
125
+ for i in range(len(content) // 2):
126
+ allpairs.append((content[i], content[i+1]))
127
+ # Find the most common one
128
+ c = Counter(allpairs).most_common(1)
129
+ if len(c) < 1:
130
+ break
131
+ pair = c[0]
132
+ if pair[1] < 4:
133
+ break
134
+ dictkey = dictkeys.pop()
135
+ common.logDebug("setting pair", common.toHex(pair[0][0]), common.toHex(pair[0][1]), "with", pair[1], "occurrences as dict key", common.toHex(dictkey))
136
+ dictvalues[dictkey] = pair[0]
137
+ content = content.replace(bytes(pair[0]), bytes([dictkey]))
138
+ with common.Stream() as f:
139
+ # Write the dictionary values
140
+ currentkey = 0
141
+ ordkeys = list(dictvalues.keys())
142
+ ordkeys.sort()
143
+ isconsecutive = False
144
+ # Special case where there are no dict keys
145
+ if len(ordkeys) == 0:
146
+ f.writeByte(0x7f + 0x7f)
147
+ f.writeByte(0x7f)
148
+ f.writeByte(0x7f + 0x7f)
149
+ f.writeByte(0xff)
150
+ currentkey = 0x100
151
+ else:
152
+ for i in range(len(ordkeys)):
153
+ dictkey = ordkeys[i]
154
+ common.logDebug("Writing key", common.toHex(dictkey))
155
+ # If the key is not consecutive, we need to skip places
156
+ if dictkey > currentkey:
157
+ keydiff = dictkey - currentkey
158
+ # Since we can only skip 0x7f bytes, we need to do an additional skip if it's bigger
159
+ while keydiff > 0x7f:
160
+ f.writeByte(0x7f + 0x7f)
161
+ # Also write a byte equal to the index
162
+ f.writeByte(0x7f)
163
+ keydiff -= 0x80
164
+ f.writeByte(keydiff + 0x7f)
165
+ currentkey = dictkey
166
+ isconsecutive = False
167
+ elif not isconsecutive:
168
+ # If this is the first time we're writing a key, we need to check how many consecutive ones there are
169
+ consecutive = 1
170
+ for j in range(i+1, len(ordkeys)):
171
+ if ordkeys[j] == dictkey + consecutive:
172
+ consecutive += 1
173
+ f.writeByte(consecutive - 1)
174
+ isconsecutive = True
175
+ common.logDebug("Writing key pairs", common.toHex(dictvalues[dictkey][0]), common.toHex(dictvalues[dictkey][1]))
176
+ f.writeByte(dictvalues[dictkey][0])
177
+ # Don't write the 2nd byte if it's the same as the index (shouldn't happen)
178
+ if dictvalues[dictkey][1] != dictkey:
179
+ f.writeByte(dictvalues[dictkey][1])
180
+ currentkey += 1
181
+ # We're forced to write all indexes even if they aren't used
182
+ if currentkey < 0x100:
183
+ f.writeByte(0x100 - currentkey - 1)
184
+ while currentkey < 0x100:
185
+ f.writeByte(currentkey)
186
+ currentkey += 1
187
+ # Write the actual content
188
+ numloopspos = f.tell()
189
+ f.writeByte(0)
190
+ f.writeByte(0)
191
+ numloops = 0
192
+ for b in content:
193
+ f.writeByte(b)
194
+ numloops += 1
195
+ f.seek(numloopspos)
196
+ f.writeByte(numloops >> 8)
197
+ f.writeByte(numloops & 0xff)
198
+ f.seek(0)
199
+ return f.read()
200
+
201
+
202
+ def decompress(data, declen):
203
+ with common.Stream() as f:
204
+ with common.Stream() as fout:
205
+ f.write(data)
206
+ f.seek(0)
207
+ # Based on Tinke's ARCH implementation
208
+ buffer1 = []
209
+ buffer2 = []
210
+ for i in range(0x100):
211
+ buffer1.append(0)
212
+ buffer2.append(0)
213
+ while f.tell() < len(data):
214
+ # InitBuffer
215
+ for i in range(0x100):
216
+ buffer2[i] = i
217
+ # FillBuffer
218
+ index = 0
219
+ while index != 0x100:
220
+ bufid = f.readByte()
221
+ numloops = bufid
222
+ if bufid > 0x7f:
223
+ numloops = 0
224
+ index += bufid - 0x7f
225
+ if index == 0x100:
226
+ break
227
+ if numloops < 0:
228
+ continue
229
+ for i in range(numloops + 1):
230
+ byte = f.readByte()
231
+ buffer2[index] = byte
232
+ if byte != index:
233
+ buffer1[index] = f.readByte()
234
+ index += 1
235
+ # Process
236
+ numloops = (f.readByte() << 8) + f.readByte()
237
+ common.logDebug("Decompressing with", common.toHex(numloops), "loops starting at", common.toHex(f.tell()))
238
+ nextsamples = []
239
+ while True:
240
+ if len(nextsamples) == 0:
241
+ if numloops == 0:
242
+ break
243
+ numloops -= 1
244
+ index = f.readByte()
245
+ else:
246
+ index = nextsamples.pop()
247
+ if buffer2[index] == index:
248
+ fout.writeByte(index)
249
+ else:
250
+ nextsamples.append(buffer1[index])
251
+ nextsamples.append(buffer2[index])
252
+ index = len(nextsamples)
253
+ common.logDebug("Finished at", common.toHex(f.tell()), "with numloops", common.toHex(numloops))
254
+ fout.seek(0)
255
+ return fout.read()
Binary file
Binary file
Binary file
Binary file