From 74522dabadf1c03a95b70a3f7caacc33b6a075b0 Mon Sep 17 00:00:00 2001 From: nek0bit Date: Sun, 10 Nov 2019 18:43:21 -0500 Subject: [PATCH] GUI --- .gitignore | 2 +- README.md | 32 +++++-- gui-custominstall.py | 199 +++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 + 4 files changed, 226 insertions(+), 9 deletions(-) create mode 100644 gui-custominstall.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index a82ec60..7182572 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,5 @@ bin/linux/save3ds_fuse **/__pycache__ **/finalize -cstinst/ +cstins/ testing-class.py \ No newline at end of file diff --git a/README.md b/README.md index 6a98224..0f6bc93 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,15 @@ Experimental script to automate the process of a manual title install for Ninten ## Summary 1. Dump boot9.bin and movable.sed from a 3DS system. -2. Install pycryptodomex: - * Windows: `py -3 -m pip install --user --upgrade pycryptodomex` - * macOS/Linux: `python3 -m pip install --user --upgrade pycryptodomex` +2. Install the packages: + * Windows: `py -3 -m pip install --user -r requirements.txt` + * macOS/Linux: `python3 -m pip install --user -r requirements.txt` 3. Download the repo ([zip link](https://github.com/ihaveamac/custom-install/archive/master.zip) or `git clone`) -4. Run `custom-install.py` with boot9.bin, movable.sed, path to the SD root, and CIA files to install (see Usage section). +4. Run `custominstall.py` with boot9.bin, movable.sed, path to the SD root, and CIA files to install (see Usage section). 5. Download and use [custom-install-finalize](https://github.com/ihaveamac/custom-install/releases) on the 3DS system to finish the install. ## Setup -Linux users must build [wwylele/save3ds](https://github.com/wwylele/save3ds) and place `save3ds_fuse` in `bin/linux`. +Linux users must build [wwylele/save3ds](https://github.com/wwylele/save3ds) and place `save3ds_fuse` in `bin/linux`. Just install [rust using rustup](https://www.rust-lang.org/tools/install), then compile with: `cargo build`. Your compiled binary is located in `target/debug/save3ds_fuse`, copy it to `bin/linux`. movable.sed is required and can be provided with `-m` or `--movable`. @@ -32,14 +32,30 @@ SeedDB is checked in order of: * `~/.3ds/seeddb.bin` * `~/3ds/seeddb.bin` +## Building finalize +Finalize is **required** for newer games that use seeds. Without finalize, the game may not show up on your 3ds, while it being on your sd card. + +In order to build finalize so you can put it in your `SD:/3ds/` directory (or whatever directory you prefer for homebrew software), you will need devkitARM, or preferrably, using devkitPro's pacman installer: + +If you tell everyone you use Arch (btw), your current pacman package manager will work, you just need the dependencies, skip the step below and [see here.](https://devkitpro.org/wiki/devkitPro_pacman#Customising_Existing_Pacman_Install) + +[Installation instructions for devkitPro Pacman](https://devkitpro.org/wiki/Getting_Started) + * macOS/Linux: `sudo pacman -S 3ds-dev` + +*You may need to add `dpk-` to the devkitPro pacman build, tab completion might help* + +Now head to the directory `finalize/` where you see the Makefile, and run: + +* macOS/Linux: `make` + ## Usage Use `-h` to view arguments. Examples: ``` -py -3 custom-install.py -b boot9.bin -m movable.sed --sd E:\ file.cia file2.cia -python3 custom-install.py -b boot9.bin -m movable.sed --sd /Volumes/GM9SD file.cia file2.cia -python3 custom-install.py -b boot9.bin -m movable.sed --sd /media/GM9SD file.cia file2.cia +py -3 custominstall.py -b boot9.bin -m movable.sed --sd E:\ file.cia file2.cia +python3 custominstall.py -b boot9.bin -m movable.sed --sd /Volumes/GM9SD file.cia file2.cia +python3 custominstall.py -b boot9.bin -m movable.sed --sd /media/GM9SD file.cia file2.cia ``` ## License/Credits diff --git a/gui-custominstall.py b/gui-custominstall.py new file mode 100644 index 0000000..e04d5d8 --- /dev/null +++ b/gui-custominstall.py @@ -0,0 +1,199 @@ +from custominstall import CustomInstall +import tkinter as tk +from tkinter.filedialog import askopenfilenames +from tkinter.ttk import Progressbar +import os +import datetime +import threading +import queue + + +class CustomInstallGui(tk.Frame): + def __init__(self, master=None): + tk.Frame.__init__(self, master) + self.master = master + + # Title name for window + self.window_title = "Custom-Install GUI" + + # Config + self.skip_contents = False + self.cias = [] + self.boot9 = None + self.movable = None + self.sd = None + self.skip_cont_var = tk.IntVar(self) + + for x in range(8): + tk.Grid.rowconfigure(self, x, weight=1) + for x in range(1): + tk.Grid.columnconfigure(self, x, weight=1) + + def set_cias(self, filename): + self.cias = filename + + def set_boot9(self, filename): + self.boot9 = filename + + def set_movable(self, filename): + self.movable = filename + + def set_sd(self, directory): + self.sd = directory + + def start_install(self, event): + self.progress['value'] = 0 + error = False + + # Checks + if len(self.cias) == 0: + self.add_log_msg("Error: Please select CIA file(s)") + error = True + if self.boot9 == None: + self.add_log_msg("Error: Please add your boot9 file") + error = True + if self.movable == None: + self.add_log_msg("Error: Please add your movable file") + if self.sd == None: + self.add_log_msg("Error: Please locate your SD card directory") + self.add_log_msg("Note: Linux usually mounts to /media/") + if error: + self.add_log_msg("--- Errors occured, aborting ---") + return False + + # Start the job + if self.skip_cont_var.get() == 1: self.skip_contents = True + else: self.skip_contents = False + + print(f'{self.cias}\n{self.boot9}\n{self.movable}\n{self.skip_contents}') + self.log.insert(tk.END, "Starting install...\n") + + installer = CustomInstall(boot9=self.boot9, + movable=self.movable, + cias=self.cias, + sd=self.sd, + skip_contents=self.skip_contents) + + + # DEBUG + # self.debug_values() + + def start_install(): + def log_handle(message): self.add_log_msg(message) + def percentage_handle(percent, total_read, size): self.progress['value'] = percent + + installer.event.on_log_msg += log_handle + installer.event.update_percentage += percentage_handle + installer.start() + print('--- Script is done ---') + + t = threading.Thread(target=start_install) + t.start() + + + + + def debug_values(self): + self.add_log_msg(self.boot9) + self.add_log_msg(self.movable) + self.add_log_msg(self.cias) + self.add_log_msg(self.sd) + self.add_log_msg(self.skip_contents) + + def start(self): + self.master.title(self.window_title) + self.pack(fill=tk.BOTH, expand=True) + + self.log = tk.Text(self, height=10, width=40) + install = tk.Button(self, text="Install CIA") + skip_checkbox = tk.Checkbutton(self, text="Skip Contents", variable=self.skip_cont_var) + + self.progress = Progressbar(self, orient=tk.HORIZONTAL, length=100, mode='determinate') + + # File pickers + cia_picker = self.filepicker_option("CIA file(s)", True, self.set_cias) + boot9_picker = self.filepicker_option("Select boot9.bin...", False, self.set_boot9) + movable_picker = self.filepicker_option("Select movable.sed...", False, self.set_movable) + sd_picker = self.filepicker_option("Select SD card...", False, self.set_sd, True) + + # Place widgets + self.log.grid(column=0, row=0, sticky=tk.N+tk.E+tk.W) + self.progress.grid(column=0, row=1, sticky=tk.E+tk.W) + sd_picker.grid(column=0, row=2, sticky=tk.E+tk.W) + boot9_picker.grid(column=0, row=3, sticky=tk.E+tk.W) + movable_picker.grid(column=0, row=4, sticky=tk.E+tk.W) + cia_picker.grid(column=0, row=5, sticky=tk.E+tk.W) + skip_checkbox.grid(column=0, row=6, sticky=tk.E+tk.W) + install.grid(column=0, row=7, sticky=tk.S+tk.E+tk.W) + + + # Events + install.bind('', self.start_install) + + # Just a greeting :) + now = datetime.datetime.now() + time_short = "day!" + if now.hour < 12: time_short = "morning!" + elif now.hour > 12: time_short = "afternoon!" + self.add_log_msg(f'Good {time_short} Please pick your boot9, movable.sed, SD, and CIA file(s).\n---\nPress "Install CIA" when ready!') + + def add_log_msg(self, message): + self.log.insert(tk.END, str(message)+"\n") + self.log.see(tk.END) + + def filepicker_option(self, title, multiple_files, on_file_add, dir_only=False): + frame = tk.Frame(self) + + browse_button = tk.Button(frame, text="Pick file") + filename_label = tk.Label(frame, text=title, wraplength=200) + + browse_button.grid(column=0, row=0) + filename_label.grid(column=1, row=0) + + # Wrapper for event + def file_add(event): + if dir_only: + folder = tk.filedialog.askdirectory() + if not folder: + return False + + dir = os.path.basename(folder) + filename_label.config(text="SD => "+dir) + + on_file_add(folder) + return True + # Returns multiple files in a tuple + filename = (tk.filedialog.askopenfilenames() + if multiple_files else + tk.filedialog.askopenfilename()) + + # User may select "cancel" + if not filename: + return False + + + if multiple_files: + basename = os.path.basename(filename[0]) + if len(filename) <= 1: + more = "" + elif len(filename) > 1: + more = " + "+str(len(filename))+" more" + else: + basename = os.path.basename(filename) + more = "" + + filename_label.config(text=basename+more) + + # Runs callback provided + on_file_add(filename) + + browse_button.bind('', file_add) + + return frame + + +root = tk.Tk() +app = CustomInstallGui(root) +app.start() + +root.mainloop() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..113c820 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pycryptodomex==3.9.1 +events==0.3 \ No newline at end of file