This commit is contained in:
nek0bit
2019-11-10 18:43:21 -05:00
parent 0fb0d65c68
commit 74522dabad
4 changed files with 226 additions and 9 deletions

2
.gitignore vendored
View File

@@ -2,5 +2,5 @@
bin/linux/save3ds_fuse
**/__pycache__
**/finalize
cstinst/
cstins/
testing-class.py

View File

@@ -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

199
gui-custominstall.py Normal file
View File

@@ -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('<Button-1>', 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('<Button-1>', file_add)
return frame
root = tk.Tk()
app = CustomInstallGui(root)
app.start()
root.mainloop()

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
pycryptodomex==3.9.1
events==0.3