hacktools 0.37.1__tar.gz → 0.38.1__tar.gz

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.
Files changed (34) hide show
  1. {hacktools-0.37.1/hacktools.egg-info → hacktools-0.38.1}/PKG-INFO +2 -2
  2. {hacktools-0.37.1 → hacktools-0.38.1}/README.md +1 -1
  3. hacktools-0.38.1/hacktools/__init__.py +1 -0
  4. hacktools-0.38.1/hacktools/arch.py +255 -0
  5. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/psp.py +1 -1
  6. {hacktools-0.37.1 → hacktools-0.38.1/hacktools.egg-info}/PKG-INFO +2 -2
  7. hacktools-0.37.1/hacktools/__init__.py +0 -1
  8. hacktools-0.37.1/hacktools/arch.py +0 -137
  9. {hacktools-0.37.1 → hacktools-0.38.1}/LICENSE +0 -0
  10. {hacktools-0.37.1 → hacktools-0.38.1}/MANIFEST.in +0 -0
  11. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/assets/icon.png +0 -0
  12. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/c_ext/cmp_cri.c +0 -0
  13. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/c_ext/cmp_lzss.c +0 -0
  14. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/c_ext/cmp_misc.c +0 -0
  15. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/c_ext/cmp_racjin.c +0 -0
  16. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/c_ext/inc.h +0 -0
  17. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/common.py +0 -0
  18. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/compression.py +0 -0
  19. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/cpk.py +0 -0
  20. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/gb.py +0 -0
  21. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/gui.py +0 -0
  22. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/nasm.py +0 -0
  23. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/nds.py +0 -0
  24. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/nitro.py +0 -0
  25. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/psx.py +0 -0
  26. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/wii.py +0 -0
  27. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools/ws.py +0 -0
  28. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools.egg-info/SOURCES.txt +0 -0
  29. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools.egg-info/dependency_links.txt +0 -0
  30. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools.egg-info/requires.txt +0 -0
  31. {hacktools-0.37.1 → hacktools-0.38.1}/hacktools.egg-info/top_level.txt +0 -0
  32. {hacktools-0.37.1 → hacktools-0.38.1}/setup.cfg +0 -0
  33. {hacktools-0.37.1 → hacktools-0.38.1}/setup.py +0 -0
  34. {hacktools-0.37.1 → hacktools-0.38.1}/tests/test_cmp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: hacktools
3
- Version: 0.37.1
3
+ Version: 0.38.1
4
4
  Summary: A set of utilities and tools for rom hacking and translations.
5
5
  Home-page: https://github.com/Illidanz/hacktools
6
6
  Author: Illidan
