hacktools 0.38.7__cp314-cp314-win32.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 +1 -0
- hacktools/arch.py +255 -0
- hacktools/assets/icon.png +0 -0
- hacktools/cmp_cri.cp314-win32.pyd +0 -0
- hacktools/cmp_lzss.cp314-win32.pyd +0 -0
- hacktools/cmp_misc.cp314-win32.pyd +0 -0
- hacktools/cmp_racjin.cp314-win32.pyd +0 -0
- hacktools/common.py +1505 -0
- hacktools/compression.py +216 -0
- hacktools/cpk.py +643 -0
- hacktools/gb.py +92 -0
- hacktools/gui.py +304 -0
- hacktools/nasm.py +84 -0
- hacktools/nds.py +367 -0
- hacktools/nitro.py +1776 -0
- hacktools/psp.py +1257 -0
- hacktools/psx.py +295 -0
- hacktools/wii.py +285 -0
- hacktools/ws.py +564 -0
- hacktools-0.38.7.dist-info/METADATA +112 -0
- hacktools-0.38.7.dist-info/RECORD +24 -0
- hacktools-0.38.7.dist-info/WHEEL +5 -0
- hacktools-0.38.7.dist-info/licenses/LICENSE +21 -0
- hacktools-0.38.7.dist-info/top_level.txt +1 -0
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
|
|
Binary file
|