ntool/lib/ctr_exefs.py
2023-03-26 23:48:17 +08:00

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}')