11 Commits

Author SHA1 Message Date
ihaveahax
f81734f293 remove old scripts for pre-package setup 2026-01-11 17:14:28 -06:00
ihaveahax
c81601fec0 format flake.nix 2026-01-11 17:09:49 -06:00
ihaveahax
6c70ee5780 update README for the python packaging update 2026-01-11 16:52:46 -06:00
ihaveahax
cf0fdcd9a1 flake.lock: Update
Flake lock file updates:

• Updated input 'finalize':
    'path:finalize?lastModified=1&narHash=sha256-qQn272f8Z4QhQV2reyd9v0GtypYgnTrOqEiewWnSGJY%3D' (1970-01-01)
  → 'path:finalize?lastModified=1&narHash=sha256-BZgu7%2B/RV9Gy1xo/icz5kd2fKCa3Zow%2BZz6MJWzpgMM%3D' (1970-01-01)
• Updated input 'hax-nur':
    'github:ihaveamac/nur-packages/f612d64a4136c3a4820e37ed50cefb6460dde857' (2026-01-01)
  → 'github:ihaveamac/nur-packages/8ebcd637fd5cd8e673c8e01ed408bf206f9d4f9b' (2026-01-11)
• Updated input 'hax-nur/treefmt-nix':
    'github:numtide/treefmt-nix/dec15f37015ac2e774c84d0952d57fcdf169b54d' (2025-12-30)
  → 'github:numtide/treefmt-nix/0c445aa21b01fd1d4bb58927f7b268568af87b20' (2026-01-10)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/16c7794d0a28b5a37904d55bcca36003b9109aaa' (2026-01-02)
  → 'github:NixOS/nixpkgs/3146c6aa9995e7351a398e17470e15305e6e18ff' (2026-01-10)
2026-01-11 16:45:19 -06:00
ihaveahax
9fba2ff88f finalize/flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/16c7794d0a28b5a37904d55bcca36003b9109aaa' (2026-01-02)
  → 'github:NixOS/nixpkgs/3146c6aa9995e7351a398e17470e15305e6e18ff' (2026-01-10)
2026-01-11 16:45:12 -06:00
ihaveahax
4d8a6de163 include custom-install-finalize.3dsx in package folder, add nix app to build and copy a new version 2026-01-11 16:44:59 -06:00
ihaveahax
69fc8bb39a finalize: add default outputs to flake 2026-01-11 16:32:46 -06:00
ihaveahax
a395c22aee actually include TaskbarLib.tlb and title.db.gz in package 2026-01-11 16:27:44 -06:00
ihaveahax
baf7490de0 update gitignore 2026-01-11 16:27:24 -06:00
ihaveahax
4b41703107 include TaskbarLib.tlb in package 2026-01-11 16:27:02 -06:00
ihaveahax
927ab5c669 add note if custom-install-finalize was not copied, print all messages to stdout in gui 2026-01-11 16:26:17 -06:00
14 changed files with 158 additions and 87 deletions

2
.gitignore vendored
View File

@@ -2,6 +2,7 @@
bin/linux/save3ds_fuse bin/linux/save3ds_fuse
cstins/ cstins/
testing-class.py testing-class.py
*.local
# macOS # macOS
.DS_Store .DS_Store
@@ -12,6 +13,7 @@ venv/
**/__pycache__/ **/__pycache__/
*.pyc *.pyc
*.egg-info/ *.egg-info/
*.whl
# JetBrains # JetBrains
.idea/ .idea/

View File

