mirror of
https://github.com/ihaveamac/custom-install.git
synced 2025-12-06 06:41:45 +00:00
custominstall: support new events, check if save3ds_fuse runs successfully, fix seed loading, don't continue if cia fails to load (temp maybe)
This commit is contained in:
@@ -9,8 +9,10 @@ from os import makedirs, scandir
|
|||||||
from os.path import dirname, join
|
from os.path import dirname, join
|
||||||
from random import randint
|
from random import randint
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from sys import platform
|
from locale import getpreferredencoding
|
||||||
|
from sys import exc_info, platform
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
from traceback import format_exception
|
||||||
from typing import BinaryIO, TYPE_CHECKING
|
from typing import BinaryIO, TYPE_CHECKING
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
@@ -20,7 +22,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
from events import Events
|
from events import Events
|
||||||
|
|
||||||
from pyctr.crypto import CryptoEngine, Keyslot, load_seeddb
|
from pyctr.crypto import CryptoEngine, Keyslot, load_seeddb, get_seed
|
||||||
from pyctr.type.cia import CIAReader, CIASection
|
from pyctr.type.cia import CIAReader, CIASection
|
||||||
from pyctr.type.ncch import NCCHSection
|
from pyctr.type.ncch import NCCHSection
|
||||||
from pyctr.util import roundup
|
from pyctr.util import roundup
|
||||||
@@ -147,7 +149,7 @@ class CustomInstall:
|
|||||||
self.cifinish_out = cifinish_out
|
self.cifinish_out = cifinish_out
|
||||||
self.movable = movable
|
self.movable = movable
|
||||||
|
|
||||||
def copy_with_progress(self, src: BinaryIO, dst: BinaryIO, size: int, path: str):
|
def copy_with_progress(self, src: BinaryIO, dst: BinaryIO, size: int, path: str, fire_event: bool = True):
|
||||||
left = size
|
left = size
|
||||||
cipher = self.crypto.create_ctr_cipher(Keyslot.SD, self.crypto.sd_path_to_iv(path))
|
cipher = self.crypto.create_ctr_cipher(Keyslot.SD, self.crypto.sd_path_to_iv(path))
|
||||||
while left > 0:
|
while left > 0:
|
||||||
@@ -156,9 +158,10 @@ class CustomInstall:
|
|||||||
dst.write(data)
|
dst.write(data)
|
||||||
left -= to_read
|
left -= to_read
|
||||||
total_read = size - left
|
total_read = size - left
|
||||||
self.event.update_percentage((total_read / size) * 100, total_read / 1048576, size / 1048576)
|
if fire_event:
|
||||||
|
self.event.update_percentage((total_read / size) * 100, total_read / 1048576, size / 1048576)
|
||||||
|
|
||||||
def start(self):
|
def start(self, continue_on_fail=True):
|
||||||
crypto = self.crypto
|
crypto = self.crypto
|
||||||
# TODO: Move a lot of these into their own methods
|
# TODO: Move a lot of these into their own methods
|
||||||
self.log("Finding path to install to...")
|
self.log("Finding path to install to...")
|
||||||
@@ -181,14 +184,19 @@ class CustomInstall:
|
|||||||
|
|
||||||
# Now loop through all provided cia files
|
# Now loop through all provided cia files
|
||||||
|
|
||||||
for c in self.cias:
|
for idx, c in enumerate(self.cias):
|
||||||
self.log('Reading ' + c)
|
self.log('Reading ' + c)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cia = CIAReader(c)
|
cia = CIAReader(c)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log(f'Failed to load file: {type(e).__name__}: {e}')
|
self.event.on_error(exc_info())
|
||||||
continue
|
if continue_on_fail:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.event.on_cia_start(idx)
|
||||||
|
|
||||||
self.cia = cia
|
self.cia = cia
|
||||||
|
|
||||||
@@ -256,7 +264,8 @@ class CustomInstall:
|
|||||||
self.log(f'Writing {enc_path}...')
|
self.log(f'Writing {enc_path}...')
|
||||||
with cia.open_raw_section(CIASection.TitleMetadata) as s:
|
with cia.open_raw_section(CIASection.TitleMetadata) as s:
|
||||||
with open(join(content_root, tmd_filename), 'wb') as o:
|
with open(join(content_root, tmd_filename), 'wb') as o:
|
||||||
self.copy_with_progress(s, o, cia.sections[CIASection.TitleMetadata].size, enc_path)
|
self.copy_with_progress(s, o, cia.sections[CIASection.TitleMetadata].size, enc_path,
|
||||||
|
fire_event=False)
|
||||||
|
|
||||||
# write each content
|
# write each content
|
||||||
for co in cia.content_info:
|
for co in cia.content_info:
|
||||||
@@ -375,7 +384,7 @@ 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)
|
||||||
|
|
||||||
cifinish_data[int(cia.tmd.title_id, 16)] = {'seed': (cia.contents[0].seed if cia.contents[0].flags.uses_seed else None)}
|
cifinish_data[int(cia.tmd.title_id, 16)] = {'seed': (get_seed(cia.contents[0].program_id) if cia.contents[0].flags.uses_seed else None)}
|
||||||
|
|
||||||
# This is saved regardless if any titles were installed, so the file can be upgraded just in case.
|
# This is saved regardless if any titles were installed, so the file can be upgraded just in case.
|
||||||
save_cifinish(cifinish_path, cifinish_data)
|
save_cifinish(cifinish_path, cifinish_data)
|
||||||
@@ -394,7 +403,14 @@ class CustomInstall:
|
|||||||
|
|
||||||
# extract the title database to add our own entry to
|
# extract the title database to add our own entry to
|
||||||
self.log('Extracting Title Database...')
|
self.log('Extracting Title Database...')
|
||||||
subprocess.run(save3ds_fuse_common_args + ['-x'])
|
out = subprocess.run(save3ds_fuse_common_args + ['-x'],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
encoding=getpreferredencoding())
|
||||||
|
if out.returncode:
|
||||||
|
for l in out.stdout.split('\n'):
|
||||||
|
self.log(l)
|
||||||
|
return False
|
||||||
|
|
||||||
for title_id, entry in title_info_entries.items():
|
for title_id, entry in title_info_entries.items():
|
||||||
# write the title info entry to the temp directory
|
# write the title info entry to the temp directory
|
||||||
@@ -403,13 +419,23 @@ class CustomInstall:
|
|||||||
|
|
||||||
# import the directory, now including our title
|
# import the directory, now including our title
|
||||||
self.log('Importing into Title Database...')
|
self.log('Importing into Title Database...')
|
||||||
subprocess.run(save3ds_fuse_common_args + ['-i'])
|
out = subprocess.run(save3ds_fuse_common_args + ['-i'],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
encoding=getpreferredencoding())
|
||||||
|
if out.returncode:
|
||||||
|
for l in out.stdout.split('\n'):
|
||||||
|
self.log(l)
|
||||||
|
return False
|
||||||
|
|
||||||
self.log('FINAL STEP:\nRun custom-install-finalize through homebrew launcher.')
|
self.log('FINAL STEP:')
|
||||||
|
self.log('Run 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.')
|
||||||
|
return True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.log('Did not install any titles.', 2)
|
self.log('Did not install any titles.', 2)
|
||||||
|
return None
|
||||||
|
|
||||||
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())
|
||||||
@@ -479,7 +505,16 @@ if __name__ == "__main__":
|
|||||||
def percent_handle(total_percent, total_read, size):
|
def percent_handle(total_percent, total_read, size):
|
||||||
installer.log(f' {total_percent:>5.1f}% {total_read:>.1f} MiB / {size:.1f} MiB\r', end='')
|
installer.log(f' {total_percent:>5.1f}% {total_read:>.1f} MiB / {size:.1f} MiB\r', end='')
|
||||||
|
|
||||||
|
def error(exc):
|
||||||
|
for line in format_exception(*exc):
|
||||||
|
for line2 in line.split('\n')[:-1]:
|
||||||
|
installer.log(line2)
|
||||||
|
|
||||||
installer.event.on_log_msg += log_handle
|
installer.event.on_log_msg += log_handle
|
||||||
installer.event.update_percentage += percent_handle
|
installer.event.update_percentage += percent_handle
|
||||||
|
installer.event.on_error += error
|
||||||
|
|
||||||
installer.start()
|
result = installer.start(continue_on_fail=False)
|
||||||
|
if result is False:
|
||||||
|
# save3ds_fuse failed
|
||||||
|
installer.log('NOTE: Once save3ds_fuse is fixed, run the same command again with --skip-contents')
|
||||||
|
|||||||
Reference in New Issue
Block a user