mirror of
https://github.com/ihaveamac/custom-install.git
synced 2025-12-06 06:41:45 +00:00
GUI
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,5 +2,5 @@
|
||||
bin/linux/save3ds_fuse
|
||||
**/__pycache__
|
||||
**/finalize
|
||||
cstinst/
|
||||
cstins/
|
||||
testing-class.py
|
||||
32
README.md
32
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
|
||||
|
||||
199
gui-custominstall.py
Normal file
199
gui-custominstall.py
Normal 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
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
pycryptodomex==3.9.1
|
||||
events==0.3
|
||||
Reference in New Issue
Block a user