@@ -1,2 +1,4 @@
recursive-include custominstall/bin/* recursive-include custominstall/bin/*
include custominstall/title.db.gz include custominstall/title.db.gz
include custominstall/TaskbarLib.tlb
include custominstall/custom-install-finalize.3dsx

View File

@@ -12,19 +12,28 @@ Installs a title directly to an SD card for the Nintendo 3DS. Originally created
3. Extract and run ci-gui. Read `windows-quickstart.txt`. 3. Extract and run ci-gui. Read `windows-quickstart.txt`.
### With installed Python ### With installed Python
Note for Windows users: Enabling "Add Python 3.X to PATH" is **NOT** required! Python is installed with the `py` launcher by default.
> [!NOTE]
> Windows users: Enabling "Add Python 3.X to PATH" is **NOT** required! Python is installed with the `py` launcher by default.
1. [Dump boot9.bin and movable.sed](https://ihaveamac.github.io/dump.html) from a 3DS system. 1. [Dump boot9.bin and movable.sed](https://ihaveamac.github.io/dump.html) from a 3DS system.
2. Download the repo ([zip link](https://github.com/ihaveamac/custom-install/archive/safe-install.zip) or `git clone`) 2. Install the packages:
3. Install the packages: * Windows: `py -3 -m pip install --user --upgrade https://github.com/ihaveamac/custom-install/archive/refs/heads/python-package.zip`
* Windows: Double-click `windows-install-dependencies.py` * macOS/Linux: `python3 -m pip install --user --upgrade https://github.com/ihaveamac/custom-install/archive/refs/heads/python-package.zip`
* Alternate manual method: `py -3 -m pip install --user -r requirements-win32.txt`
* macOS/Linux: `python3 -m pip install --user -r requirements.txt` To run the GUI:
4. Run `custominstall.py` with boot9.bin, movable.sed, path to the SD root, and CIA files to install (see Usage section). * Windows: `py -3 -m custominstall.gui`
5. Download and use [custom-install-finalize](https://github.com/ihaveamac/custom-install/releases) on the 3DS system to finish the install. * macOS/Linux: `python3 -m custominstall.gui`
To run the command line version:
* Windows: `py -3 -m custominstall`
* macOS/Linux: `python3 -m custominstall`
## Setup ## Setup
Linux users must build [wwylele/save3ds](https://github.com/wwylele/save3ds) and place `save3ds_fuse` in `bin/linux`. Install [rust using rustup](https://www.rust-lang.org/tools/install), then compile with: `cargo build --release --no-default-features`. The compiled binary is located in `target/release/save3ds_fuse`, copy it to `bin/linux`. Linux users must build [wwylele/save3ds](https://github.com/wwylele/save3ds) and place `save3ds_fuse` in one of these places:
* A directory in `PATH`
* In `custominstall/bin/linux`
* Set the environment variable `CUSTOM_INSTALL_SAVE3DS_PATH` to the `save3ds_fuse` binary
movable.sed is required and can be provided with `-m` or `--movable`. movable.sed is required and can be provided with `-m` or `--movable`.
@@ -55,9 +64,9 @@ Use `-h` to view arguments.
Examples: Examples:
``` ```
py -3 custominstall.py -b boot9.bin -m movable.sed --sd E:\ file.cia file2.cia py -3 -m custominstall -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 -m custominstall -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 python3 -m custominstall -b boot9.bin -m movable.sed --sd /media/GM9SD file.cia file2.cia
``` ```
## GUI ## GUI
@@ -66,26 +75,14 @@ A GUI is provided to make the process easier.
### GUI Setup ### GUI Setup
Linux users may need to install a Tk package: Linux users may need to install a Tk package:
- Ubuntu/Debian: `sudo apt install python3-tk` - Ubuntu/Debian: `sudo apt install python3-tk`
- Manjaro/Arch: `sudo pacman -S tk` - Arch: `sudo pacman -S tk`
Install the requirements listed in "Summary", then run `ci-gui.py`.
## Development ## Development
### Building Windows standalone ### Building Windows standalone
Using a 32-bit version of Python is recommended to build a version to be distributed. > [!WARNING]
> This section is OUTDATED and currently does not work with the Python package setup.
A [virtual environment](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment) is recommended to isolate the packages from system directories. The build script `make-standalone.bat` assumes that the dependencies are in PATH.
Install the dependencies, plus cx-Freeze. In a virtual environment, the specific Python version doesn't need to be requested.
```batch
pip install cx-freeze -r requirements-win32.txt
```
Copy `custom-install-finalize.3dsx` to the project root, this will be copied to the build directory and included in the final archive.
Run `make-standalone.bat`. This will run cxfreeze and make a standalone version at `dist\custom-install-standalone.zip`
## License/Credits ## License/Credits
[save3ds by wwylele](https://github.com/wwylele/save3ds) is used to interact with the Title Database (details in `bin/README`). [save3ds by wwylele](https://github.com/wwylele/save3ds) is used to interact with the Title Database (details in `bin/README`).

Binary file not shown.

View File

@@ -230,8 +230,12 @@ class InstallResults(tk.Toplevel):
else: else:
message = 'Nothing was installed.' message = 'Nothing was installed.'
if install_state['installed'] and copied_3dsx: if install_state['installed']:
message += '\n\ncustom-install-finalize has been copied to the SD card.' if copied_3dsx:
message += '\n\ncustom-install-finalize has been copied to the SD card.'
else:
message += ('\n\nNote: custom-install-finalize was not copied.\n'
'You can either manually copy the 3dsx to your SD card, or use GodMode9 to finish the install.')
if application_count >= 300: if application_count >= 300:
message += (f'\n\nWarning: {application_count} installed applications were detected.\n' message += (f'\n\nWarning: {application_count} installed applications were detected.\n'
@@ -601,6 +605,8 @@ class CustomInstallGUI(ttk.Frame):
if status: if status:
self.status_label.config(text=line) self.status_label.config(text=line)
print(log_msg)
def show_error(self, message): def show_error(self, message):
mb.showerror('Error', message, parent=self.parent) mb.showerror('Error', message, parent=self.parent)

6
finalize/flake.lock generated
View File

@@ -41,11 +41,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1767364772, "lastModified": 1768032153,
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=", "narHash": "sha256-6kD1MdY9fsE6FgSwdnx29hdH2UcBKs3/+JJleMShuJg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa", "rev": "3146c6aa9995e7351a398e17470e15305e6e18ff",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -11,6 +11,7 @@
devShells.x86_64-linux = rec { devShells.x86_64-linux = rec {
custom-install-finalize = pkgs.mkShell.override { stdenv = pkgs.devkitNix.stdenvARM; } {}; custom-install-finalize = pkgs.mkShell.override { stdenv = pkgs.devkitNix.stdenvARM; } {};
cif = custom-install-finalize; cif = custom-install-finalize;
default = custom-install-finalize;
}; };
packages.x86_64-linux = rec { packages.x86_64-linux = rec {
@@ -26,6 +27,7 @@
''; '';
}; };
cif = custom-install-finalize; cif = custom-install-finalize;
default = custom-install-finalize;
}; };
}; };
} }

92
flake.lock generated
View File

@@ -1,5 +1,63 @@
{ {
"nodes": { "nodes": {
"devkitNix": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": [
"finalize",
"nixpkgs"
]
},
"locked": {
"lastModified": 1766539742,
"narHash": "sha256-F6OeM2LrLo2n+Xg5XU4udQR/vuWWrDMKxXRzNXE2ClQ=",
"owner": "bandithedoge",
"repo": "devkitNix",
"rev": "c97f9880737716085e78009cba6bf85ad104628b",
"type": "github"
},
"original": {
"owner": "bandithedoge",
"repo": "devkitNix",
"type": "github"
}
},
"finalize": {
"inputs": {
"devkitNix": "devkitNix",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1,
"narHash": "sha256-BZgu7+/RV9Gy1xo/icz5kd2fKCa3Zow+Zz6MJWzpgMM=",
"path": "finalize",
"type": "path"
},
"original": {
"path": "finalize",
"type": "path"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"hax-nur": { "hax-nur": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -8,11 +66,11 @@
"treefmt-nix": "treefmt-nix" "treefmt-nix": "treefmt-nix"
}, },
"locked": { "locked": {
"lastModified": 1767302708, "lastModified": 1768151313,
"narHash": "sha256-uCSEH/PR5/JxwuMayB4fMcOhOCT7I6BzWp7EtEYYjFQ=", "narHash": "sha256-qcMLsdACTlFHltziBAsS1r09cVZyp5fUR16//mIhLIs=",
"owner": "ihaveamac", "owner": "ihaveamac",
"repo": "nur-packages", "repo": "nur-packages",
"rev": "f612d64a4136c3a4820e37ed50cefb6460dde857", "rev": "8ebcd637fd5cd8e673c8e01ed408bf206f9d4f9b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -24,11 +82,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1767364772, "lastModified": 1768032153,
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=", "narHash": "sha256-6kD1MdY9fsE6FgSwdnx29hdH2UcBKs3/+JJleMShuJg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa", "rev": "3146c6aa9995e7351a398e17470e15305e6e18ff",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -61,11 +119,27 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"finalize": "finalize",
"hax-nur": "hax-nur", "hax-nur": "hax-nur",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"pyctr": "pyctr" "pyctr": "pyctr"
} }
}, },
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"treefmt-nix": { "treefmt-nix": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -74,11 +148,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1767122417, "lastModified": 1768031762,
"narHash": "sha256-yOt/FTB7oSEKQH9EZMFMeuldK1HGpQs2eAzdS9hNS/o=", "narHash": "sha256-b2gJDJfi+TbA7Hu2sKip+1mWqya0GJaWrrXQjpbOVTU=",
"owner": "numtide", "owner": "numtide",
"repo": "treefmt-nix", "repo": "treefmt-nix",
"rev": "dec15f37015ac2e774c84d0952d57fcdf169b54d", "rev": "0c445aa21b01fd1d4bb58927f7b268568af87b20",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -7,6 +7,8 @@
pyctr.inputs.nixpkgs.follows = "nixpkgs"; pyctr.inputs.nixpkgs.follows = "nixpkgs";
hax-nur.url = "github:ihaveamac/nur-packages/master"; hax-nur.url = "github:ihaveamac/nur-packages/master";
hax-nur.inputs.nixpkgs.follows = "nixpkgs"; hax-nur.inputs.nixpkgs.follows = "nixpkgs";
finalize.url = "path:finalize";
finalize.inputs.nixpkgs.follows = "nixpkgs";
}; };
outputs = outputs =
@@ -15,6 +17,7 @@
nixpkgs, nixpkgs,
pyctr, pyctr,
hax-nur, hax-nur,
finalize,
}: }:
let let
systems = [ systems = [
@@ -43,5 +46,35 @@
packages = forAllSystems ( packages = forAllSystems (
system: nixpkgs.lib.filterAttrs (_: v: nixpkgs.lib.isDerivation v) self.legacyPackages.${system} system: nixpkgs.lib.filterAttrs (_: v: nixpkgs.lib.isDerivation v) self.legacyPackages.${system}
); );
apps = forAllSystems (
system:
let
pkgs = import nixpkgs { inherit system; };
in
{
gui = {
type = "app";
program = "${self.packages.${system}.custominstall}/bin/custominstall-gui";
};
}
// (
if system == "x86_64-linux" then
# this only works on x86_64-linux due to devkitNix only working there
{
update-finalize = {
type = "app";
program =
(pkgs.writeShellScript "update-finalize" ''
set -x
finalize=${inputs.finalize.packages.${system}.custom-install-finalize}/custom-install-finalize.3dsx
cp --no-preserve=mode,ownership,timestamps $finalize custominstall/custom-install-finalize.3dsx
'').outPath;
};
}
else
{ }
)
);
}; };
} }

View File

@@ -1,13 +0,0 @@
mkdir build
mkdir dist
python setup-cxfreeze.py build_exe --build-exe=build\custom-install-standalone
mkdir build\custom-install-standalone\bin
copy TaskbarLib.tlb build\custom-install-standalone
copy bin\win32\save3ds_fuse.exe build\custom-install-standalone\bin
copy bin\README build\custom-install-standalone\bin
copy custom-install-finalize.3dsx build\custom-install-standalone
copy title.db.gz build\custom-install-standalone
copy extras\windows-quickstart.txt build\custom-install-standalone
copy extras\run_with_cmd.bat build\custom-install-standalone
copy LICENSE.md build\custom-install-standalone
python -m zipfile -c dist\custom-install-standalone.zip build\custom-install-standalone

View File

@@ -45,4 +45,4 @@ find = {namespaces = false}
# is it even possible to make these OS-specific with pyproject.toml? # is it even possible to make these OS-specific with pyproject.toml?
[tool.setuptools.package-data] [tool.setuptools.package-data]
custominstall = ["bin/darwin/save3ds_fuse", "bin/win32/save3ds_fuse.exe"] custominstall = ["bin/darwin/save3ds_fuse", "bin/win32/save3ds_fuse.exe", "TaskbarLib.tlb", "title.db.gz", "custom-install-finalize.3dsx"]

View File

@@ -1,19 +0,0 @@
import sys
from cx_Freeze import setup, Executable
if sys.platform == 'win32':
executables = [
Executable('ci-gui.py', target_name='ci-gui-console'),
Executable('ci-gui.py', target_name='ci-gui', base='Win32GUI'),
]
else:
executables = [
Executable('ci-gui.py', target_name='ci-gui'),
]
setup(
name = "ci-gui",
version = "2.1b4",
description = "Installs a title directly to an SD card for the Nintendo 3DS",
executables = executables
)

View File

@@ -1,13 +0,0 @@
# This is meant to be double-clicked from File Explorer.
# This doesn't import pip as a module in case the way it's executed changes, which it has in the past.
# Instead we call it like we would in the command line.
from subprocess import run
from os.path import dirname, join
from sys import executable
root_dir = dirname(__file__)
run([executable, '-m', 'pip', 'install', '--user', '-r', join(root_dir, 'requirements-win32.txt')])
input('Press enter to close')