hacktools 0.39.0__tar.gz → 0.40.0__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 (33) hide show
  1. {hacktools-0.39.0/hacktools.egg-info → hacktools-0.40.0}/PKG-INFO +1 -1
  2. hacktools-0.40.0/hacktools/__init__.py +1 -0
  3. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/psp.py +162 -0
  4. {hacktools-0.39.0 → hacktools-0.40.0/hacktools.egg-info}/PKG-INFO +1 -1
  5. hacktools-0.39.0/hacktools/__init__.py +0 -1
  6. {hacktools-0.39.0 → hacktools-0.40.0}/LICENSE +0 -0
  7. {hacktools-0.39.0 → hacktools-0.40.0}/MANIFEST.in +0 -0
  8. {hacktools-0.39.0 → hacktools-0.40.0}/README.md +0 -0
  9. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/arch.py +0 -0
  10. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/assets/icon.png +0 -0
  11. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/c_ext/cmp_cri.c +0 -0
  12. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/c_ext/cmp_lzss.c +0 -0
  13. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/c_ext/cmp_misc.c +0 -0
  14. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/c_ext/cmp_racjin.c +0 -0
  15. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/c_ext/inc.h +0 -0
  16. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/common.py +0 -0
  17. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/compression.py +0 -0
  18. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/cpk.py +0 -0
  19. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/gb.py +0 -0
  20. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/gui.py +0 -0
  21. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/nasm.py +0 -0
  22. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/nds.py +0 -0
  23. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/nitro.py +0 -0
  24. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/psx.py +0 -0
  25. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/wii.py +0 -0
  26. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools/ws.py +0 -0
  27. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools.egg-info/SOURCES.txt +0 -0
  28. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools.egg-info/dependency_links.txt +0 -0
  29. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools.egg-info/requires.txt +0 -0
  30. {hacktools-0.39.0 → hacktools-0.40.0}/hacktools.egg-info/top_level.txt +0 -0
  31. {hacktools-0.39.0 → hacktools-0.40.0}/setup.cfg +0 -0
  32. {hacktools-0.39.0 → hacktools-0.40.0}/setup.py +0 -0
  33. {hacktools-0.39.0 → hacktools-0.40.0}/tests/test_cmp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hacktools
3
- Version: 0.39.0
3
+ Version: 0.40.0
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
@@ -0,0 +1 @@
1
+ __version__ = "0.40.0"
@@ -243,6 +243,168 @@ def readELF(infile):
243
243
  return elf
244
244
 
245
245
 
246
+ def expandELF(elfpath, n, fixheap=True):
247
+ # Inserts a code cave of n bytes at the end of the RWX PT_LOAD segment of a
248
+ # 32-bit ELF, fixing up all the headers so the file stays valid. Returns the
249
+ # virtual address of the cave (the old segment _end). When fixheap is set,
250
+ # MIPS references to _end used as the heap base are bumped past the cave.
251
+ # Read headers
252
+ with common.Stream(elfpath, "rb") as f:
253
+ e_phoff = f.readUIntAt(0x1c)
254
+ e_shoff = f.readUIntAt(0x20)
255
+ f.seek(0x2a)
256
+ e_phentsize = f.readUShort()
257
+ e_phnum = f.readUShort()
258
+ e_shentsize = f.readUShort()
259
+ e_shnum = f.readUShort()
260
+
261
+ phs = [] # (ph_off, p_type, p_offset, p_vaddr, p_paddr, p_filesz, p_memsz, p_flags, p_align)
262
+ for i in range(e_phnum):
263
+ ph_off = e_phoff + i * e_phentsize
264
+ f.seek(ph_off)
265
+ entry = (ph_off,
266
+ f.readUInt(), f.readUInt(), f.readUInt(), f.readUInt(),
267
+ f.readUInt(), f.readUInt(), f.readUInt(), f.readUInt())
268
+ phs.append(entry)
269
+
270
+ sections = [] # (sh_off, sh_addr, sh_offset, sh_size)
271
+ for i in range(e_shnum):
272
+ sh_off = e_shoff + i * e_shentsize
273
+ f.seek(sh_off + 0x0c)
274
+ sh_addr = f.readUInt()
275
+ sh_offset = f.readUInt()
276
+ sh_size = f.readUInt()
277
+ sections.append((sh_off, sh_addr, sh_offset, sh_size))
278
+
279
+ f.seek(0)
280
+ data = bytearray(f.read())
281
+
282
+ # Identify the RWX PT_LOAD segment that holds .text/.data
283
+ target = next((p for p in phs if p[1] == 1 and p[7] == 7), None)
284
+ if target is None:
285
+ common.logError("Could not find RWX PT_LOAD segment")
286
+ return 0
287
+ _, _, p_offset, p_vaddr, _, p_filesz, p_memsz, _, _ = target
288
+
289
+ # The cave will live at vaddr = p_vaddr + p_memsz, above all original BSS
290
+ # We need filesz to reach that vaddr and cover the cave, which means inserting
291
+ # (p_memsz - p_filesz) zero bytes for the BSS gap + n cave bytes
292
+ bss_gap = p_memsz - p_filesz
293
+ insert_total = bss_gap + n
294
+ insert_at = p_offset + p_filesz
295
+
296
+ # Splice the inserted bytes (all zero) into the file
297
+ data[insert_at:insert_at] = bytes(insert_total)
298
+ with common.Stream(elfpath, "wb") as f:
299
+ f.write(bytes(data))
300
+
301
+ # Patch headers (positions are post-splice)
302
+ new_shoff = e_shoff + insert_total if e_shoff >= insert_at else e_shoff
303
+ with common.Stream(elfpath, "r+b") as f:
304
+ # e_shoff
305
+ f.writeUIntAt(0x20, new_shoff)
306
+
307
+ # Program headers
308
+ for (ph_off, p_type, ph_p_offset, ph_p_vaddr, ph_p_paddr, ph_p_filesz, ph_p_memsz, ph_p_flags, ph_p_align) in phs:
309
+ # Program header table itself doesn't move (e_phoff < insert_at).
310
+ new_p_offset = ph_p_offset + insert_total if ph_p_offset >= insert_at else ph_p_offset
311
+ new_p_vaddr = ph_p_vaddr
312
+ new_p_paddr = ph_p_paddr
313
+ new_p_filesz = ph_p_filesz
314
+ new_p_memsz = ph_p_memsz
315
+ if (p_type, ph_p_flags) == (1, 7):
316
+ # The cave segment grows in both file and memory.
317
+ new_p_filesz = ph_p_filesz + insert_total
318
+ new_p_memsz = ph_p_memsz + n
319
+ elif ph_p_vaddr >= p_vaddr + p_memsz:
320
+ # Any segment that lived at/above the old memsz boundary (e.g. PH1)
321
+ # gets shifted up by the cave size so it doesn't overlap
322
+ new_p_vaddr = ph_p_vaddr + n
323
+ new_p_paddr = ph_p_paddr + n
324
+ f.writeUIntAt(ph_off + 0x04, new_p_offset)
325
+ f.writeUIntAt(ph_off + 0x08, new_p_vaddr)
326
+ f.writeUIntAt(ph_off + 0x0c, new_p_paddr)
327
+ f.writeUIntAt(ph_off + 0x10, new_p_filesz)
328
+ f.writeUIntAt(ph_off + 0x14, new_p_memsz)
329
+
330
+ # Section headers
331
+ for sh_off, sh_addr, sh_offset, sh_size in sections:
332
+ new_addr, new_offset, new_size = sh_addr, sh_offset, sh_size
333
+ if sh_offset >= insert_at:
334
+ new_offset = sh_offset + insert_total
335
+ elif sh_offset != 0 and sh_size != 0 and sh_offset + sh_size == insert_at:
336
+ new_size = sh_size + insert_total
337
+ if sh_addr >= p_vaddr + p_memsz:
338
+ new_addr = sh_addr + n
339
+ new_sh_off = sh_off + insert_total if sh_off >= insert_at else sh_off
340
+ f.writeUIntAt(new_sh_off + 0x0c, new_addr)
341
+ f.writeUIntAt(new_sh_off + 0x10, new_offset)
342
+ f.writeUIntAt(new_sh_off + 0x14, new_size)
343
+
344
+ # Bump references to the original _end (= old PT_LOAD memsz boundary) that
345
+ # are used as the heap base. If we don't shift them, the game's first
346
+ # malloc/sbrk lands on top of our cave
347
+ old_end = p_vaddr + p_memsz
348
+ new_end = old_end + n
349
+ if fixheap:
350
+ sec_lo = p_offset
351
+ sec_hi = p_offset + p_filesz + insert_total
352
+ with common.Stream(elfpath, "r+b") as f:
353
+ # Literals: any 4-byte word equal to old_end gets bumped.
354
+ for off in range(sec_lo, sec_hi - 3, 4):
355
+ if f.readUIntAt(off) == old_end:
356
+ f.writeUIntAt(off, new_end)
357
+ # lui+addiu/ori pairs producing old_end, followed by a syscall nearby
358
+ for off in range(sec_lo, sec_hi - 4, 4):
359
+ w = f.readUIntAt(off)
360
+ if (w >> 26) != 0x0f:
361
+ continue
362
+ rt = (w >> 16) & 0x1f
363
+ hi = (w & 0xffff) << 16
364
+ for j in range(1, 9):
365
+ off2 = off + j * 4
366
+ if off2 >= sec_hi:
367
+ break
368
+ w2 = f.readUIntAt(off2)
369
+ op2 = w2 >> 26
370
+ rs2 = (w2 >> 21) & 0x1f
371
+ if rs2 != rt:
372
+ continue
373
+ if op2 == 0x09:
374
+ lo = w2 & 0xffff
375
+ if lo >= 0x8000:
376
+ lo -= 0x10000
377
+ addr = (hi + lo) & 0xffffffff
378
+ elif op2 == 0x0d:
379
+ addr = hi | (w2 & 0xffff)
380
+ else:
381
+ continue
382
+ if addr != old_end:
383
+ break
384
+ # Look ahead up to 3 instructions for a "syscall" (opcode=0, funct=0x0c)
385
+ # Only the heap-setup site matches this
386
+ has_syscall = False
387
+ for k in range(1, 5):
388
+ off3 = off2 + k * 4
389
+ if off3 >= sec_hi:
390
+ break
391
+ w3 = f.readUIntAt(off3)
392
+ if (w3 >> 26) == 0 and (w3 & 0x3f) == 0x0c:
393
+ has_syscall = True
394
+ break
395
+ if not has_syscall:
396
+ break
397
+ new_hi = (new_end >> 16) & 0xffff
398
+ new_lo = new_end & 0xffff
399
+ if op2 == 0x09 and (new_lo & 0x8000):
400
+ new_hi = (new_hi + 1) & 0xffff
401
+ f.writeUIntAt(off, (w & 0xffff0000) | new_hi)
402
+ f.writeUIntAt(off2, (w2 & 0xffff0000) | new_lo)
403
+ break
404
+ common.logDebug("Expanded ELF, cave at", common.toHex(old_end), "size", common.toHex(n))
405
+ return old_end
406
+
407
+
246
408
  def extractBinaryStrings(elf, foundstrings, infile, func, encoding="shift_jis", elfsections=[".rodata"]):
247
409
  with common.Stream(infile, "rb") as f:
248
410
  for sectionname in elfsections:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hacktools
3
- Version: 0.39.0
3
+ Version: 0.40.0
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
@@ -1 +0,0 @@
1
- __version__ = "0.39.0"
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
File without changes
File without changes