ci-gui: ensure boot9 is loaded before allowing titles to be added, show better error message for missing seeddb, load seeddb before starting and any time a new one is selected

This commit is contained in:
Ian Burgwin
2021-02-09 20:24:34 -08:00
parent b3eae08f27
commit 9c777adf26

View File

@@ -16,6 +16,7 @@ import tkinter.filedialog as fd
import tkinter.messagebox as mb import tkinter.messagebox as mb
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from pyctr.crypto import MissingSeedError, CryptoEngine, load_seeddb
from pyctr.crypto.engine import b9_paths from pyctr.crypto.engine import b9_paths
from pyctr.util import config_dirs from pyctr.util import config_dirs
from pyctr.type.cdn import CDNError from pyctr.type.cdn import CDNError
@@ -25,7 +26,8 @@ from pyctr.type.tmd import TitleMetadataError
from custominstall import CustomInstall, CI_VERSION, load_cifinish, InvalidCIFinishError from custominstall import CustomInstall, CI_VERSION, load_cifinish, InvalidCIFinishError
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Dict, List from os import PathLike
from typing import Dict, List, Union
is_windows = platform == 'win32' is_windows = platform == 'win32'
taskbar = None taskbar = None
@@ -70,6 +72,9 @@ default_b9_path = find_first_file(b9_paths)
default_seeddb_path = find_first_file(seeddb_paths) default_seeddb_path = find_first_file(seeddb_paths)
default_movable_sed_path = find_first_file([join(file_parent, 'movable.sed')]) default_movable_sed_path = find_first_file([join(file_parent, 'movable.sed')])
if default_seeddb_path:
load_seeddb(default_seeddb_path)
class ConsoleFrame(ttk.Frame): class ConsoleFrame(ttk.Frame):
def __init__(self, parent: tk.BaseWidget = None, starting_lines: 'List[str]' = None): def __init__(self, parent: tk.BaseWidget = None, starting_lines: 'List[str]' = None):
@@ -235,6 +240,7 @@ class InstallResults(tk.Toplevel):
class CustomInstallGUI(ttk.Frame): class CustomInstallGUI(ttk.Frame):
console = None console = None
b9_loaded = False
def __init__(self, parent: tk.Tk = None): def __init__(self, parent: tk.Tk = None):
super().__init__(parent) super().__init__(parent)
@@ -304,13 +310,14 @@ class CustomInstallGUI(ttk.Frame):
self.file_picker_textboxes['sd'] = sd_selected self.file_picker_textboxes['sd'] = sd_selected
# This feels so wrong. # This feels so wrong.
def create_required_file_picker(type_name, types, default, row): def create_required_file_picker(type_name, types, default, row, callback=lambda filename: None):
def internal_callback(): def internal_callback():
f = fd.askopenfilename(parent=parent, title='Select ' + type_name, filetypes=types, f = fd.askopenfilename(parent=parent, title='Select ' + type_name, filetypes=types,
initialdir=file_parent) initialdir=file_parent)
if f: if f:
selected.delete('1.0', tk.END) selected.delete('1.0', tk.END)
selected.insert(tk.END, f) selected.insert(tk.END, f)
callback(f)
type_label = ttk.Label(file_pickers, text=type_name) type_label = ttk.Label(file_pickers, text=type_name)
type_label.grid(row=row, column=0) type_label.grid(row=row, column=0)
@@ -325,8 +332,15 @@ class CustomInstallGUI(ttk.Frame):
self.file_picker_textboxes[type_name] = selected self.file_picker_textboxes[type_name] = selected
create_required_file_picker('boot9', [('boot9 file', '*.bin')], default_b9_path, 1) def b9_callback(path: 'Union[PathLike, bytes, str]'):
create_required_file_picker('seeddb', [('seeddb file', '*.bin')], default_seeddb_path, 2) self.check_b9_loaded()
self.enable_buttons()
def seeddb_callback(path: 'Union[PathLike, bytes, str]'):
load_seeddb(path)
create_required_file_picker('boot9', [('boot9 file', '*.bin')], default_b9_path, 1, b9_callback)
create_required_file_picker('seeddb', [('seeddb file', '*.bin')], default_seeddb_path, 2, seeddb_callback)
create_required_file_picker('movable.sed', [('movable.sed file', '*.sed')], default_movable_sed_path, 3) create_required_file_picker('movable.sed', [('movable.sed file', '*.sed')], default_movable_sed_path, 3)
# ---------------------------------------------------------------- # # ---------------------------------------------------------------- #
@@ -454,7 +468,13 @@ class CustomInstallGUI(ttk.Frame):
self.log('Ready.') self.log('Ready.')
self.disable_during_install = (add_cias, add_dirs, remove_selected, start, *self.file_picker_textboxes.values()) self.require_boot9 = (add_cias, add_cdn, add_dirs, remove_selected, start)
self.disable_buttons()
self.check_b9_loaded()
self.enable_buttons()
if not self.b9_loaded:
self.log('Note: boot9 was not auto-detected. Please choose it before adding any titles.')
def sort_treeview(self): def sort_treeview(self):
l = [(self.treeview.set(k, 'titlename'), k) for k in self.treeview.get_children()] l = [(self.treeview.set(k, 'titlename'), k) for k in self.treeview.get_children()]
@@ -464,7 +484,20 @@ class CustomInstallGUI(ttk.Frame):
for idx, pair in enumerate(l): for idx, pair in enumerate(l):
self.treeview.move(pair[1], '', idx) self.treeview.move(pair[1], '', idx)
def check_b9_loaded(self):
if not self.b9_loaded:
boot9 = self.file_picker_textboxes['boot9'].get('1.0', tk.END).strip()
try:
tmp_crypto = CryptoEngine(boot9=boot9)
self.b9_loaded = tmp_crypto.b9_keys_set
except:
return False
return self.b9_loaded
def add_cia(self, path): def add_cia(self, path):
if not self.check_b9_loaded():
# this shouldn't happen
return False, 'Please choose boot9 first'
path = abspath(path) path = abspath(path)
if path in self.readers: if path in self.readers:
return False, 'File already in list' return False, 'File already in list'
@@ -472,6 +505,8 @@ class CustomInstallGUI(ttk.Frame):
reader = CustomInstall.get_reader(path) reader = CustomInstall.get_reader(path)
except (CIAError, CDNError, TitleMetadataError): except (CIAError, CDNError, TitleMetadataError):
return False, 'Failed to read as a CIA or CDN title, probably corrupt' return False, 'Failed to read as a CIA or CDN title, probably corrupt'
except MissingSeedError:
return False, 'Latest seeddb.bin is required, check the README for details'
except Exception as e: except Exception as e:
return False, f'Exception occurred: {type(e).__name__}: {e}' return False, f'Exception occurred: {type(e).__name__}: {e}'
@@ -532,25 +567,26 @@ class CustomInstallGUI(ttk.Frame):
mb.showinfo('Info', message, parent=self.parent) mb.showinfo('Info', message, parent=self.parent)
def disable_buttons(self): def disable_buttons(self):
for b in self.disable_during_install: for b in self.require_boot9:
b.config(state=tk.DISABLED)
for b in self.file_picker_textboxes.values():
b.config(state=tk.DISABLED) b.config(state=tk.DISABLED)
def enable_buttons(self): def enable_buttons(self):
for b in self.disable_during_install: if self.b9_loaded:
for b in self.require_boot9:
b.config(state=tk.NORMAL)
for b in self.file_picker_textboxes.values():
b.config(state=tk.NORMAL) b.config(state=tk.NORMAL)
def start_install(self): def start_install(self):
sd_root = self.file_picker_textboxes['sd'].get('1.0', tk.END).strip() sd_root = self.file_picker_textboxes['sd'].get('1.0', tk.END).strip()
boot9 = self.file_picker_textboxes['boot9'].get('1.0', tk.END).strip()
seeddb = self.file_picker_textboxes['seeddb'].get('1.0', tk.END).strip() seeddb = self.file_picker_textboxes['seeddb'].get('1.0', tk.END).strip()
movable_sed = self.file_picker_textboxes['movable.sed'].get('1.0', tk.END).strip() movable_sed = self.file_picker_textboxes['movable.sed'].get('1.0', tk.END).strip()
if not sd_root: if not sd_root:
self.show_error('SD root is not specified.') self.show_error('SD root is not specified.')
return return
if not boot9:
self.show_error('boot9 is not specified.')
return
if not movable_sed: if not movable_sed:
self.show_error('movable.sed is not specified.') self.show_error('movable.sed is not specified.')
return return
@@ -571,9 +607,7 @@ class CustomInstallGUI(ttk.Frame):
if taskbar: if taskbar:
taskbar.SetProgressState(self.hwnd, tbl.TBPF_NORMAL) taskbar.SetProgressState(self.hwnd, tbl.TBPF_NORMAL)
installer = CustomInstall(boot9=boot9, installer = CustomInstall(movable=movable_sed,
seeddb=seeddb,
movable=movable_sed,
sd=sd_root, sd=sd_root,
skip_contents=self.skip_contents_var.get() == 1, skip_contents=self.skip_contents_var.get() == 1,
overwrite_saves=self.overwrite_saves_var.get() == 1) overwrite_saves=self.overwrite_saves_var.get() == 1)