@@ -104,7 +104,7 @@ External dependencies not included are marked as `(through *dependency*)`
104
104
  - BRFNT fonts (through [brfnt2tpl](https://wiki.tockdom.com/wiki/Brfnt2tpl) and [wimgt](https://szs.wiimm.de))
105
105
  ### Other / Generic
106
106
  - CPK archives
107
- - ARCH archives
107
+ - ARCH archives (including compression/decompression)
108
108
  - LZ10, LZ11, Huffman, CRILAYLA and RACJIN compression/decompression
109
109
  - ARM/MIPS binary patching
110
110
  - xdelta patch creation
@@ -46,7 +46,7 @@ External dependencies not included are marked as `(through *dependency*)`
46
46
  - BRFNT fonts (through [brfnt2tpl](https://wiki.tockdom.com/wiki/Brfnt2tpl) and [wimgt](https://szs.wiimm.de))
47
47
  ### Other / Generic
48
48
  - CPK archives
49
- - ARCH archives
49
+ - ARCH archives (including compression/decompression)
50
50
  - LZ10, LZ11, Huffman, CRILAYLA and RACJIN compression/decompression
51
51
  - ARM/MIPS binary patching
52
52
  - xdelta patch creation
@@ -0,0 +1 @@
1
+ __version__ = "0.38.1"
@@ -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()
@@ -181,7 +181,7 @@ def searchUMD(f, filename, path, lba, length):
181
181
  found = searchUMD(f, filename, newpath, newlba, newlen)
182
182
  if found != 0:
183
183
  return found
184
- elif newpath.lower() == filename.lower():
184
+ elif newpath.lower() == filename.lower() or newpath.lower() == filename.lower() + ".":
185
185
  return 0x800 * (lba + i) + pos
186
186
  return 0
187
187
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: hacktools
3
- Version: 0.37.1
3
+ Version: 0.38.1
4
4
  Summary: A set of utilities and tools for rom hacking and translations.
5
5
  Home-page: https://github.com/Illidanz/hacktools
6
6
  Author: Illidan
@@ -104,7 +104,7 @@ External dependencies not included are marked as `(through *dependency*)`
104
104
  - BRFNT fonts (through [brfnt2tpl](https://wiki.tockdom.com/wiki/Brfnt2tpl) and [wimgt](https://szs.wiimm.de))
105
105
  ### Other / Generic
106
106
  - CPK archives
107
- - ARCH archives
107
+ - ARCH archives (including compression/decompression)
108
108
  - LZ10, LZ11, Huffman, CRILAYLA and RACJIN compression/decompression
109
109
  - ARM/MIPS binary patching
110
110
  - xdelta patch creation
@@ -1 +0,0 @@
1
- __version__ = "0.37.1"
@@ -1,137 +0,0 @@
1
- import os
2
- from hacktools import common
3
-
4
-
5
- class ARCHArchive:
6
- def __init__(self):
7
- self.filenum = 0
8
- self.tableoff = 0
9
- self.fatoff = 0
10
- self.nameindexoff = 0
11
- self.dataoff = 0
12
- self.files = []
13
-
14
-
15
- class ARCHFile:
16
- def __init__(self):
17
- self.name = ""
18
- self.length = 0
19
- self.declength = 0
20
- self.offset = 0
21
- self.nameoffset = 0
22
- self.encoded = False
23
-
24
-
25
- def read(f):
26
- f.seek(4) # Magic: ARCH
27
- archive = ARCHArchive()
28
- archive.filenum = f.readUInt()
29
- archive.tableoff = f.readUInt()
30
- archive.fatoff = f.readUInt()
31
- archive.nameindexoff = f.readUInt()
32
- archive.dataoff = f.readUInt()
33
- common.logDebug("Archive:", vars(archive))
34
- for i in range(archive.filenum):
35
- f.seek(archive.fatoff + i * 16)
36
- subfile = ARCHFile()
37
- subfile.length = f.readUInt()
38
- subfile.declength = f.readUInt()
39
- subfile.offset = f.readUInt()
40
- subfile.nameoffset = f.readUShort()
41
- subfile.encoded = f.readUShort() == 1
42
- f.seek(archive.tableoff + subfile.nameoffset)
43
- subfile.name = f.readNullString()
44
- common.logDebug("File", i, vars(subfile))
45
- archive.files.append(subfile)
46
- return archive
47
-
48
-
49
- def repack(fin, f, archive, infolder):
50
- # Copy everything up to dataoff
51
- fin.seek(0)
52
- f.seek(0)
53
- f.write(fin.read(archive.dataoff))
54
- # Loop the files
55
- dataoff = 0
56
- for i in range(archive.filenum):
57
- subfile = archive.files[i]
58
- filepath = infolder + subfile.name
59
- if not os.path.isfile(filepath):
60
- # Just update the offset and copy the file
61
- f.seek(archive.fatoff + i * 16)
62
- f.seek(8, 1)
63
- f.writeUInt(dataoff)
64
- fin.seek(archive.dataoff + subfile.offset)
65
- f.seek(archive.dataoff + dataoff)
66
- f.write(fin.read(subfile.length))
67
- else:
68
- # Set the file as not encoded and copy it
69
- size = os.path.getsize(filepath)
70
- f.seek(archive.fatoff + i * 16)
71
- f.writeUInt(size)
72
- f.writeUInt(size)
73
- f.writeUInt(dataoff)
74
- f.seek(2, 1)
75
- f.writeUShort(0)
76
- f.seek(archive.dataoff + dataoff)
77
- with common.Stream(filepath, "rb") as subf:
78
- f.write(subf.read())
79
- # Align with 0s
80
- if f.tell() % 16 > 0:
81
- f.writeZero(16 - (f.tell() % 16))
82
- dataoff = f.tell() - archive.dataoff
83
-
84
-
85
- def extract(f, archive, outfolder):
86
- for subfile in archive.files:
87
- with common.Stream(outfolder + subfile.name, "wb") as fout:
88
- f.seek(archive.dataoff + subfile.offset)
89
- if not subfile.encoded:
90
- fout.write(f.read(subfile.length))
91
- else:
92
- # Based on Tinke's ARCH implementation
93
- startpos = f.tell()
94
- buffer1 = []
95
- buffer2 = []
96
- for i in range(0x100):
97
- buffer1.append(0)
98
- buffer2.append(0)
99
- while f.tell() - startpos < subfile.length:
100
- # InitBuffer
101
- for i in range(0x100):
102
- buffer2[i] = i
103
- # FillBuffer
104
- index = 0
105
- while index != 0x100:
106
- bufid = f.readByte()
107
- numloops = bufid
108
- if bufid > 0x7f:
109
- numloops = 0
110
- index += bufid - 0x7f
111
- if index == 0x100:
112
- break
113
- if numloops < 0:
114
- continue
115
- for i in range(numloops + 1):
116
- byte = f.readByte()
117
- buffer2[index] = byte
118
- if byte != index:
119
- buffer1[index] = f.readByte()
120
- index += 1
121
- # Process
122
- numloops = (f.readByte() << 8) + f.readByte()
123
- nextsamples = []
124
- while True:
125
- if len(nextsamples) == 0:
126
- if numloops == 0:
127
- break
128
- numloops -= 1
129
- index = f.readByte()
130
- else:
131
- index = nextsamples.pop()
132
- if buffer2[index] == index:
133
- fout.writeByte(index)
134
- else:
135
- nextsamples.append(buffer1[index])
136
- nextsamples.append(buffer2[index])
137
- index = len(nextsamples)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes