hacktools 0.38.7__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.
- {hacktools-0.38.7/hacktools.egg-info → hacktools-0.40.0}/PKG-INFO +4 -3
- hacktools-0.40.0/hacktools/__init__.py +1 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/c_ext/cmp_lzss.c +8 -2
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/common.py +1 -1
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/nds.py +2 -2
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/psp.py +162 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/psx.py +5 -5
- {hacktools-0.38.7 → hacktools-0.40.0/hacktools.egg-info}/PKG-INFO +4 -3
- {hacktools-0.38.7 → hacktools-0.40.0}/setup.py +2 -2
- hacktools-0.38.7/hacktools/__init__.py +0 -1
- {hacktools-0.38.7 → hacktools-0.40.0}/LICENSE +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/MANIFEST.in +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/README.md +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/arch.py +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/assets/icon.png +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/c_ext/cmp_cri.c +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/c_ext/cmp_misc.c +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/c_ext/cmp_racjin.c +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/c_ext/inc.h +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/compression.py +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/cpk.py +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/gb.py +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/gui.py +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/nasm.py +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/nitro.py +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/wii.py +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools/ws.py +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools.egg-info/SOURCES.txt +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools.egg-info/dependency_links.txt +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools.egg-info/requires.txt +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/hacktools.egg-info/top_level.txt +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/setup.cfg +0 -0
- {hacktools-0.38.7 → hacktools-0.40.0}/tests/test_cmp.py +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hacktools
|
|
3
|
-
Version: 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
|
|
7
|
-
|
|
7
|
+
License: MIT
|
|
8
8
|
Classifier: Programming Language :: Python :: 3.8
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.9
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.10
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
-
Classifier:
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
16
|
Requires-Python: >=3.7
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
@@ -52,6 +52,7 @@ Dynamic: classifier
|
|
|
52
52
|
Dynamic: description
|
|
53
53
|
Dynamic: description-content-type
|
|
54
54
|
Dynamic: home-page
|
|
55
|
+
Dynamic: license
|
|
55
56
|
Dynamic: license-file
|
|
56
57
|
Dynamic: provides-extra
|
|
57
58
|
Dynamic: requires-python
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.40.0"
|
|
@@ -210,7 +210,10 @@ static PyObject* compressLZ10(PyObject* m, PyObject* args, PyObject* kwargs)
|
|
|
210
210
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s#i", kwlist, &indata, &inlength, &mindisp))
|
|
211
211
|
return NULL;
|
|
212
212
|
|
|
213
|
-
|
|
213
|
+
// Worst case: uncompressible data emits 1 flag byte per 8 input bytes,
|
|
214
|
+
// so output can reach inlength + ceil(inlength/8)
|
|
215
|
+
size_t outcap = inlength + (inlength + 7) / 8 + 32;
|
|
216
|
+
unsigned char* out = PyMem_Malloc(outcap);
|
|
214
217
|
MALLOC_CHECK(out);
|
|
215
218
|
|
|
216
219
|
unsigned int compressedlength = 0;
|
|
@@ -288,7 +291,10 @@ static PyObject* compressLZ11(PyObject* m, PyObject* args, PyObject* kwargs)
|
|
|
288
291
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s#i", kwlist, &indata, &inlength, &mindisp))
|
|
289
292
|
return NULL;
|
|
290
293
|
|
|
291
|
-
|
|
294
|
+
// Worst case: uncompressible data emits 1 flag byte per 8 input bytes,
|
|
295
|
+
// so output can reach inlength + ceil(inlength/8)
|
|
296
|
+
size_t outcap = inlength + (inlength + 7) / 8 + 32;
|
|
297
|
+
unsigned char* out = PyMem_Malloc(outcap);
|
|
292
298
|
MALLOC_CHECK(out);
|
|
293
299
|
|
|
294
300
|
unsigned int compressedlength = 0;
|
|
@@ -1171,7 +1171,7 @@ def repackBinaryStrings(section, infile, outfile, binranges, freeranges=None, re
|
|
|
1171
1171
|
fo.writeZero(endpos - fo.tell())
|
|
1172
1172
|
pos = fi.tell() - 1
|
|
1173
1173
|
fi.seek(pos + 1)
|
|
1174
|
-
return notfound
|
|
1174
|
+
return notfound, freeranges
|
|
1175
1175
|
|
|
1176
1176
|
|
|
1177
1177
|
# Folders
|
|
@@ -152,14 +152,14 @@ def repackBIN(binrange, freeranges=[], readfunc=common.detectEncodedString, writ
|
|
|
152
152
|
section.preloadLookup(comments)
|
|
153
153
|
if type(binrange) == tuple:
|
|
154
154
|
binrange = [binrange]
|
|
155
|
-
notfound = common.repackBinaryStrings(section, binin, binout, binrange, freeranges, readfunc, writefunc, encoding, pointerstart, injectstart, fallbackf, injectfallback, sectionname, preformat, postformat)
|
|
155
|
+
notfound, freeranges = common.repackBinaryStrings(section, binin, binout, binrange, freeranges, readfunc, writefunc, encoding, pointerstart, injectstart, fallbackf, injectfallback, sectionname, preformat, postformat)
|
|
156
156
|
for pointer in notfound:
|
|
157
157
|
common.logError("Pointer", common.toHex(pointer.old), "->", common.toHex(pointer.new), "not found for string", pointer.str)
|
|
158
158
|
if binfile.endswith(".txt"):
|
|
159
159
|
common.logMessage("Done! Translation is at {0:.2f}%".format((100 * transtot) / chartot))
|
|
160
160
|
else:
|
|
161
161
|
common.logMessage("Done! Translation is at {0:.2f}%".format(section.getProgress()))
|
|
162
|
-
return
|
|
162
|
+
return freeranges
|
|
163
163
|
|
|
164
164
|
|
|
165
165
|
class BINSection:
|
|
@@ -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:
|
|
@@ -7,13 +7,13 @@ from hacktools import common
|
|
|
7
7
|
# Image functions
|
|
8
8
|
def extractBIN(infolder, outfolder, cuefile):
|
|
9
9
|
try:
|
|
10
|
-
import
|
|
10
|
+
import pydumpsxiso
|
|
11
11
|
except ImportError:
|
|
12
|
-
common.logError("
|
|
12
|
+
common.logError("pydumpsxiso not found")
|
|
13
13
|
return
|
|
14
14
|
common.logMessage("Extracting BIN", cuefile, "...")
|
|
15
15
|
common.makeFolder(infolder)
|
|
16
|
-
|
|
16
|
+
pydumpsxiso.run(cuefile.replace(".cue", ".bin"), infolder[:-1], infolder[:-1] + ".xml")
|
|
17
17
|
common.logMessage("Copying data to", outfolder, "...")
|
|
18
18
|
common.copyFolder(infolder, outfolder)
|
|
19
19
|
with open(infolder[:-1] + ".xml", "r") as f:
|
|
@@ -30,7 +30,7 @@ def repackBIN(infolder, binin, binout, cuefile, patchfile=""):
|
|
|
30
30
|
common.logError("pymkpsxiso not found")
|
|
31
31
|
return
|
|
32
32
|
common.logMessage("Repacking BIN", binout, "...")
|
|
33
|
-
pymkpsxiso.
|
|
33
|
+
pymkpsxiso.run(binout, cuefile, infolder[:-1] + ".xml")
|
|
34
34
|
common.logMessage("Done!")
|
|
35
35
|
# Create xdelta patch
|
|
36
36
|
if patchfile != "":
|
|
@@ -64,7 +64,7 @@ def repackEXE(binrange, freeranges=None, manualptrs=None, readfunc=common.detect
|
|
|
64
64
|
chartot, transtot = common.getSectionPercentage(section)
|
|
65
65
|
if type(binrange) == tuple:
|
|
66
66
|
binrange = [binrange]
|
|
67
|
-
notfound = common.repackBinaryStrings(section, exein, exeout, binrange, freeranges, readfunc, writefunc, encoding, 0x8000f800)
|
|
67
|
+
notfound, freeranges = common.repackBinaryStrings(section, exein, exeout, binrange, freeranges, readfunc, writefunc, encoding, 0x8000f800)
|
|
68
68
|
# Handle not found pointers by manually replacing the opcodes
|
|
69
69
|
if len(notfound) > 0 and manualptrs is not None:
|
|
70
70
|
with open(ptrfile, "w") as f:
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hacktools
|
|
3
|
-
Version: 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
|
|
7
|
-
|
|
7
|
+
License: MIT
|
|
8
8
|
Classifier: Programming Language :: Python :: 3.8
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.9
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.10
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
-
Classifier:
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
16
|
Requires-Python: >=3.7
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
@@ -52,6 +52,7 @@ Dynamic: classifier
|
|
|
52
52
|
Dynamic: description
|
|
53
53
|
Dynamic: description-content-type
|
|
54
54
|
Dynamic: home-page
|
|
55
|
+
Dynamic: license
|
|
55
56
|
Dynamic: license-file
|
|
56
57
|
Dynamic: provides-extra
|
|
57
58
|
Dynamic: requires-python
|
|
@@ -43,15 +43,15 @@ setup(
|
|
|
43
43
|
Extension("hacktools.cmp_misc", sources=["hacktools/c_ext/cmp_misc.c"]),
|
|
44
44
|
Extension("hacktools.cmp_racjin", sources=["hacktools/c_ext/cmp_racjin.c"]),
|
|
45
45
|
],
|
|
46
|
+
license="MIT",
|
|
46
47
|
classifiers=[
|
|
47
|
-
"Programming Language :: Python :: 3.7",
|
|
48
48
|
"Programming Language :: Python :: 3.8",
|
|
49
49
|
"Programming Language :: Python :: 3.9",
|
|
50
50
|
"Programming Language :: Python :: 3.10",
|
|
51
51
|
"Programming Language :: Python :: 3.11",
|
|
52
52
|
"Programming Language :: Python :: 3.12",
|
|
53
53
|
"Programming Language :: Python :: 3.13",
|
|
54
|
-
"
|
|
54
|
+
"Programming Language :: Python :: 3.14",
|
|
55
55
|
"Operating System :: OS Independent",
|
|
56
56
|
],
|
|
57
57
|
extras_require = {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.38.7"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|