mirror of
https://github.com/ihaveamac/custom-install.git
synced 2025-12-06 06:41:45 +00:00
custominstall: style updates
This commit is contained in:
@@ -33,13 +33,16 @@ TITLE_ALIGN_SIZE = 0x8000
|
|||||||
# size to read at a time when copying files
|
# size to read at a time when copying files
|
||||||
READ_SIZE = 0x200000
|
READ_SIZE = 0x200000
|
||||||
|
|
||||||
|
|
||||||
# Placeholder for SDPathErrors
|
# Placeholder for SDPathErrors
|
||||||
class SDPathError(Exception): pass
|
class SDPathError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CustomInstall():
|
class CustomInstall():
|
||||||
def __init__(self, boot9, movable, cias, sd, skip_contents=False):
|
def __init__(self, boot9, movable, cias, sd, skip_contents=False):
|
||||||
self.event = Events()
|
self.event = Events()
|
||||||
self.log_lines = [] # Stores all info messages for user to view
|
self.log_lines = [] # Stores all info messages for user to view
|
||||||
|
|
||||||
self.crypto = CryptoEngine(boot9=boot9)
|
self.crypto = CryptoEngine(boot9=boot9)
|
||||||
self.crypto.setup_sd_key_from_file(movable)
|
self.crypto.setup_sd_key_from_file(movable)
|
||||||
@@ -67,7 +70,8 @@ class CustomInstall():
|
|||||||
[sd_path, id1s] = self.get_sd_path()
|
[sd_path, id1s] = self.get_sd_path()
|
||||||
try:
|
try:
|
||||||
if len(id1s) > 1:
|
if len(id1s) > 1:
|
||||||
raise SDPathError(f'There are multiple id1 directories for id0 {crypto.id0.hex()}, please remove extra directories')
|
raise SDPathError(f'There are multiple id1 directories for id0 {crypto.id0.hex()}, '
|
||||||
|
f'please remove extra directories')
|
||||||
elif len(id1s) == 0:
|
elif len(id1s) == 0:
|
||||||
raise SDPathError(f'Could not find a suitable id1 directory for id0 {crypto.id0.hex()}')
|
raise SDPathError(f'Could not find a suitable id1 directory for id0 {crypto.id0.hex()}')
|
||||||
except SDPathError:
|
except SDPathError:
|
||||||
@@ -148,22 +152,23 @@ class CustomInstall():
|
|||||||
# write the tmd
|
# write the tmd
|
||||||
enc_path = content_root_cmd + '/' + tmd_filename
|
enc_path = content_root_cmd + '/' + tmd_filename
|
||||||
self.log(f'Writing {enc_path}...')
|
self.log(f'Writing {enc_path}...')
|
||||||
with cia.open_raw_section(CIASection.TitleMetadata) as s, open(join(content_root, tmd_filename), 'wb') as o:
|
with cia.open_raw_section(CIASection.TitleMetadata) as s:
|
||||||
self.copy_with_progress(s, o, cia.sections[CIASection.TitleMetadata].size, enc_path)
|
with open(join(content_root, tmd_filename), 'wb') as o:
|
||||||
|
self.copy_with_progress(s, o, cia.sections[CIASection.TitleMetadata].size, enc_path)
|
||||||
|
|
||||||
# write each content
|
# write each content
|
||||||
for c in cia.content_info:
|
for co in cia.content_info:
|
||||||
content_filename = c.id + '.app'
|
content_filename = co.id + '.app'
|
||||||
if is_dlc:
|
if is_dlc:
|
||||||
dir_index = format((c.cindex // 256), '08x')
|
dir_index = format((co.cindex // 256), '08x')
|
||||||
enc_path = content_root_cmd + f'/{dir_index}/{content_filename}'
|
enc_path = content_root_cmd + f'/{dir_index}/{content_filename}'
|
||||||
out_path = join(content_root, dir_index, content_filename)
|
out_path = join(content_root, dir_index, content_filename)
|
||||||
else:
|
else:
|
||||||
enc_path = content_root_cmd + '/' + content_filename
|
enc_path = content_root_cmd + '/' + content_filename
|
||||||
out_path = join(content_root, content_filename)
|
out_path = join(content_root, content_filename)
|
||||||
self.log(f'Writing {enc_path}...')
|
self.log(f'Writing {enc_path}...')
|
||||||
with cia.open_raw_section(c.cindex) as s, open(out_path, 'wb') as o:
|
with cia.open_raw_section(co.cindex) as s, open(out_path, 'wb') as o:
|
||||||
self.copy_with_progress(s, o, c.size, enc_path)
|
self.copy_with_progress(s, o, co.size, enc_path)
|
||||||
|
|
||||||
# generate a blank save
|
# generate a blank save
|
||||||
if cia.tmd.save_size:
|
if cia.tmd.save_size:
|
||||||
@@ -193,9 +198,9 @@ class CustomInstall():
|
|||||||
id_bytes = bytes.fromhex(record.id)[::-1]
|
id_bytes = bytes.fromhex(record.id)[::-1]
|
||||||
cmac_data += record.cindex.to_bytes(4, 'little') + id_bytes
|
cmac_data += record.cindex.to_bytes(4, 'little') + id_bytes
|
||||||
|
|
||||||
c = crypto.create_cmac_object(Keyslot.CMACSDNAND)
|
cmac_ncch = crypto.create_cmac_object(Keyslot.CMACSDNAND)
|
||||||
c.update(sha256(cmac_data).digest())
|
cmac_ncch.update(sha256(cmac_data).digest())
|
||||||
content_ids[record.cindex] = (id_bytes, c.digest())
|
content_ids[record.cindex] = (id_bytes, cmac_ncch.digest())
|
||||||
|
|
||||||
# add content IDs up to the last one
|
# add content IDs up to the last one
|
||||||
ids_by_index = [CMD_MISSING] * (highest_index + 1)
|
ids_by_index = [CMD_MISSING] * (highest_index + 1)
|
||||||
@@ -213,12 +218,12 @@ class CustomInstall():
|
|||||||
installed_ids.sort(key=lambda x: int.from_bytes(x, 'little'))
|
installed_ids.sort(key=lambda x: int.from_bytes(x, 'little'))
|
||||||
|
|
||||||
final = (cmd_id.to_bytes(4, 'little')
|
final = (cmd_id.to_bytes(4, 'little')
|
||||||
+ len(ids_by_index).to_bytes(4, 'little')
|
+ len(ids_by_index).to_bytes(4, 'little')
|
||||||
+ len(installed_ids).to_bytes(4, 'little')
|
+ len(installed_ids).to_bytes(4, 'little')
|
||||||
+ (1).to_bytes(4, 'little'))
|
+ (1).to_bytes(4, 'little'))
|
||||||
c = crypto.create_cmac_object(Keyslot.CMACSDNAND)
|
cmac_cmd_header = crypto.create_cmac_object(Keyslot.CMACSDNAND)
|
||||||
c.update(final)
|
cmac_cmd_header.update(final)
|
||||||
final += c.digest()
|
final += cmac_cmd_header.digest()
|
||||||
|
|
||||||
final += b''.join(ids_by_index)
|
final += b''.join(ids_by_index)
|
||||||
final += b''.join(installed_ids)
|
final += b''.join(installed_ids)
|
||||||
@@ -265,9 +270,6 @@ class CustomInstall():
|
|||||||
|
|
||||||
title_info_entries[cia.tmd.title_id] = b''.join(title_info_entry_data)
|
title_info_entries[cia.tmd.title_id] = b''.join(title_info_entry_data)
|
||||||
|
|
||||||
with cia.open_raw_section(CIASection.Ticket) as t:
|
|
||||||
ticket_data = t.read()
|
|
||||||
|
|
||||||
finalize_entry_data = [
|
finalize_entry_data = [
|
||||||
# magic
|
# magic
|
||||||
b'TITLE\0',
|
b'TITLE\0',
|
||||||
@@ -318,14 +320,13 @@ class CustomInstall():
|
|||||||
self.log('FINAL STEP:\nRun custom-install-finalize through homebrew launcher.')
|
self.log('FINAL STEP:\nRun custom-install-finalize through homebrew launcher.')
|
||||||
self.log('This will install a ticket and seed if required.')
|
self.log('This will install a ticket and seed if required.')
|
||||||
|
|
||||||
|
|
||||||
def get_sd_path(self):
|
def get_sd_path(self):
|
||||||
sd_path = join(self.sd, 'Nintendo 3DS', self.crypto.id0.hex())
|
sd_path = join(self.sd, 'Nintendo 3DS', self.crypto.id0.hex())
|
||||||
id1s = []
|
id1s = []
|
||||||
for d in scandir(sd_path):
|
for d in scandir(sd_path):
|
||||||
if d.is_dir() and len(d.name) == 32:
|
if d.is_dir() and len(d.name) == 32:
|
||||||
try:
|
try:
|
||||||
#id1_tmp = bytes.fromhex(d.name)
|
# id1_tmp = bytes.fromhex(d.name)
|
||||||
pass
|
pass
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
@@ -333,7 +334,6 @@ class CustomInstall():
|
|||||||
id1s.append(d.name)
|
id1s.append(d.name)
|
||||||
return [sd_path, id1s]
|
return [sd_path, id1s]
|
||||||
|
|
||||||
|
|
||||||
def log(self, message, mtype=0, errorname=None):
|
def log(self, message, mtype=0, errorname=None):
|
||||||
"""Logs an Message with a type. Format is similar to python errors
|
"""Logs an Message with a type. Format is similar to python errors
|
||||||
|
|
||||||
@@ -344,15 +344,15 @@ class CustomInstall():
|
|||||||
|
|
||||||
optionally, errorname can be a custom name as a string to identify errors easily
|
optionally, errorname can be a custom name as a string to identify errors easily
|
||||||
"""
|
"""
|
||||||
if errorname == None:
|
if errorname:
|
||||||
|
errorname += ": "
|
||||||
|
else:
|
||||||
# No errorname provided
|
# No errorname provided
|
||||||
errorname = ""
|
errorname = ""
|
||||||
else:
|
|
||||||
errorname += ": "
|
|
||||||
types = [
|
types = [
|
||||||
"", # Type 0
|
"", # Type 0
|
||||||
"Warning: ", # Type 1
|
"Warning: ", # Type 1
|
||||||
"Error: " # Type 2
|
"Error: " # Type 2
|
||||||
]
|
]
|
||||||
# Example: "Warning: UninformativeError: An error occured, try again.""
|
# Example: "Warning: UninformativeError: An error occured, try again.""
|
||||||
msg_with_type = types[mtype] + errorname + str(message)
|
msg_with_type = types[mtype] + errorname + str(message)
|
||||||
@@ -360,6 +360,7 @@ class CustomInstall():
|
|||||||
self.event.on_log_msg(msg_with_type)
|
self.event.on_log_msg(msg_with_type)
|
||||||
return msg_with_type
|
return msg_with_type
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = ArgumentParser(description='Manually install a CIA to the SD card for a Nintendo 3DS system.')
|
parser = ArgumentParser(description='Manually install a CIA to the SD card for a Nintendo 3DS system.')
|
||||||
parser.add_argument('cia', help='CIA files', nargs='+')
|
parser.add_argument('cia', help='CIA files', nargs='+')
|
||||||
@@ -368,14 +369,13 @@ if __name__ == "__main__":
|
|||||||
parser.add_argument('--sd', help='path to SD root')
|
parser.add_argument('--sd', help='path to SD root')
|
||||||
parser.add_argument('--skip-contents', help="don't add contents, only add title info entry", action='store_true')
|
parser.add_argument('--skip-contents', help="don't add contents, only add title info entry", action='store_true')
|
||||||
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
installer = CustomInstall(boot9=args.boot9,
|
installer = CustomInstall(boot9=args.boot9,
|
||||||
cias=args.cia,
|
cias=args.cia,
|
||||||
movable=args.movable,
|
movable=args.movable,
|
||||||
sd=args.sd,
|
sd=args.sd,
|
||||||
skip_contents=(args.skip_contents or False))
|
skip_contents=(args.skip_contents or False))
|
||||||
|
|
||||||
def log_handle(msg):
|
def log_handle(msg):
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|||||||
Reference in New Issue
Block a user