mirror of
https://github.com/xprism1/ntool.git
synced 2025-06-18 15:55:31 -04:00
162 lines
5.5 KiB
Python
162 lines
5.5 KiB
Python
from .common import *
|
|
from .keys import *
|
|
|
|
if platform.system() == 'Windows':
|
|
tool = os.path.join(resources_dir, '3dstool.exe')
|
|
elif platform.system() == 'Linux':
|
|
tool = os.path.join(resources_dir, '3dstool_linux')
|
|
elif platform.system() == 'Darwin':
|
|
tool = os.path.join(resources_dir, '3dstool_macos')
|
|
else:
|
|
raise Exception('Could not identify OS')
|
|
|
|
block_size = 0x200
|
|
|
|
class ExeFSFileHdr(Structure):
|
|
_fields_ = [
|
|
('name', c_char * 8),
|
|
('offset', c_uint32),
|
|
('size', c_uint32),
|
|
]
|
|
|
|
def __new__(cls, buf):
|
|
return cls.from_buffer_copy(buf)
|
|
|
|
def __init__(self, data):
|
|
pass
|
|
|
|
class ExeFSHdr(Structure):
|
|
_pack_ = 1
|
|
|
|
_fields_ = [
|
|
('file_headers', ExeFSFileHdr * 10),
|
|
('reserved', c_uint8 * 0x20),
|
|
('file_hashes', c_uint8 * 0x140),
|
|
]
|
|
|
|
def __new__(cls, buf):
|
|
return cls.from_buffer_copy(buf)
|
|
|
|
def __init__(self, data):
|
|
pass
|
|
|
|
class ExeFSReader:
|
|
def __init__(self, file):
|
|
self.file = file
|
|
|
|
with open(file, 'rb') as f:
|
|
self.hdr = ExeFSHdr(f.read(0x200))
|
|
|
|
files = {}
|
|
for i in range(10):
|
|
file_hdr = self.hdr.file_headers[i]
|
|
if file_hdr.size:
|
|
files[f'{file_hdr.name.decode("utf-8")}.bin'] = {
|
|
'size': file_hdr.size,
|
|
'offset': 0x200 + file_hdr.offset
|
|
}
|
|
self.files = files
|
|
|
|
def extract(self, code_compressed=0):
|
|
f = open(self.file, 'rb')
|
|
for name, info in self.files.items():
|
|
f.seek(info['offset'])
|
|
g = open(name, 'wb')
|
|
|
|
for data in read_chunks(f, info['size']):
|
|
g.write(data)
|
|
|
|
print(f'Extracted {name}')
|
|
g.close()
|
|
|
|
if name == '.code.bin' and code_compressed:
|
|
proc = subprocess.Popen([tool, '-uvf', '.code.bin', '--compress-type', 'blz', '--compress-out', 'code-decompressed.bin'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
result = proc.communicate()
|
|
if result[0] == b'':
|
|
print('Decompressed to code-decompressed.bin')
|
|
else:
|
|
print(result[0].decode('utf-8'))
|
|
f.close()
|
|
|
|
def verify(self):
|
|
f = open(self.file, 'rb')
|
|
|
|
hash_check = []
|
|
hashes = [bytes(self.hdr.file_hashes[i * 0x20:(i + 1) * 0x20]) for i in range(10)]
|
|
hashes.reverse()
|
|
|
|
for i, (name, info) in enumerate(self.files.items()):
|
|
f.seek(info['offset'])
|
|
hash_check.append((name.replace('.bin', ''), Crypto.sha256(f, info['size']) == hashes[i]))
|
|
|
|
f.close()
|
|
print("Hashes:")
|
|
for i in hash_check:
|
|
print(' > {0:15} {1:4}'.format(i[0] + ':', 'GOOD' if i[1] else 'FAIL'))
|
|
|
|
class ExeFSBuilder:
|
|
def __init__(self, exefs_dir='', code_compress=0, out='exefs.bin'):
|
|
'''
|
|
exefs_dir: path to directory containing files to be added to exefs (files must be named '.code.bin', 'banner.bin', 'icon.bin', 'logo.bin')
|
|
code_compress: 0 or 1
|
|
out: path to output file
|
|
'''
|
|
|
|
files = os.listdir(exefs_dir) # Contains filenames, not paths
|
|
files.sort()
|
|
hdr = ExeFSHdr(b'\x00' * 0x200)
|
|
|
|
if files[0] == '.code.bin' and code_compress == 1:
|
|
proc = subprocess.Popen([tool, '-zvf', os.path.join(exefs_dir, '.code.bin'), '--compress-type', 'blz', '--compress-out', os.path.join(exefs_dir, 'code-compressed.bin')], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
result = proc.communicate()
|
|
if result[0] == b'':
|
|
files[0] = 'code-compressed.bin'
|
|
else:
|
|
print(result[0].decode('utf-8'))
|
|
|
|
# Create ExeFS header
|
|
hashes = []
|
|
for i in range(len(files)):
|
|
if files[i] == 'code-compressed.bin':
|
|
hdr.file_headers[i].name = '.code'.encode('utf-8')
|
|
else:
|
|
hdr.file_headers[i].name = files[i].replace('.bin', '').encode('utf-8')
|
|
hdr.file_headers[i].size = os.path.getsize(os.path.join(exefs_dir, files[i]))
|
|
if i == 0:
|
|
hdr.file_headers[i].offset = 0
|
|
else:
|
|
hdr.file_headers[i].offset = roundup(hdr.file_headers[i - 1].offset + hdr.file_headers[i - 1].size, block_size)
|
|
|
|
f = open(os.path.join(exefs_dir, files[i]), 'rb')
|
|
hashes.append(Crypto.sha256(f, hdr.file_headers[i].size))
|
|
f.close()
|
|
|
|
for _ in range(len(files), 10):
|
|
hashes.append(b'\x00' * 0x20)
|
|
hashes.reverse()
|
|
hashes_all = b''.join(hashes)
|
|
hdr.file_hashes = (c_uint8 * sizeof(hdr.file_hashes))(*hashes_all)
|
|
|
|
# Write ExeFS
|
|
f = open(out, 'wb')
|
|
f.write(bytes(hdr))
|
|
curr = 0x200
|
|
for i in range(len(files)):
|
|
g = open(os.path.join(exefs_dir, files[i]), 'rb')
|
|
if curr < (hdr.file_headers[i].offset + 0x200):
|
|
pad_size = hdr.file_headers[i].offset + 0x200 - curr
|
|
f.write(b'\x00' * pad_size)
|
|
curr += pad_size
|
|
|
|
for data in read_chunks(g, hdr.file_headers[i].size):
|
|
f.write(data)
|
|
|
|
curr += hdr.file_headers[i].size
|
|
g.close()
|
|
|
|
f.write(b'\x00' * align(curr, block_size))
|
|
f.close()
|
|
if os.path.isfile(os.path.join(exefs_dir, 'code-compressed.bin')):
|
|
os.remove(os.path.join(exefs_dir, 'code-compressed.bin'))
|
|
print(f'Wrote to {out}')
|