Add a native two-step iPhone build app and restore the visual gallery
Provide folder selection, build progress, process-group cancellation, and IPA/RomFS/Sideloadly buttons. Preserve command documentation separately and correct the personal author policy.
This commit is contained in:
1 parent
585eb02cdf
commit
183c4d958b
10 files changed
+739
-248
No files matched your search
@@ -19,7 +19,7 @@
|
||||
- To analyse or port **another** Switch title, first run `scripts/build_ipa_from_nsp.py` on its NSP folder. With no matching profile, it writes a survey to `<NSP folder>/<title ID>_survey/survey.md`, then a provisional profile guessed from the game files (`dm/games/profile_guess.py`; `<NSP folder>/<title ID>_build/profile/<title ID>/`, found by the tools through `DM_GAMES_DIR`), with a short heap check on the Mac when the game has virtual address memory, and a best-effort IPA that may not boot. Then follow [docs/agents](docs/agents/README.md) (extraction, binary survey, host bring-up, graphics specifics, debugging recipes; the [symptom index](docs/agents/symptoms.md) for each failure) and add a profile with its note.
|
||||
- Scripts the user runs live in `scripts/` at the repository root. Each one runs from a terminal (non-zero exit on error). `build_ipa_from_nsp.py` also runs in Spyder (F5, configured by the settings block at its top; command-line options override those settings); the others need not. Tools that only agents or other scripts call stay under `dm/`.
|
||||
- Game files, keys and anything extracted from them live in the game's NSP folder, never in this repository and never in the home folder (it is nearly full): the work folder `<NSP folder>/<id>-work` for extraction, runs and analysis, beside the NSP script's `<id>_build/`. Tool venvs and other large scratch data go on that drive too. Do not print key values.
|
||||
- Language: everything is in English: `README.md`, docs, code comments, log messages, script output and other files. Red log lines (something unimplemented or wrong) start with `[RED] `. Text that must match a game's own Chinese data is written as escapes (`"\xE8\xB5\xB7"`), never as characters.
|
||||
- Language: the personal README and desktop player UI are in Spanish, as requested by the repository owner; technical documentation and code remain in English: `README.md`, docs, code comments, log messages, script output and other files. Red log lines (something unimplemented or wrong) start with `[RED] `. Text that must match a game's own Chinese data is written as escapes (`"\xE8\xB5\xB7"`), never as characters.
|
||||
- The shader translation chain has one version for every game, `DMSH_CHAIN` in `dm/gfx/nvn/dmsh_chain.h`, which the host is compiled with and loads only libraries of. It covers the chain's tools: `dm/gfx/tools/dmsh_*` (`dmsh_build.py`, `dmsh_chain.py`, `dmsh_fetch.py`, `dmsh_vin.py`, `dmsh_pull.py` with its decoder `dmsh_pull.metal`, `dmsh_geometry.py`, `dmsh_facts.py`), `Program.cs`, `ios_metal.py` and `dmsh_chain.lock`. A profile's own inputs (`shader_map.tsv`, `shader_facts.tsv`, `shader_pairs.tsv`, `bf_want.tsv`, its `shader_*` / `nvn_*` keys) are not part of it: they are in each shader's cache key, and their digest stamps the libraries (`b-<V>/inputs.tsv`). The procedure:
|
||||
1. After changing a chain tool, run `dmsh_chain.py --header` once and commit `dm/gfx/nvn/dmsh_chain.h` with the change (`dm/tools/tests/test_nsp_chain.py` fails on a stale one).
|
||||
2. After that, or after changing a profile's inputs, rebuild that profile's shaders: a full `dmsh_build.py` run (Mac and, with `IOSOUT`, iOS) stamps its libraries and writes the profile's `c1_full.tsv` and `cb_extent.tsv` from its Mac outputs (git ignores them; the host takes them only beside the stamp they name). Give each game its own output directory.
|
||||
@@ -27,4 +27,4 @@
|
||||
|
||||
The translator is `dm/gfx/tools/dmsh_xlate_bin`, which matches `dmsh_chain.lock`; a fresh `dotnet publish` does not.
|
||||
- iOS builds are unsigned IPAs that the user sideloads and tests; diagnostics come back as the app's `Documents/log.txt`. Do not sign or install builds on the user's devices.
|
||||
- Commit only when asked, with `git -c user.name=seedds -c user.email=seedds@gmail.com commit`.
|
||||
- Commit only when requested or as part of an authorized repository upload. Personal commits use the repository owner: `Erick Sanchez <7268597+nalancer08@users.noreply.github.com>`. Never attribute personal changes to seedds; retain original upstream authors.
|
||||
@@ -0,0 +1,260 @@
|
||||
# nxnative personal · Juegos de Switch en iPhone y iPad
|
||||
|
||||
Esta es nuestra copia privada de [nxnative](https://github.com/seedds/nxnative), con **editor de controles táctiles** y **scripts que preparan herramientas, compilan y generan una IPA ligera** para instalar con Sideloadly.
|
||||
|
||||
**Empieza aquí:** elige un juego compatible → ejecuta un comando → instala la IPA ligera → copia `romfs` por Finder la primera vez.
|
||||
|
||||
[Qué juegos correr](#qué-juegos-correr) · [Preparar el Mac](#preparar-el-mac) · [Compilar fácilmente](#compilar-fácilmente) · [Instalar con Sideloadly](#instalar-con-sideloadly) · [Actualizar sin perder partidas](#actualizar-sin-perder-partidas) · [README anterior](README-original.md)
|
||||
|
||||
## Qué juegos correr
|
||||
|
||||
**Metroid Dread 2.1.0 es el juego confirmado funcionando en nuestro iPhone.** También comprobamos su recompilación sin firma y la generación automática de la IPA ligera. El editor nuevo todavía requiere comprobar su uso físico tras instalarlo.
|
||||
|
||||
Esta base incluye perfiles para los juegos de la tabla completa de abajo. Upstream publica pruebas en Mac o iPhone según el título: **tener un perfil o una IPA compilada no confirma que ese juego funcione en tu iPhone**. Abre la ficha del juego para comprobar plataforma, versión y problemas conocidos.
|
||||
|
||||
Algunos títulos incluidos para empezar a explorar:
|
||||
|
||||
| Juego | Versión del perfil | Referencia |
|
||||
|---|---|---|
|
||||
| **Metroid Dread** | **2.1.0** | [Ficha](dm/games/010093801237C000/README.md) · confirmado en nuestro iPhone |
|
||||
| Super Mario Bros. Wonder | 1.2.1 / 1.0.0 | [Ficha y límites](dm/games/010015100B514000/README.md) |
|
||||
| Super Mario Odyssey | 1.4.1 | [Ficha y límites](dm/games/0100000000010000/README.md) |
|
||||
| Mario Kart 8 Deluxe | 4.0.0 | [Ficha y límites](dm/games/0100152000022000/README.md) |
|
||||
| The Legend of Zelda: Breath of the Wild | 1.9.0 | [Ficha y límites](dm/games/01007EF00011E000/README.md) |
|
||||
| The Legend of Zelda: Tears of the Kingdom | 1.4.3 | [Ficha y límites](dm/games/0100F2C0115B6000/README.md) |
|
||||
| The Legend of Zelda: Link's Awakening | 1.1.0 | [Ficha y límites](dm/games/01006BB00C6F0000/README.md) |
|
||||
| Hades | 1.0.38233 | [Ficha y límites](dm/games/0100535012974000/README.md) |
|
||||
| Cuphead | 1.3.7 | [Ficha y límites](dm/games/0100A5C00D162000/README.md) |
|
||||
| Celeste | 1.4.1.1 | [Ficha y límites](dm/games/01002B30028F6000/README.md) |
|
||||
| Balatro | 1.1.3 | [Ficha y límites](dm/games/0100CD801CE5E000/README.md) |
|
||||
|
||||
<details>
|
||||
<summary>Ver los 70 perfiles incluidos y sus versiones</summary>
|
||||
|
||||
| Juego y ficha | Versión del perfil |
|
||||
|---|---|
|
||||
| [13 Sentinels: Aegis Rim](dm/games/01003FC01670C000/README.md) | 1.10 |
|
||||
| [13 Sentinels: Aegis Rim, Asian release](dm/games/01008D7016438000/README.md) | 1.00 (base) |
|
||||
| [Absolum](dm/games/0100A83022A22000/README.md) | 1.1.1 |
|
||||
| [Ace Attorney Investigations Collection](dm/games/010005501E68C000/README.md) | 1.0.0.2 |
|
||||
| [Advance Wars 1+2: Re-Boot Camp](dm/games/0100300012F2A000/README.md) | 1.0.0 (base) |
|
||||
| [AI: THE SOMNIUM FILES](dm/games/010089B00D09C000/README.md) | 1.0.1 |
|
||||
| [ASTRAL CHAIN](dm/games/01007300020FA000/README.md) | 1.0.1 |
|
||||
| [AI: THE SOMNIUM FILES - nirvanA Initiative](dm/games/0100713016CCC000/README.md) | 1.0.2 |
|
||||
| [ANIMAL WELL](dm/games/010020D01AD24000/README.md) | 1.0.10 |
|
||||
| [Animal Crossing: New Horizons](dm/games/01006F8002326000/README.md) | 3.0.3 |
|
||||
| [BALL x PIT](dm/games/010086A022444000/README.md) | 1.293 |
|
||||
| [Balatro](dm/games/0100CD801CE5E000/README.md) | 1.1.3 |
|
||||
| [Bayonetta](dm/games/010076F0049A2000/README.md) | 1.1 |
|
||||
| [Bayonetta 2](dm/games/01007960049A0000/README.md) | 1.2 |
|
||||
| [Bayonetta 3](dm/games/01004A4010FEA000/README.md) | 1.2.0 |
|
||||
| [Big Brain Academy: Brain vs. Brain](dm/games/0100620012D6E000/README.md) | 1.2.0 |
|
||||
| [Brain Memory Training Academy](dm/games/0100F5D01DA3A000/README.md) | 1.0.0 (base) |
|
||||
| [Captain Toad: Treasure Tracker](dm/games/01009BF0072D4000/README.md) | 1.4.2 |
|
||||
| [Catherine: Full Body](dm/games/0100BF00112C0000/README.md) | 1.0.1a |
|
||||
| [Celeste](dm/games/01002B30028F6000/README.md) | 1.4.1.1 |
|
||||
| [Cuphead](dm/games/0100A5C00D162000/README.md) | 1.3.7 |
|
||||
| [DARK SOULS: REMASTERED](dm/games/01004AB00A260000/README.md) | 1.0.3 |
|
||||
| [Digimon Story Cyber Sleuth: Complete Edition](dm/games/010014E00DB56000/README.md) | 1.0.3 |
|
||||
| [DRAGON QUEST VII Reimagined](dm/games/0100A9D01C446000/README.md) | 1.2.0 |
|
||||
| [DRAGON QUEST XI S: Echoes of an Elusive Age – Definitive Edition](dm/games/01006C300E9F0000/README.md) | 1.0.4 |
|
||||
| [FINAL FANTASY RESONANCE](dm/games/0100606026F12000/README.md) | 1.0.1 |
|
||||
| [FINAL FANTASY TACTICS - The Ivalice Chronicles](dm/games/010038B015560000/README.md) | 1.4.0 |
|
||||
| [Fire Emblem Engage](dm/games/0100A6301214E000/README.md) | 2.0.0 |
|
||||
| [Fire Emblem: Three Houses](dm/games/010055D009F78000/README.md) | 1.2.0 |
|
||||
| [Ghost Trick: Phantom Detective](dm/games/010029B018432000/README.md) | 1.0.0 (base) |
|
||||
| [Hades](dm/games/0100535012974000/README.md) | 1.0.38233 |
|
||||
| [Hades II](dm/games/0100A00019DE0000/README.md) | base game |
|
||||
| [Hollow Knight: Silksong](dm/games/010013C00E930000/README.md) | 1.0.30000 |
|
||||
| [Hyrule Warriors: Age of Calamity](dm/games/01002B00111A2000/README.md) | 1.3.0 |
|
||||
| [Luigi's Mansion 3](dm/games/0100DCA0064A6000/README.md) | 1.4.0 |
|
||||
| [Mario Kart 8 Deluxe](dm/games/0100152000022000/README.md) | 4.0.0 |
|
||||
| [Mario vs. Donkey Kong](dm/games/0100B99019412000/README.md) | 1.0.1 (cartridge base) |
|
||||
| [Metroid Dread](dm/games/010093801237C000/README.md) | 2.1.0 |
|
||||
| [Monster Hunter Rise](dm/games/0100B04011742000/README.md) | 16.0.2 |
|
||||
| [Monster Train 2](dm/games/010051701FB46000/README.md) | 1.1.1 |
|
||||
| [NieR:Automata The End of YoRHa Edition](dm/games/010056B015FE8000/README.md) | 1.0.3 |
|
||||
| [Octopath Traveler 0](dm/games/01005270232F2000/README.md) | 1.0.5 |
|
||||
| [OCTOPATH TRAVELER II](dm/games/0100A3501946E000/README.md) | 1.1.1 |
|
||||
| [Paper Mario: The Thousand-Year Door](dm/games/0100ECD018EBE000/README.md) | 1.0.1 |
|
||||
| [Patrick's Parabox](dm/games/0100E44016C22000/README.md) | 1.0.1 |
|
||||
| [Persona 5 Royal](dm/games/01005CA01580E000/README.md) | 1.0.2 |
|
||||
| [Pikmin 4](dm/games/0100B7C00933A000/README.md) | 1.1.1 |
|
||||
| [Pokémon Brilliant Diamond](dm/games/0100000011D90000/README.md) | 1.3.0 |
|
||||
| [Pokémon Legends: Z-A](dm/games/0100F43008C44000/README.md) | 2.0.2 |
|
||||
| [Pokémon Shield](dm/games/01008DB008C2C000/README.md) | 1.3.2 |
|
||||
| [Pokémon Sword](dm/games/0100ABF008968000/README.md) | 1.3.2 |
|
||||
| [Portal](dm/games/01007BB017812000/README.md) | 1.0.3 |
|
||||
| [Raging Loop](dm/games/010020400E1C2000/README.md) | 1.0.0 (base) |
|
||||
| [Rhythm Heaven Groove](dm/games/0100D9F01D474000/README.md) | 1.0.0 (base) |
|
||||
| [ROMANCE OF THE THREE KINGDOMS 8 REMAKE](dm/games/0100C2A01A730000/README.md) | 1.1.3 |
|
||||
| [STEINS;GATE RE:BOOT](dm/games/01008BC024FDE000/README.md) | 1.0.3 |
|
||||
| [Stardew Valley](dm/games/0100E65002BB8000/README.md) | 1.6.15.13 |
|
||||
| [Super Mario 3D World + Bowser's Fury](dm/games/010028600EBDA000/README.md) | 1.2.2 |
|
||||
| [Super Mario Bros. Wonder](dm/games/010015100B514000/README.md) | 1.2.1 (and 1.0.0) |
|
||||
| [Super Mario Galaxy](dm/games/010099C022B96000/README.md) | 1.3.1 |
|
||||
| [Super Mario Galaxy 2](dm/games/0100FD8022DAA000/README.md) | 1.4.0 |
|
||||
| [Super Mario Odyssey](dm/games/0100000000010000/README.md) | 1.4.1 |
|
||||
| [Super Smash Bros. Ultimate](dm/games/01006A800016E000/README.md) | 13.0.5 |
|
||||
| [Taiko Risshiden V DX](dm/games/0100346017304000/README.md) | 1.1.1 |
|
||||
| [The Great Ace Attorney Chronicles](dm/games/010036E00FB20000/README.md) | 1.0.0 (base) |
|
||||
| [The Legend of Zelda: Breath of the Wild](dm/games/01007EF00011E000/README.md) | 1.9.0 |
|
||||
| [The Legend of Zelda: Echoes of Wisdom](dm/games/01008CF01BAAC000/README.md) | 1.1.0 |
|
||||
| [The Legend of Zelda: Link's Awakening](dm/games/01006BB00C6F0000/README.md) | 1.1.0 |
|
||||
| [The Legend of Zelda: Tears of the Kingdom](dm/games/0100F2C0115B6000/README.md) | 1.4.3 |
|
||||
| [Unicorn Overlord](dm/games/010069401ADB8000/README.md) | 1.05 |
|
||||
|
||||
</details>
|
||||
|
||||
La región y versión del juego importan. Un título sin perfil puede recibir una compilación provisional y aun así no arrancar. Los PR de juegos nuevos que sólo revisamos no están incluidos automáticamente; consulta [la revisión de PR](docs/personal-pr-review.md).
|
||||
|
||||
## Preparar el Mac
|
||||
|
||||
Necesitas:
|
||||
|
||||
- Un Mac con **Xcode completo** instalado y abierto al menos una vez, y SDK de iOS 26 o posterior.
|
||||
- [Homebrew](https://brew.sh) instalado. El script instala las demás herramientas que falten.
|
||||
- Un iPhone con **A17 Pro o posterior**, o iPad con **M1 o posterior**, y **iOS/iPadOS 26 o posterior**, según los requisitos de esta base. El soporte para otros chips no se incorporó desde los PR revisados.
|
||||
- Tus archivos del juego y `prod.keys`, en una carpeta fuera del repositorio.
|
||||
- Espacio para los archivos originales, la extracción, cachés, la IPA completa y otra copia de `romfs`.
|
||||
- [Sideloadly](https://sideloadly.io) para firmar e instalar con tu cuenta Apple personal.
|
||||
|
||||
En un Mac nuevo, descarga nuestra copia privada. Debes tener acceso con tu cuenta GitHub:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/nalancer08/nxnative-personal.git
|
||||
cd nxnative-personal
|
||||
```
|
||||
|
||||
**Si ya tienes nuestra copia en este Mac, abre Terminal en esa carpeta y omite el clonado.** No clones de nuevo el repositorio original para compilar: esa copia no tendría nuestro editor ni estos scripts.
|
||||
|
||||
## Compilar fácilmente
|
||||
|
||||
### Opción 1: un comando y selector de carpeta
|
||||
|
||||
Desde la carpeta del repositorio:
|
||||
|
||||
```sh
|
||||
bash scripts/prepare_ios.sh
|
||||
```
|
||||
|
||||
Selecciona la carpeta que contiene un solo juego con sus actualizaciones. El script pide `prod.keys` si no está en esa carpeta, prepara las herramientas, compila y separa los datos automáticamente.
|
||||
|
||||
### Opción 2: indicar la carpeta
|
||||
|
||||
Cambia la ruta del ejemplo por la tuya y conserva las comillas:
|
||||
|
||||
```sh
|
||||
bash scripts/prepare_ios.sh "/Volumes/Juegos/MetroidDread"
|
||||
```
|
||||
|
||||
La carpeta puede tener esta estructura; no cambies las extensiones de los archivos:
|
||||
|
||||
```text
|
||||
MetroidDread/
|
||||
juego-base.nsp
|
||||
actualizacion.nsp
|
||||
prod.keys
|
||||
```
|
||||
|
||||
También se admiten XCI y, con soporte adicional instalado por el script, NSZ/XCZ. Usa una carpeta distinta para cada juego. Por defecto se selecciona la actualización más reciente presente; puedes fijar una con `--package`.
|
||||
|
||||
### Opción 3: doble clic
|
||||
|
||||
Abre **`scripts/Build-iPhone.command`** desde Finder. Ejecuta el mismo flujo y te pide la carpeta. En nuestro Mac también está el acceso `Preparar-juego-iPhone.command` en Descargas.
|
||||
|
||||
### Qué obtienes
|
||||
|
||||
Al terminar, la consola muestra las rutas exactas. Junto al juego encontrarás:
|
||||
|
||||
```text
|
||||
nxnative-ios-FECHA-unsigned.ipa ← copia completa, se conserva
|
||||
nxnative-ios-FECHA-unsigned-light-XXXX/
|
||||
nxnative-ios-FECHA-unsigned-light.ipa ← instala ESTA con Sideloadly
|
||||
romfs/ ← datos del juego para Finder
|
||||
INSTALL.txt
|
||||
nxnative-ios.json ← identidad y opciones de futuras compilaciones
|
||||
```
|
||||
|
||||
Los nombres incluyen fecha y una parte única. La IPA ligera de Dread mide aproximadamente **57 MB**; sus datos van aparte. Otros juegos pueden tener tamaños distintos.
|
||||
|
||||
El proceso conserva las salidas anteriores y reutiliza las cachés válidas. **La IPA se genera sin firmar**: la cuenta Apple se usa después, dentro de Sideloadly. No se suben juegos, llaves ni partidas a GitHub.
|
||||
|
||||
## Instalar con Sideloadly
|
||||
|
||||
1. Descarga y abre [Sideloadly](https://sideloadly.io) en el Mac.
|
||||
2. Conecta el iPhone por USB, desbloquéalo y acepta **Confiar** si lo solicita.
|
||||
3. Selecciona tu iPhone en Sideloadly y arrastra la **IPA que termina en `-light.ipa`**.
|
||||
4. Usa tu **cuenta Apple personal**, la misma que utilizaste si estás actualizando una instalación existente. En este proyecto no usamos cuentas ni equipos de empresa.
|
||||
5. Pulsa **Start** y completa la autenticación que solicite Sideloadly.
|
||||
6. Si el iPhone lo solicita, activa **Ajustes → Privacidad y seguridad → Modo de desarrollador**. Para un desarrollador no confiable, revisa **Ajustes → General → VPN y gestión de dispositivos** y autoriza la cuenta con la que firmaste.
|
||||
|
||||
Consulta las instrucciones vigentes y los errores en la [FAQ oficial de Sideloadly](https://sideloadly.io/faq). Con cuenta gratuita, la firma normalmente dura **7 días**; Sideloadly también ofrece renovación automática cuando su configuración y conexión lo permiten.
|
||||
|
||||
### Copiar los datos con Finder: primera instalación
|
||||
|
||||
1. En Finder, selecciona el **iPhone → Archivos → la app del juego**.
|
||||
2. Arrastra la carpeta **`romfs` completa** del resultado de compilación a los documentos de esa app y espera a que termine.
|
||||
3. Abre el juego. Si ya estaba abierto, ciérralo por completo y ábrelo otra vez.
|
||||
|
||||
En Dread, la estructura dentro de la app debe ser `Documents/romfs/system/shd/...`. Evita `romfs/romfs`. En otros juegos el contenido cambia, pero el directorio que copias sigue siendo `romfs`.
|
||||
|
||||
Si la app no aparece en Archivos de Finder, ábrela una vez y ciérrala; aún sin los datos puede fallar al arrancar. Copia `romfs` y vuelve a abrirla. [Guía de Apple para compartir archivos](https://support.apple.com/en-us/119585).
|
||||
|
||||
## Actualizar sin perder partidas
|
||||
|
||||
**Instala encima de la app existente. No la desinstales.** Desinstalar elimina sus documentos, partidas y datos copiados.
|
||||
|
||||
- Usa la **misma cuenta Apple personal** y el **mismo bundle ID efectivo** de la instalación anterior. Si configuraste un identificador personalizado en Sideloadly, consérvalo.
|
||||
- Mantén `nxnative-ios.json`: nuestros scripts guardan ahí la identidad para siguientes compilaciones. Eso no sustituye los ajustes que Sideloadly haya aplicado al firmar.
|
||||
- Haz una copia de `save` desde los documentos de la app antes de cambiar de versión, cuando esté disponible y con el juego cerrado.
|
||||
- Una actualización del editor puede reutilizar el `romfs` del teléfono si los datos del juego siguen siendo idénticos. Una actualización del juego puede necesitar el nuevo `romfs`.
|
||||
- Para renovar una firma, vuelve a instalar la misma IPA con la misma identidad o usa la renovación de Sideloadly. [Referencia oficial sobre actualizar y conservar progreso](https://sideloadly.io/faq).
|
||||
|
||||
### Recompilar sólo la app con los datos ya preparados
|
||||
|
||||
Para cambios del editor u otros cambios de la app, cuando ya existen extracción, módulos reescritos y shaders válidos:
|
||||
|
||||
```sh
|
||||
bash scripts/prepare_ios.sh "/Volumes/Juegos/MetroidDread" --app-only
|
||||
```
|
||||
|
||||
Este modo no necesita llaves ni vuelve a extraer el juego. Recompila y vuelve a generar las IPA y una copia de `romfs`. Verifica las cachés; si no son válidas, se detiene. Para una versión de juego o cadena de shaders distinta, usa el comando normal.
|
||||
|
||||
## Mover y ajustar los controles
|
||||
|
||||
Dentro del juego toca **Edit controls**:
|
||||
|
||||
1. Selecciona A, B, X, Y u otro control en el menú, o toca cerca de su centro.
|
||||
2. Arrastra fuera del panel para mover el seleccionado. **Show / hide tools** muestra u oculta el panel para acceder al área detrás.
|
||||
3. Ajusta **Size** entre 50–200% y **Opacity** entre 0–100%.
|
||||
4. **Save** guarda; **Cancel** descarta. **Reset control** restaura el seleccionado y **Reset all** restaura el layout; confirma con **Save**.
|
||||
|
||||
Cada juego guarda sus propios acomodos de **2D y 3D**. Un control al 0% sigue recibiendo toques; el editor lo muestra parcialmente para encontrarlo. **El juego sigue corriendo mientras editas.** Conectar o desconectar un mando cancela los cambios de edición no guardados.
|
||||
|
||||
## Si algo falla
|
||||
|
||||
| Problema | Qué revisar |
|
||||
|---|---|
|
||||
| No hay NSP/XCI en la carpeta | Elige la carpeta que contiene los archivos directamente, no su carpeta padre. |
|
||||
| Faltan herramientas | Instala Xcode y Homebrew; ejecuta otra vez el preparador y revisa el primer error. |
|
||||
| Caché de shaders inválida en `--app-only` | Ejecuta el flujo normal para regenerar lo que haya cambiado. |
|
||||
| Sideloadly no detecta el iPhone | Desbloquéalo, revisa el cable USB y acepta Confiar en el Mac/iPhone. |
|
||||
| El juego no abre después de instalar | Comprueba que `romfs` se copió completo, la versión y los requisitos de la ficha del juego. |
|
||||
| El juego dejó de abrir días después | Revisa si venció la firma y renueva conservando la cuenta y el identificador. |
|
||||
| Error de compilación o pantalla incorrecta | Conserva el error y los logs de `<title ID>_build/logs/`; en el teléfono, busca `Documents/log.txt`. |
|
||||
|
||||
Los mensajes de compilación se muestran en inglés. El nuevo preparador usa esta copia del código y no exige borrar tus modificaciones ni volver al checkout original.
|
||||
|
||||
## Qué cambiamos en esta copia
|
||||
|
||||
- Editor compartido de posición, tamaño y opacidad por control, con guardado por juego/layout.
|
||||
- Preparación de dependencias y compilación desde nuestra propia base.
|
||||
- Exportación automática de IPA ligera y `romfs`, validación de integridad y conservación de archivos anteriores.
|
||||
- Registro de identidad por juego y bloqueo de compilaciones simultáneas desde el wrapper.
|
||||
- Corrección del cálculo de espacio de extracción del PR #277.
|
||||
- [Revisión de 57 PR abiertos y 20 cambios posteriores](docs/personal-pr-review.md). Sólo #277 se integró desde esa revisión; los demás conservan decisiones y validaciones pendientes.
|
||||
|
||||
Guías adicionales: [automatización detallada](docs/personal-ios.md) · [documentación de desarrollo](docs/development.md) · [README anterior con la galería y créditos completos](README-original.md).
|
||||
|
||||
Este proyecto deriva de **seedds/nxnative** y conserva la [licencia MIT](LICENSE). Los créditos y licencias de sus componentes siguen en el [README anterior](README-original.md#acknowledgements) y en [third_party](dm/third_party/README.md). El repositorio no contiene los juegos ni sus llaves.
|
||||
@@ -1,260 +1,164 @@
|
||||
# nxnative personal · Juegos de Switch en iPhone y iPad
|
||||
# nxnative personal
|
||||
|
||||
Esta es nuestra copia privada de [nxnative](https://github.com/seedds/nxnative), con **editor de controles táctiles** y **scripts que preparan herramientas, compilan y generan una IPA ligera** para instalar con Sideloadly.
|
||||
**Tus juegos, en tu iPhone. Con imágenes y sin comandos para el uso diario.**
|
||||
|
||||
**Empieza aquí:** elige un juego compatible → ejecuta un comando → instala la IPA ligera → copia `romfs` por Finder la primera vez.
|
||||

|
||||
|
||||
[Qué juegos correr](#qué-juegos-correr) · [Preparar el Mac](#preparar-el-mac) · [Compilar fácilmente](#compilar-fácilmente) · [Instalar con Sideloadly](#instalar-con-sideloadly) · [Actualizar sin perder partidas](#actualizar-sin-perder-partidas) · [README anterior](README-original.md)
|
||||
## Abre la app y elige tu juego
|
||||
|
||||
## Qué juegos correr
|
||||
1. Abre **nxnative.app**.
|
||||
2. Pulsa **Elegir juego**, selecciona su carpeta y pulsa **Preparar para iPhone**.
|
||||
|
||||
**Metroid Dread 2.1.0 es el juego confirmado funcionando en nuestro iPhone.** También comprobamos su recompilación sin firma y la generación automática de la IPA ligera. El editor nuevo todavía requiere comprobar su uso físico tras instalarlo.
|
||||
La app prepara las herramientas, compila y deja lista la **IPA ligera**. Verás el avance en la ventana. Al terminar, pulsa **Mostrar IPA** y **Abrir Sideloadly**.
|
||||
|
||||
Esta base incluye perfiles para los juegos de la tabla completa de abajo. Upstream publica pruebas en Mac o iPhone según el título: **tener un perfil o una IPA compilada no confirma que ese juego funcione en tu iPhone**. Abre la ficha del juego para comprobar plataforma, versión y problemas conocidos.
|
||||
**En nuestro Mac:** la app está disponible como `nxnative.app` en Descargas. Recuerda tu última carpeta y permite volver a mostrar la IPA ya preparada.
|
||||
|
||||
Algunos títulos incluidos para empezar a explorar:
|
||||
**En otro Mac:** después de descargar este repositorio privado, abre [`scripts/Abrir-interfaz.command`](scripts/Abrir-interfaz.command) con doble clic. Crea y abre la app local. La primera vez necesitas Xcode completo y Homebrew instalados; si hacen falta tus llaves, te pedirá elegir `prod.keys`. Mantén cada juego fuera del repositorio, en una carpeta con su base y actualizaciones.
|
||||
|
||||
| Juego | Versión del perfil | Referencia |
|
||||
|---|---|---|
|
||||
| **Metroid Dread** | **2.1.0** | [Ficha](dm/games/010093801237C000/README.md) · confirmado en nuestro iPhone |
|
||||
| Super Mario Bros. Wonder | 1.2.1 / 1.0.0 | [Ficha y límites](dm/games/010015100B514000/README.md) |
|
||||
| Super Mario Odyssey | 1.4.1 | [Ficha y límites](dm/games/0100000000010000/README.md) |
|
||||
| Mario Kart 8 Deluxe | 4.0.0 | [Ficha y límites](dm/games/0100152000022000/README.md) |
|
||||
| The Legend of Zelda: Breath of the Wild | 1.9.0 | [Ficha y límites](dm/games/01007EF00011E000/README.md) |
|
||||
| The Legend of Zelda: Tears of the Kingdom | 1.4.3 | [Ficha y límites](dm/games/0100F2C0115B6000/README.md) |
|
||||
| The Legend of Zelda: Link's Awakening | 1.1.0 | [Ficha y límites](dm/games/01006BB00C6F0000/README.md) |
|
||||
| Hades | 1.0.38233 | [Ficha y límites](dm/games/0100535012974000/README.md) |
|
||||
| Cuphead | 1.3.7 | [Ficha y límites](dm/games/0100A5C00D162000/README.md) |
|
||||
| Celeste | 1.4.1.1 | [Ficha y límites](dm/games/01002B30028F6000/README.md) |
|
||||
| Balatro | 1.1.3 | [Ficha y límites](dm/games/0100CD801CE5E000/README.md) |
|
||||
## Instalar: Sideloadly + Finder
|
||||
|
||||
1. Conecta tu iPhone al Mac y abre [Sideloadly](https://sideloadly.io).
|
||||
2. Arrastra la **IPA ligera** y pulsa **Start** con tu cuenta Apple **personal**.
|
||||
3. La primera vez, pulsa **Mostrar datos romfs** en nuestra app y copia la carpeta `romfs` completa por **Finder → iPhone → Archivos → tu juego**.
|
||||
|
||||
Para actualizar, usa la misma cuenta y el mismo bundle ID efectivo de Sideloadly. **Instala encima; no desinstales la app**, para conservar tus partidas. Si los datos del juego no cambiaron, puedes reutilizar el `romfs` existente. La firma e instalación siguen siendo pasos de Sideloadly; nuestra app prepara el archivo sin firmar.
|
||||
|
||||
## Juegos incluidos
|
||||
|
||||
**Metroid Dread 2.1.0 está confirmado en nuestro iPhone.** Los demás tienen perfiles y pruebas reportadas por upstream en Mac o iPhone; revisa [la ficha de tu juego](dm/games/README.md#games) antes de prepararlo. Un perfil no garantiza que cualquier versión funcione en cualquier iPhone.
|
||||
|
||||
Esta base requiere iOS/iPadOS 26 o posterior, iPhone con A17 Pro o posterior o iPad con M1 o posterior. Los datos de los juegos y las llaves los aportas tú.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/mario-kart-8-deluxe.jpg" width="256" alt="Mario Kart 8 Deluxe"><br>Mario Kart 8 Deluxe<br><sub>~11.9 GB · no Mii heads</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/mario-vs-donkey-kong.jpg" width="256" alt="Mario vs. Donkey Kong"><br>Mario vs. Donkey Kong<br><sub>~1.6 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/paper-mario-the-thousand-year-door.jpg" width="256" alt="Paper Mario: The Thousand-Year Door"><br>Paper Mario: The Thousand-Year Door<br><sub>~5 GB · 60 fps with 1.0.1</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/super-mario-3d-world.jpg" width="256" alt="Super Mario 3D World + Bowser's Fury"><br>Super Mario 3D World + Bowser's Fury<br><sub>~3 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/super-mario-bros-wonder.jpg" width="256" alt="Super Mario Bros. Wonder"><br>Super Mario Bros. Wonder<br><sub>~4 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/super-mario-galaxy.jpg" width="256" alt="Super Mario Galaxy"><br>Super Mario Galaxy<br><sub>~3 GB</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/super-mario-galaxy-2.jpg" width="256" alt="Super Mario Galaxy 2"><br>Super Mario Galaxy 2<br><sub>~3 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/super-mario-odyssey.jpg" width="256" alt="Super Mario Odyssey"><br>Super Mario Odyssey<br><sub>~6 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/zelda-breath-of-the-wild.jpg" width="256" alt="The Legend of Zelda: Breath of the Wild"><br>The Legend of Zelda: Breath of the Wild<br><sub>~16 GB · 60 fps with 1.9.0</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/zelda-links-awakening.jpg" width="256" alt="The Legend of Zelda: Link's Awakening"><br>The Legend of Zelda: Link's Awakening<br><sub>~6 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/zelda-tears-of-the-kingdom.jpg" width="256" alt="The Legend of Zelda: Tears of the Kingdom"><br>The Legend of Zelda: Tears of the Kingdom<br><sub>~17 GB · 60 fps with 1.4.3</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/13-sentinels.jpg" width="256" alt="13 Sentinels: Aegis Rim"><br>13 Sentinels: Aegis Rim<br><sub>~5 GB</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/absolum.jpg" width="256" alt="Absolum"><br>Absolum<br><sub>~2 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/ace-attorney-investigations.jpg" width="256" alt="Ace Attorney Investigations Collection"><br>Ace Attorney Investigations Collection<br><sub>~7 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/advance-wars-1-2-re-boot-camp.jpg" width="256" alt="Advance Wars 1+2: Re-Boot Camp"><br>Advance Wars 1+2: Re-Boot Camp<br><sub>~7.1 GB</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/ai-the-somnium-files.jpg" width="256" alt="AI: THE SOMNIUM FILES"><br>AI: THE SOMNIUM FILES<br><sub>~8.3 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/ai-the-somnium-files-nirvana-initiative.jpg" width="256" alt="AI: THE SOMNIUM FILES - nirvanA Initiative"><br>AI: THE SOMNIUM FILES - nirvanA Initiative<br><sub>~11.6 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/animal-crossing-new-horizons.jpg" width="256" alt="Animal Crossing: New Horizons"><br>Animal Crossing: New Horizons<br><sub>~10.5 GB</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/animal-well.png" width="256" alt="ANIMAL WELL"><br>ANIMAL WELL<br><sub>~54 MB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/astral-chain.png" width="256" alt="ASTRAL CHAIN"><br><a href="dm/games/01007300020FA000/README.md">ASTRAL CHAIN</a><br><sub>~9.7 GB · 60 fps target with 1.0.1</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/balatro.jpg" width="256" alt="Balatro"><br>Balatro<br><sub>~82 MB · played by touch</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/ball-x-pit.jpg" width="256" alt="BALL x PIT"><br>BALL x PIT<br><sub>~1.1 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/bayonetta.jpg" width="256" alt="Bayonetta"><br>Bayonetta<br><sub>~8.8 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/bayonetta-2.jpg" width="256" alt="Bayonetta 2"><br>Bayonetta 2<br><sub>~12.5 GB</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/bayonetta-3.jpg" width="256" alt="Bayonetta 3"><br>Bayonetta 3<br><sub>~14.2 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/big-brain-academy-brain-vs-brain.jpg" width="256" alt="Big Brain Academy: Brain vs. Brain"><br>Big Brain Academy: Brain vs. Brain<br><sub>~0.8 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/brain-memory-training-academy.jpg" width="256" alt="Brain Memory Training Academy"><br>Brain Memory Training Academy<br><sub>~0.1 GB</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/captain-toad-treasure-tracker.jpg" width="256" alt="Captain Toad: Treasure Tracker"><br>Captain Toad: Treasure Tracker<br><sub>~2 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/catherine-full-body.jpg" width="256" alt="Catherine: Full Body"><br>Catherine: Full Body<br><sub>~14 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/celeste.jpg" width="256" alt="Celeste"><br>Celeste<br><sub>~0.9 GB</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/cuphead.jpg" width="256" alt="Cuphead"><br>Cuphead<br><sub>~5 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/dark-souls-remastered-creation.jpg" width="256" alt="DARK SOULS: REMASTERED"><br>DARK SOULS: REMASTERED<br><sub>~4 GB · 60 fps with 1.0.3</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/digimon-story-cyber-sleuth.jpg" width="256" alt="Digimon Story Cyber Sleuth: Complete Edition"><br>Digimon Story Cyber Sleuth: Complete Edition<br><sub>~5 GB</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/dragon-quest-vii-reimagined.jpg" width="256" alt="DRAGON QUEST VII Reimagined"><br>DRAGON QUEST VII Reimagined<br><sub>~5.9 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/dragon-quest-xi-s.jpg" width="256" alt="DRAGON QUEST XI S: Echoes of an Elusive Age – Definitive Edition"><br>DRAGON QUEST XI S: Echoes of an Elusive Age – Definitive Edition<br><sub>~15 GB · 60 fps with 1.0.4</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/final-fantasy-resonance.jpg" width="256" alt="FINAL FANTASY RESONANCE"><br>FINAL FANTASY RESONANCE<br><sub>~10 GB · 60 fps with 1.0.1</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/final-fantasy-tactics.jpg" width="256" alt="FINAL FANTASY TACTICS - The Ivalice Chronicles"><br>FINAL FANTASY TACTICS - The Ivalice Chronicles<br><sub>~11 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/fire-emblem-engage.jpg" width="256" alt="Fire Emblem Engage"><br>Fire Emblem Engage<br><sub>~15 GB · 60 fps with 2.0.0</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/fire-emblem-three-houses.jpg" width="256" alt="Fire Emblem: Three Houses"><br>Fire Emblem: Three Houses<br><sub>~13 GB · 60 fps with 1.2.0</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/ghost-trick-phantom-detective.jpg" width="256" alt="Ghost Trick: Phantom Detective"><br>Ghost Trick: Phantom Detective<br><sub>~3 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/the-great-ace-attorney-chronicles.jpg" width="256" alt="The Great Ace Attorney Chronicles"><br>The Great Ace Attorney Chronicles<br><sub>~4 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/hades.jpg" width="256" alt="Hades"><br>Hades<br><sub>~4 GB</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/hades-ii.jpg" width="256" alt="Hades II"><br>Hades II<br><sub>~4 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/hollow-knight-silksong.jpg" width="256" alt="Hollow Knight: Silksong"><br>Hollow Knight: Silksong<br><sub>~6.4 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/hyrule-warriors-age-of-calamity.jpg" width="256" alt="Hyrule Warriors: Age of Calamity"><br>Hyrule Warriors: Age of Calamity<br><sub>~15 GB · 60 fps with 1.3.0</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/luigis-mansion-3.jpg" width="256" alt="Luigi's Mansion 3"><br>Luigi's Mansion 3<br><sub>~7.5 GB · 60 fps with 1.4.0</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/metroid-dread.jpg" width="256" alt="Metroid Dread"><br>Metroid Dread<br><sub>~5 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/monster-hunter-rise.jpg" width="256" alt="Monster Hunter Rise"><br>Monster Hunter Rise<br><sub>~22 GB · 60 fps with 16.0.2</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/monster-train-2.jpg" width="256" alt="Monster Train 2"><br>Monster Train 2<br><sub>~1.6 GB · 60 fps with 1.1.1</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/octopath-traveler-0.jpg" width="256" alt="Octopath Traveler 0"><br>Octopath Traveler 0<br><sub>~4 GB · 60 fps with 1.0.5</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/octopath-traveler-ii.jpg" width="256" alt="Octopath Traveler II"><br>Octopath Traveler II<br><sub>~5.9 GB · 60 fps with 1.1.1</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/patricks-parabox.jpg" width="256" alt="Patrick's Parabox"><br>Patrick's Parabox<br><sub>~0.2 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/persona-5-royal.jpg" width="256" alt="Persona 5 Royal"><br>Persona 5 Royal<br><sub>~14.9 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/pikmin-4.jpg" width="256" alt="Pikmin 4"><br>Pikmin 4<br><sub>~10.8 GB</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/pokemon-brilliant-diamond.jpg" width="256" alt="Pokémon Brilliant Diamond"><br>Pokémon Brilliant Diamond<br><sub>~4.6 GB · 60 fps with 1.3.0</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/pokemon-legends-z-a.jpg" width="256" alt="Pokémon Legends: Z-A"><br>Pokémon Legends: Z-A<br><sub>~6 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/pokemon-sword.jpg" width="256" alt="Pokémon Sword"><br>Pokémon Sword<br><sub>~12.4 GB</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/pokemon-shield.jpg" width="256" alt="Pokémon Shield"><br>Pokémon Shield<br><sub>~12.4 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/portal.jpg" width="256" alt="Portal"><br>Portal<br><sub>~2.6 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/raging-loop.jpg" width="256" alt="Raging Loop"><br>Raging Loop<br><sub>~0.9 GB</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/rhythm-heaven-groove.jpg" width="256" alt="Rhythm Heaven Groove"><br>Rhythm Heaven Groove<br><sub>~3 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/romance-of-the-three-kingdoms-8-remake.jpg" width="256" alt="ROMANCE OF THE THREE KINGDOMS 8 REMAKE"><br>ROMANCE OF THE THREE KINGDOMS 8 REMAKE<br><sub>~16.8 GB · controller only</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/stardew-valley.jpg" width="256" alt="Stardew Valley"><br>Stardew Valley<br><sub>~0.6 GB</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/steins-gate-reboot.jpg" width="256" alt="STEINS;GATE RE:BOOT"><br>STEINS;GATE RE:BOOT<br><sub>~6 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/super-smash-bros-ultimate.jpg" width="256" alt="Super Smash Bros. Ultimate"><br>Super Smash Bros. Ultimate<br><sub>~17.5 GB · offline</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/taiko-risshiden-v-dx.jpg" width="256" alt="Taiko Risshiden V DX"><br>Taiko Risshiden V DX<br><sub>~3.3 GB · Japanese · played by touch</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/unicorn-overlord.jpg" width="256" alt="Unicorn Overlord"><br>Unicorn Overlord<br><sub>~4 GB</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/nier-automata.png" width="256" alt="NieR:Automata The End of YoRHa Edition"><br>NieR:Automata The End of YoRHa Edition<br><sub>~10.9 GB · 60 fps with 1.0.3</sub></td>
|
||||
<td align="center" valign="top" width="33%"><img src="docs/images/zelda-echoes-of-wisdom.jpg" width="256" alt="The Legend of Zelda: Echoes of Wisdom"><br>The Legend of Zelda: Echoes of Wisdom<br><sub>~7.8 GB</sub></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Controles a tu gusto
|
||||
|
||||
En el juego pulsa **Edit controls**. Mueve los botones, ajusta tamaño y opacidad y pulsa **Save**. **Cancel** descarta y **Reset control / Reset all** restaura. Cada juego guarda su acomodo 2D y 3D. El juego sigue corriendo mientras editas.
|
||||
|
||||
<details>
|
||||
<summary>Ver los 70 perfiles incluidos y sus versiones</summary>
|
||||
<summary>Guías, comandos y detalles técnicos</summary>
|
||||
|
||||
| Juego y ficha | Versión del perfil |
|
||||
|---|---|
|
||||
| [13 Sentinels: Aegis Rim](dm/games/01003FC01670C000/README.md) | 1.10 |
|
||||
| [13 Sentinels: Aegis Rim, Asian release](dm/games/01008D7016438000/README.md) | 1.00 (base) |
|
||||
| [Absolum](dm/games/0100A83022A22000/README.md) | 1.1.1 |
|
||||
| [Ace Attorney Investigations Collection](dm/games/010005501E68C000/README.md) | 1.0.0.2 |
|
||||
| [Advance Wars 1+2: Re-Boot Camp](dm/games/0100300012F2A000/README.md) | 1.0.0 (base) |
|
||||
| [AI: THE SOMNIUM FILES](dm/games/010089B00D09C000/README.md) | 1.0.1 |
|
||||
| [ASTRAL CHAIN](dm/games/01007300020FA000/README.md) | 1.0.1 |
|
||||
| [AI: THE SOMNIUM FILES - nirvanA Initiative](dm/games/0100713016CCC000/README.md) | 1.0.2 |
|
||||
| [ANIMAL WELL](dm/games/010020D01AD24000/README.md) | 1.0.10 |
|
||||
| [Animal Crossing: New Horizons](dm/games/01006F8002326000/README.md) | 3.0.3 |
|
||||
| [BALL x PIT](dm/games/010086A022444000/README.md) | 1.293 |
|
||||
| [Balatro](dm/games/0100CD801CE5E000/README.md) | 1.1.3 |
|
||||
| [Bayonetta](dm/games/010076F0049A2000/README.md) | 1.1 |
|
||||
| [Bayonetta 2](dm/games/01007960049A0000/README.md) | 1.2 |
|
||||
| [Bayonetta 3](dm/games/01004A4010FEA000/README.md) | 1.2.0 |
|
||||
| [Big Brain Academy: Brain vs. Brain](dm/games/0100620012D6E000/README.md) | 1.2.0 |
|
||||
| [Brain Memory Training Academy](dm/games/0100F5D01DA3A000/README.md) | 1.0.0 (base) |
|
||||
| [Captain Toad: Treasure Tracker](dm/games/01009BF0072D4000/README.md) | 1.4.2 |
|
||||
| [Catherine: Full Body](dm/games/0100BF00112C0000/README.md) | 1.0.1a |
|
||||
| [Celeste](dm/games/01002B30028F6000/README.md) | 1.4.1.1 |
|
||||
| [Cuphead](dm/games/0100A5C00D162000/README.md) | 1.3.7 |
|
||||
| [DARK SOULS: REMASTERED](dm/games/01004AB00A260000/README.md) | 1.0.3 |
|
||||
| [Digimon Story Cyber Sleuth: Complete Edition](dm/games/010014E00DB56000/README.md) | 1.0.3 |
|
||||
| [DRAGON QUEST VII Reimagined](dm/games/0100A9D01C446000/README.md) | 1.2.0 |
|
||||
| [DRAGON QUEST XI S: Echoes of an Elusive Age – Definitive Edition](dm/games/01006C300E9F0000/README.md) | 1.0.4 |
|
||||
| [FINAL FANTASY RESONANCE](dm/games/0100606026F12000/README.md) | 1.0.1 |
|
||||
| [FINAL FANTASY TACTICS - The Ivalice Chronicles](dm/games/010038B015560000/README.md) | 1.4.0 |
|
||||
| [Fire Emblem Engage](dm/games/0100A6301214E000/README.md) | 2.0.0 |
|
||||
| [Fire Emblem: Three Houses](dm/games/010055D009F78000/README.md) | 1.2.0 |
|
||||
| [Ghost Trick: Phantom Detective](dm/games/010029B018432000/README.md) | 1.0.0 (base) |
|
||||
| [Hades](dm/games/0100535012974000/README.md) | 1.0.38233 |
|
||||
| [Hades II](dm/games/0100A00019DE0000/README.md) | base game |
|
||||
| [Hollow Knight: Silksong](dm/games/010013C00E930000/README.md) | 1.0.30000 |
|
||||
| [Hyrule Warriors: Age of Calamity](dm/games/01002B00111A2000/README.md) | 1.3.0 |
|
||||
| [Luigi's Mansion 3](dm/games/0100DCA0064A6000/README.md) | 1.4.0 |
|
||||
| [Mario Kart 8 Deluxe](dm/games/0100152000022000/README.md) | 4.0.0 |
|
||||
| [Mario vs. Donkey Kong](dm/games/0100B99019412000/README.md) | 1.0.1 (cartridge base) |
|
||||
| [Metroid Dread](dm/games/010093801237C000/README.md) | 2.1.0 |
|
||||
| [Monster Hunter Rise](dm/games/0100B04011742000/README.md) | 16.0.2 |
|
||||
| [Monster Train 2](dm/games/010051701FB46000/README.md) | 1.1.1 |
|
||||
| [NieR:Automata The End of YoRHa Edition](dm/games/010056B015FE8000/README.md) | 1.0.3 |
|
||||
| [Octopath Traveler 0](dm/games/01005270232F2000/README.md) | 1.0.5 |
|
||||
| [OCTOPATH TRAVELER II](dm/games/0100A3501946E000/README.md) | 1.1.1 |
|
||||
| [Paper Mario: The Thousand-Year Door](dm/games/0100ECD018EBE000/README.md) | 1.0.1 |
|
||||
| [Patrick's Parabox](dm/games/0100E44016C22000/README.md) | 1.0.1 |
|
||||
| [Persona 5 Royal](dm/games/01005CA01580E000/README.md) | 1.0.2 |
|
||||
| [Pikmin 4](dm/games/0100B7C00933A000/README.md) | 1.1.1 |
|
||||
| [Pokémon Brilliant Diamond](dm/games/0100000011D90000/README.md) | 1.3.0 |
|
||||
| [Pokémon Legends: Z-A](dm/games/0100F43008C44000/README.md) | 2.0.2 |
|
||||
| [Pokémon Shield](dm/games/01008DB008C2C000/README.md) | 1.3.2 |
|
||||
| [Pokémon Sword](dm/games/0100ABF008968000/README.md) | 1.3.2 |
|
||||
| [Portal](dm/games/01007BB017812000/README.md) | 1.0.3 |
|
||||
| [Raging Loop](dm/games/010020400E1C2000/README.md) | 1.0.0 (base) |
|
||||
| [Rhythm Heaven Groove](dm/games/0100D9F01D474000/README.md) | 1.0.0 (base) |
|
||||
| [ROMANCE OF THE THREE KINGDOMS 8 REMAKE](dm/games/0100C2A01A730000/README.md) | 1.1.3 |
|
||||
| [STEINS;GATE RE:BOOT](dm/games/01008BC024FDE000/README.md) | 1.0.3 |
|
||||
| [Stardew Valley](dm/games/0100E65002BB8000/README.md) | 1.6.15.13 |
|
||||
| [Super Mario 3D World + Bowser's Fury](dm/games/010028600EBDA000/README.md) | 1.2.2 |
|
||||
| [Super Mario Bros. Wonder](dm/games/010015100B514000/README.md) | 1.2.1 (and 1.0.0) |
|
||||
| [Super Mario Galaxy](dm/games/010099C022B96000/README.md) | 1.3.1 |
|
||||
| [Super Mario Galaxy 2](dm/games/0100FD8022DAA000/README.md) | 1.4.0 |
|
||||
| [Super Mario Odyssey](dm/games/0100000000010000/README.md) | 1.4.1 |
|
||||
| [Super Smash Bros. Ultimate](dm/games/01006A800016E000/README.md) | 13.0.5 |
|
||||
| [Taiko Risshiden V DX](dm/games/0100346017304000/README.md) | 1.1.1 |
|
||||
| [The Great Ace Attorney Chronicles](dm/games/010036E00FB20000/README.md) | 1.0.0 (base) |
|
||||
| [The Legend of Zelda: Breath of the Wild](dm/games/01007EF00011E000/README.md) | 1.9.0 |
|
||||
| [The Legend of Zelda: Echoes of Wisdom](dm/games/01008CF01BAAC000/README.md) | 1.1.0 |
|
||||
| [The Legend of Zelda: Link's Awakening](dm/games/01006BB00C6F0000/README.md) | 1.1.0 |
|
||||
| [The Legend of Zelda: Tears of the Kingdom](dm/games/0100F2C0115B6000/README.md) | 1.4.3 |
|
||||
| [Unicorn Overlord](dm/games/010069401ADB8000/README.md) | 1.05 |
|
||||
- [Guía de comandos e instalación detallada](README-comandos.md).
|
||||
- [Automatización y solución de problemas](docs/personal-ios.md).
|
||||
- [Revisión de los PR de upstream](docs/personal-pr-review.md).
|
||||
- [README anterior y créditos completos](README-original.md).
|
||||
|
||||
</details>
|
||||
|
||||
La región y versión del juego importan. Un título sin perfil puede recibir una compilación provisional y aun así no arrancar. Los PR de juegos nuevos que sólo revisamos no están incluidos automáticamente; consulta [la revisión de PR](docs/personal-pr-review.md).
|
||||
|
||||
## Preparar el Mac
|
||||
|
||||
Necesitas:
|
||||
|
||||
- Un Mac con **Xcode completo** instalado y abierto al menos una vez, y SDK de iOS 26 o posterior.
|
||||
- [Homebrew](https://brew.sh) instalado. El script instala las demás herramientas que falten.
|
||||
- Un iPhone con **A17 Pro o posterior**, o iPad con **M1 o posterior**, y **iOS/iPadOS 26 o posterior**, según los requisitos de esta base. El soporte para otros chips no se incorporó desde los PR revisados.
|
||||
- Tus archivos del juego y `prod.keys`, en una carpeta fuera del repositorio.
|
||||
- Espacio para los archivos originales, la extracción, cachés, la IPA completa y otra copia de `romfs`.
|
||||
- [Sideloadly](https://sideloadly.io) para firmar e instalar con tu cuenta Apple personal.
|
||||
|
||||
En un Mac nuevo, descarga nuestra copia privada. Debes tener acceso con tu cuenta GitHub:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/nalancer08/nxnative-personal.git
|
||||
cd nxnative-personal
|
||||
```
|
||||
|
||||
**Si ya tienes nuestra copia en este Mac, abre Terminal en esa carpeta y omite el clonado.** No clones de nuevo el repositorio original para compilar: esa copia no tendría nuestro editor ni estos scripts.
|
||||
|
||||
## Compilar fácilmente
|
||||
|
||||
### Opción 1: un comando y selector de carpeta
|
||||
|
||||
Desde la carpeta del repositorio:
|
||||
|
||||
```sh
|
||||
bash scripts/prepare_ios.sh
|
||||
```
|
||||
|
||||
Selecciona la carpeta que contiene un solo juego con sus actualizaciones. El script pide `prod.keys` si no está en esa carpeta, prepara las herramientas, compila y separa los datos automáticamente.
|
||||
|
||||
### Opción 2: indicar la carpeta
|
||||
|
||||
Cambia la ruta del ejemplo por la tuya y conserva las comillas:
|
||||
|
||||
```sh
|
||||
bash scripts/prepare_ios.sh "/Volumes/Juegos/MetroidDread"
|
||||
```
|
||||
|
||||
La carpeta puede tener esta estructura; no cambies las extensiones de los archivos:
|
||||
|
||||
```text
|
||||
MetroidDread/
|
||||
juego-base.nsp
|
||||
actualizacion.nsp
|
||||
prod.keys
|
||||
```
|
||||
|
||||
También se admiten XCI y, con soporte adicional instalado por el script, NSZ/XCZ. Usa una carpeta distinta para cada juego. Por defecto se selecciona la actualización más reciente presente; puedes fijar una con `--package`.
|
||||
|
||||
### Opción 3: doble clic
|
||||
|
||||
Abre **`scripts/Build-iPhone.command`** desde Finder. Ejecuta el mismo flujo y te pide la carpeta. En nuestro Mac también está el acceso `Preparar-juego-iPhone.command` en Descargas.
|
||||
|
||||
### Qué obtienes
|
||||
|
||||
Al terminar, la consola muestra las rutas exactas. Junto al juego encontrarás:
|
||||
|
||||
```text
|
||||
nxnative-ios-FECHA-unsigned.ipa ← copia completa, se conserva
|
||||
nxnative-ios-FECHA-unsigned-light-XXXX/
|
||||
nxnative-ios-FECHA-unsigned-light.ipa ← instala ESTA con Sideloadly
|
||||
romfs/ ← datos del juego para Finder
|
||||
INSTALL.txt
|
||||
nxnative-ios.json ← identidad y opciones de futuras compilaciones
|
||||
```
|
||||
|
||||
Los nombres incluyen fecha y una parte única. La IPA ligera de Dread mide aproximadamente **57 MB**; sus datos van aparte. Otros juegos pueden tener tamaños distintos.
|
||||
|
||||
El proceso conserva las salidas anteriores y reutiliza las cachés válidas. **La IPA se genera sin firmar**: la cuenta Apple se usa después, dentro de Sideloadly. No se suben juegos, llaves ni partidas a GitHub.
|
||||
|
||||
## Instalar con Sideloadly
|
||||
|
||||
1. Descarga y abre [Sideloadly](https://sideloadly.io) en el Mac.
|
||||
2. Conecta el iPhone por USB, desbloquéalo y acepta **Confiar** si lo solicita.
|
||||
3. Selecciona tu iPhone en Sideloadly y arrastra la **IPA que termina en `-light.ipa`**.
|
||||
4. Usa tu **cuenta Apple personal**, la misma que utilizaste si estás actualizando una instalación existente. En este proyecto no usamos cuentas ni equipos de empresa.
|
||||
5. Pulsa **Start** y completa la autenticación que solicite Sideloadly.
|
||||
6. Si el iPhone lo solicita, activa **Ajustes → Privacidad y seguridad → Modo de desarrollador**. Para un desarrollador no confiable, revisa **Ajustes → General → VPN y gestión de dispositivos** y autoriza la cuenta con la que firmaste.
|
||||
|
||||
Consulta las instrucciones vigentes y los errores en la [FAQ oficial de Sideloadly](https://sideloadly.io/faq). Con cuenta gratuita, la firma normalmente dura **7 días**; Sideloadly también ofrece renovación automática cuando su configuración y conexión lo permiten.
|
||||
|
||||
### Copiar los datos con Finder: primera instalación
|
||||
|
||||
1. En Finder, selecciona el **iPhone → Archivos → la app del juego**.
|
||||
2. Arrastra la carpeta **`romfs` completa** del resultado de compilación a los documentos de esa app y espera a que termine.
|
||||
3. Abre el juego. Si ya estaba abierto, ciérralo por completo y ábrelo otra vez.
|
||||
|
||||
En Dread, la estructura dentro de la app debe ser `Documents/romfs/system/shd/...`. Evita `romfs/romfs`. En otros juegos el contenido cambia, pero el directorio que copias sigue siendo `romfs`.
|
||||
|
||||
Si la app no aparece en Archivos de Finder, ábrela una vez y ciérrala; aún sin los datos puede fallar al arrancar. Copia `romfs` y vuelve a abrirla. [Guía de Apple para compartir archivos](https://support.apple.com/en-us/119585).
|
||||
|
||||
## Actualizar sin perder partidas
|
||||
|
||||
**Instala encima de la app existente. No la desinstales.** Desinstalar elimina sus documentos, partidas y datos copiados.
|
||||
|
||||
- Usa la **misma cuenta Apple personal** y el **mismo bundle ID efectivo** de la instalación anterior. Si configuraste un identificador personalizado en Sideloadly, consérvalo.
|
||||
- Mantén `nxnative-ios.json`: nuestros scripts guardan ahí la identidad para siguientes compilaciones. Eso no sustituye los ajustes que Sideloadly haya aplicado al firmar.
|
||||
- Haz una copia de `save` desde los documentos de la app antes de cambiar de versión, cuando esté disponible y con el juego cerrado.
|
||||
- Una actualización del editor puede reutilizar el `romfs` del teléfono si los datos del juego siguen siendo idénticos. Una actualización del juego puede necesitar el nuevo `romfs`.
|
||||
- Para renovar una firma, vuelve a instalar la misma IPA con la misma identidad o usa la renovación de Sideloadly. [Referencia oficial sobre actualizar y conservar progreso](https://sideloadly.io/faq).
|
||||
|
||||
### Recompilar sólo la app con los datos ya preparados
|
||||
|
||||
Para cambios del editor u otros cambios de la app, cuando ya existen extracción, módulos reescritos y shaders válidos:
|
||||
|
||||
```sh
|
||||
bash scripts/prepare_ios.sh "/Volumes/Juegos/MetroidDread" --app-only
|
||||
```
|
||||
|
||||
Este modo no necesita llaves ni vuelve a extraer el juego. Recompila y vuelve a generar las IPA y una copia de `romfs`. Verifica las cachés; si no son válidas, se detiene. Para una versión de juego o cadena de shaders distinta, usa el comando normal.
|
||||
|
||||
## Mover y ajustar los controles
|
||||
|
||||
Dentro del juego toca **Edit controls**:
|
||||
|
||||
1. Selecciona A, B, X, Y u otro control en el menú, o toca cerca de su centro.
|
||||
2. Arrastra fuera del panel para mover el seleccionado. **Show / hide tools** muestra u oculta el panel para acceder al área detrás.
|
||||
3. Ajusta **Size** entre 50–200% y **Opacity** entre 0–100%.
|
||||
4. **Save** guarda; **Cancel** descarta. **Reset control** restaura el seleccionado y **Reset all** restaura el layout; confirma con **Save**.
|
||||
|
||||
Cada juego guarda sus propios acomodos de **2D y 3D**. Un control al 0% sigue recibiendo toques; el editor lo muestra parcialmente para encontrarlo. **El juego sigue corriendo mientras editas.** Conectar o desconectar un mando cancela los cambios de edición no guardados.
|
||||
|
||||
## Si algo falla
|
||||
|
||||
| Problema | Qué revisar |
|
||||
|---|---|
|
||||
| No hay NSP/XCI en la carpeta | Elige la carpeta que contiene los archivos directamente, no su carpeta padre. |
|
||||
| Faltan herramientas | Instala Xcode y Homebrew; ejecuta otra vez el preparador y revisa el primer error. |
|
||||
| Caché de shaders inválida en `--app-only` | Ejecuta el flujo normal para regenerar lo que haya cambiado. |
|
||||
| Sideloadly no detecta el iPhone | Desbloquéalo, revisa el cable USB y acepta Confiar en el Mac/iPhone. |
|
||||
| El juego no abre después de instalar | Comprueba que `romfs` se copió completo, la versión y los requisitos de la ficha del juego. |
|
||||
| El juego dejó de abrir días después | Revisa si venció la firma y renueva conservando la cuenta y el identificador. |
|
||||
| Error de compilación o pantalla incorrecta | Conserva el error y los logs de `<title ID>_build/logs/`; en el teléfono, busca `Documents/log.txt`. |
|
||||
|
||||
Los mensajes de compilación se muestran en inglés. El nuevo preparador usa esta copia del código y no exige borrar tus modificaciones ni volver al checkout original.
|
||||
|
||||
## Qué cambiamos en esta copia
|
||||
|
||||
- Editor compartido de posición, tamaño y opacidad por control, con guardado por juego/layout.
|
||||
- Preparación de dependencias y compilación desde nuestra propia base.
|
||||
- Exportación automática de IPA ligera y `romfs`, validación de integridad y conservación de archivos anteriores.
|
||||
- Registro de identidad por juego y bloqueo de compilaciones simultáneas desde el wrapper.
|
||||
- Corrección del cálculo de espacio de extracción del PR #277.
|
||||
- [Revisión de 57 PR abiertos y 20 cambios posteriores](docs/personal-pr-review.md). Sólo #277 se integró desde esa revisión; los demás conservan decisiones y validaciones pendientes.
|
||||
|
||||
Guías adicionales: [automatización detallada](docs/personal-ios.md) · [documentación de desarrollo](docs/development.md) · [README anterior con la galería y créditos completos](README-original.md).
|
||||
|
||||
Este proyecto deriva de **seedds/nxnative** y conserva la [licencia MIT](LICENSE). Los créditos y licencias de sus componentes siguen en el [README anterior](README-original.md#acknowledgements) y en [third_party](dm/third_party/README.md). El repositorio no contiene los juegos ni sus llaves.
|
||||
Basado en [seedds/nxnative](https://github.com/seedds/nxnative), con nuestro editor de controles y automatización. Conservamos la [licencia MIT](LICENSE) y los [créditos originales](README-original.md#acknowledgements). Nuestros cambios pertenecen a la cuenta de GitHub **nalancer08**; los commits históricos de upstream conservan su autoría.
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Give a GUI build its own process group so cancellation includes child tools."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == '__main__':
|
||||
os.setsid()
|
||||
os.execv('/bin/bash', ['bash', *sys.argv[1:]])
|
||||
@@ -0,0 +1,240 @@
|
||||
import SwiftUI
|
||||
import AppKit
|
||||
import Foundation
|
||||
import Darwin
|
||||
|
||||
struct GameCard: Identifiable {
|
||||
let id: String
|
||||
let name: String
|
||||
let image: String
|
||||
}
|
||||
|
||||
@MainActor final class BuildModel: ObservableObject {
|
||||
@Published var folder: URL?
|
||||
@Published var running = false
|
||||
@Published var phase = "Tu próximo juego empieza aquí."
|
||||
@Published var detail = "Elige su carpeta. Nosotros preparamos la IPA ligera."
|
||||
@Published var transcript = ""
|
||||
@Published var output: URL?
|
||||
@Published var assets: URL?
|
||||
@Published var failed = false
|
||||
@Published var appOnly = false
|
||||
@Published var showDetails = false
|
||||
private var process: Process?
|
||||
private var cancelled = false
|
||||
let repo: URL
|
||||
let cards: [GameCard] = [
|
||||
.init(id:"dread",name:"Metroid Dread",image:"metroid-dread.jpg"),
|
||||
.init(id:"wonder",name:"Super Mario Bros. Wonder",image:"super-mario-bros-wonder.jpg"),
|
||||
.init(id:"zelda",name:"Zelda: Breath of the Wild",image:"zelda-breath-of-the-wild.jpg"),
|
||||
.init(id:"odyssey",name:"Super Mario Odyssey",image:"super-mario-odyssey.jpg"),
|
||||
.init(id:"cuphead",name:"Cuphead",image:"cuphead.jpg"),
|
||||
.init(id:"hades",name:"Hades",image:"hades.jpg")
|
||||
]
|
||||
init() {
|
||||
let configured = Bundle.main.object(forInfoDictionaryKey:"NxNativeSource") as? String ?? ""
|
||||
repo = URL(fileURLWithPath:configured)
|
||||
if let path = UserDefaults.standard.string(forKey:"lastGameFolder"), FileManager.default.fileExists(atPath:path) {
|
||||
folder=URL(fileURLWithPath:path)
|
||||
restoreOutput()
|
||||
}
|
||||
}
|
||||
var gameName: String { folder?.lastPathComponent ?? "Ningún juego seleccionado" }
|
||||
func chooseFolder() {
|
||||
let panel=NSOpenPanel();panel.canChooseDirectories=true;panel.canChooseFiles=false
|
||||
panel.allowsMultipleSelection=false;panel.prompt="Elegir juego"
|
||||
panel.message="Selecciona la carpeta con el juego base y sus actualizaciones NSP/XCI."
|
||||
guard panel.runModal() == .OK, let url=panel.url else { return }
|
||||
do {
|
||||
let files=try FileManager.default.contentsOfDirectory(at:url,includingPropertiesForKeys:nil)
|
||||
guard files.contains(where:{["nsp","xci","nsz","xcz"].contains($0.pathExtension.lowercased())}) else {
|
||||
phase="No encontramos el juego en esa carpeta."
|
||||
detail="Elige la carpeta que contiene los archivos NSP o XCI directamente."
|
||||
failed=true;return
|
||||
}
|
||||
guard !url.standardizedFileURL.path.hasPrefix(repo.standardizedFileURL.path+"/"), url.standardizedFileURL != repo.standardizedFileURL else {
|
||||
phase="Guarda el juego fuera del repositorio.";detail="Así tus juegos y llaves se quedan fuera de GitHub.";failed=true;return
|
||||
}
|
||||
folder=url;UserDefaults.standard.set(url.path,forKey:"lastGameFolder")
|
||||
phase="Listo para preparar \(gameName).";detail="Un clic más. Los archivos anteriores se conservan."
|
||||
failed=false;output=nil;assets=nil;transcript="";restoreOutput()
|
||||
} catch { phase="No pudimos leer esa carpeta.";detail=error.localizedDescription;failed=true }
|
||||
}
|
||||
func restoreOutput() {
|
||||
guard let folder,
|
||||
let data=try? Data(contentsOf:folder.appendingPathComponent("nxnative-ios.json")),
|
||||
let record=(try? JSONSerialization.jsonObject(with:data)) as? [String:Any],
|
||||
let path=record["light_dir"] as? String else {return}
|
||||
let directory=URL(fileURLWithPath:path)
|
||||
if let files=try? FileManager.default.contentsOfDirectory(at:directory,includingPropertiesForKeys:nil),
|
||||
let ipa=files.first(where:{$0.pathExtension=="ipa"}) { output=ipa;assets=directory.appendingPathComponent("romfs") }
|
||||
}
|
||||
func build() {
|
||||
guard let folder, !running else {return}
|
||||
let script=repo.appendingPathComponent("scripts/prepare_ios.sh")
|
||||
guard FileManager.default.fileExists(atPath:script.path) else {
|
||||
phase="No encontramos el código de nxnative.";detail="Vuelve a crear la app desde el repositorio con Abrir-interfaz.command.";failed=true;return
|
||||
}
|
||||
var keys: URL?
|
||||
if !appOnly && !FileManager.default.fileExists(atPath:folder.appendingPathComponent("prod.keys").path) {
|
||||
let panel=NSOpenPanel();panel.prompt="Elegir prod.keys";panel.message="Sólo la primera vez: selecciona tus llaves. Se quedan en tu Mac."
|
||||
guard panel.runModal() == .OK else {return};keys=panel.url
|
||||
}
|
||||
let task=Process();let pipe=Pipe()
|
||||
task.executableURL=URL(fileURLWithPath:"/usr/bin/python3")
|
||||
var arguments=[repo.appendingPathComponent("desktop/NxNativeEasy/build_session.py").path,script.path,folder.path]
|
||||
if appOnly { arguments.append("--app-only") }
|
||||
if let keys {arguments += ["--keys",keys.path]}
|
||||
task.arguments=arguments;task.currentDirectoryURL=repo
|
||||
task.standardOutput=pipe;task.standardError=pipe
|
||||
var environment=ProcessInfo.processInfo.environment
|
||||
environment["PYTHONUNBUFFERED"]="1"
|
||||
task.environment=environment
|
||||
running=true;cancelled=false;failed=false;output=nil;assets=nil;transcript=""
|
||||
phase="Preparando herramientas…";detail="Puedes dejar esta ventana abierta. No necesitas usar Terminal."
|
||||
pipe.fileHandleForReading.readabilityHandler={ [weak self] handle in
|
||||
let data=handle.availableData
|
||||
guard !data.isEmpty else { handle.readabilityHandler=nil;return }
|
||||
let text=String(decoding:data,as:UTF8.self)
|
||||
Task { @MainActor in self?.append(text) }
|
||||
}
|
||||
task.terminationHandler={ [weak self] task in
|
||||
let status=task.terminationStatus
|
||||
Task { @MainActor in
|
||||
guard let self else{return}
|
||||
self.running=false;self.process=nil
|
||||
if self.cancelled {
|
||||
self.phase="Preparación cancelada.";self.detail="Tus archivos y partidas anteriores se conservaron."
|
||||
} else if status==0 {
|
||||
self.restoreOutput()
|
||||
self.failed=self.output==nil
|
||||
self.phase=self.output == nil ? "Terminó, pero no encontramos la IPA." : "Tu juego está listo para Sideloadly."
|
||||
self.detail=self.output == nil ? "Abre los detalles para revisar las rutas de salida." : "Abre la IPA y arrástrala a Sideloadly. La firma se hace con tu cuenta personal."
|
||||
} else {
|
||||
self.failed=true;self.phase="Necesitamos revisar un paso."
|
||||
self.detail="No borramos tus archivos. Abre los detalles para ver el error y vuelve a intentar."
|
||||
}
|
||||
}
|
||||
}
|
||||
do { process=task;try task.run() }
|
||||
catch { running=false;process=nil;failed=true;phase="No pudimos iniciar la preparación.";detail=error.localizedDescription }
|
||||
}
|
||||
func append(_ text:String) {
|
||||
transcript += text
|
||||
if transcript.count>120000 {transcript=String(transcript.suffix(100000))}
|
||||
guard running else {return}
|
||||
let lower=text.lowercased()
|
||||
if lower.contains("pack ipa") || lower.contains("source ipa:") || lower.contains("procesados") || lower.contains("processed ") {phase="Creando tu IPA ligera…"}
|
||||
else if lower.contains("xcodebuild") || lower.contains("building the ipa") {phase="Compilando la app para iPhone…"}
|
||||
else if lower.contains("shader") {phase="Preparando los gráficos del juego…"}
|
||||
else if lower.contains("extract") {phase="Preparando los archivos del juego…"}
|
||||
}
|
||||
func cancel() {
|
||||
guard let process,process.isRunning else{return}
|
||||
cancelled=true
|
||||
if kill(-process.processIdentifier,SIGTERM) != 0 {process.terminate()}
|
||||
phase="Cancelando…"
|
||||
}
|
||||
func revealIPA() {if let output {NSWorkspace.shared.activateFileViewerSelecting([output])}}
|
||||
func revealAssets() {if let assets {NSWorkspace.shared.activateFileViewerSelecting([assets])}}
|
||||
func openSideloadly() {
|
||||
if let app=NSWorkspace.shared.urlForApplication(withBundleIdentifier:"io.sideloadly.sideloadly") {
|
||||
NSWorkspace.shared.openApplication(at:app,configuration:.init())
|
||||
} else {NSWorkspace.shared.open(URL(string:"https://sideloadly.io")!)}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView: View {
|
||||
@StateObject private var model=BuildModel()
|
||||
private let ink=Color(red:0.93,green:0.94,blue:0.90)
|
||||
private let lime=Color(red:0.79,green:0.95,blue:0.37)
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color(red:0.065,green:0.075,blue:0.07).ignoresSafeArea()
|
||||
ScrollView {
|
||||
VStack(alignment:.leading,spacing:24) {
|
||||
HStack {
|
||||
Label("nxnative / personal",systemImage:"gamecontroller.fill").font(.system(size:15,weight:.semibold,design:.monospaced)).foregroundStyle(lime)
|
||||
Spacer()
|
||||
Text("HECHO PARA TU IPHONE").font(.system(size:10,weight:.medium,design:.monospaced)).tracking(2).foregroundStyle(.gray)
|
||||
}
|
||||
VStack(alignment:.leading,spacing:8) {
|
||||
Text("Del juego a tu iPhone.").font(.system(size:42,weight:.bold,design:.rounded)).foregroundStyle(ink)
|
||||
Text("Elige una carpeta. Prepara tu juego. A jugar.").font(.system(size:17)).foregroundStyle(.gray)
|
||||
}
|
||||
HStack(spacing:12) {
|
||||
ForEach(model.cards.prefix(4)) {card in
|
||||
VStack(alignment:.leading,spacing:8) {
|
||||
if let image=NSImage(contentsOf:model.repo.appendingPathComponent("docs/images/"+card.image)) {
|
||||
Image(nsImage:image).resizable().scaledToFill().frame(height:106).clipped().clipShape(RoundedRectangle(cornerRadius:12))
|
||||
}
|
||||
Text(card.name).font(.system(size:11,weight:.medium)).foregroundStyle(ink).lineLimit(1)
|
||||
}.frame(maxWidth:.infinity)
|
||||
}
|
||||
}.accessibilityLabel("Juegos con perfiles incluidos; consulta la ficha de cada juego para compatibilidad")
|
||||
VStack(alignment:.leading,spacing:18) {
|
||||
HStack(alignment:.center,spacing:14) {
|
||||
Image(systemName:"folder.fill").font(.system(size:28)).foregroundStyle(lime)
|
||||
VStack(alignment:.leading,spacing:4) {
|
||||
Text(model.gameName).font(.system(size:18,weight:.semibold)).foregroundStyle(ink)
|
||||
Text(model.folder == nil ? "Una carpeta por juego, con su base y actualizaciones." : model.folder!.path)
|
||||
.font(.system(size:11)).foregroundStyle(.gray).lineLimit(1).truncationMode(.middle)
|
||||
}
|
||||
Spacer()
|
||||
Button(model.folder == nil ? "1. Elegir juego" : "Cambiar") {model.chooseFolder()}.disabled(model.running)
|
||||
}
|
||||
Divider().overlay(Color.white.opacity(0.08))
|
||||
HStack {
|
||||
VStack(alignment:.leading,spacing:5) {
|
||||
Text(model.phase).font(.system(size:16,weight:.semibold)).foregroundStyle(model.failed ? .orange : ink)
|
||||
Text(model.detail).font(.system(size:12)).foregroundStyle(.gray).fixedSize(horizontal:false,vertical:true)
|
||||
}
|
||||
Spacer()
|
||||
if model.running {ProgressView().controlSize(.small);Button("Cancelar"){model.cancel()}}
|
||||
}
|
||||
if !model.running {
|
||||
Button {model.build()} label: {
|
||||
HStack {Image(systemName:"iphone.gen3");Text("2. Preparar para iPhone");Spacer();Image(systemName:"arrow.right")}
|
||||
.font(.system(size:16,weight:.bold)).padding(16).foregroundStyle(Color.black)
|
||||
.background(model.folder == nil ? Color.gray : lime,in:RoundedRectangle(cornerRadius:12))
|
||||
}.buttonStyle(.plain).disabled(model.folder == nil)
|
||||
}
|
||||
if model.running {ProgressView().progressViewStyle(.linear).tint(lime)}
|
||||
if model.output != nil && !model.running {
|
||||
HStack {
|
||||
Button("Mostrar IPA"){model.revealIPA()}
|
||||
Button("Mostrar datos romfs"){model.revealAssets()}
|
||||
Button("Abrir Sideloadly"){model.openSideloadly()}
|
||||
}
|
||||
Text("Primera instalación: instala la IPA ligera y copia romfs por Finder. Para actualizar, conserva la misma cuenta y bundle ID; no desinstales la app.")
|
||||
.font(.system(size:12)).foregroundStyle(ink.opacity(0.8))
|
||||
}
|
||||
}.padding(24).background(Color.white.opacity(0.045),in:RoundedRectangle(cornerRadius:20))
|
||||
DisclosureGroup("Opciones y detalles",isExpanded:$model.showDetails) {
|
||||
VStack(alignment:.leading,spacing:12) {
|
||||
Toggle("Recrear sólo la app con las cachés existentes",isOn:$model.appOnly).disabled(model.running)
|
||||
Text("Úsalo para actualizar el editor. Para una versión nueva del juego, déjalo desactivado. La primera preparación necesita Xcode y Homebrew instalados.").font(.system(size:12)).foregroundStyle(.gray)
|
||||
if !model.transcript.isEmpty {
|
||||
ScrollView {Text(model.transcript).font(.system(size:10,design:.monospaced)).textSelection(.enabled).frame(maxWidth:.infinity,alignment:.leading)}.frame(height:180)
|
||||
}
|
||||
HStack {
|
||||
Link("Guía de instalación",destination:URL(string:"https://github.com/nalancer08/nxnative-personal#readme")!)
|
||||
if !model.transcript.isEmpty {Button("Copiar diagnóstico"){NSPasteboard.general.clearContents();NSPasteboard.general.setString(model.transcript,forType:.string)}}
|
||||
}
|
||||
}.padding(.top,10)
|
||||
}.font(.system(size:12)).foregroundStyle(.gray)
|
||||
Text("Tus juegos y llaves se quedan en tu Mac. IPA sin firmar · Sideloadly usa tu cuenta personal.\nMetroid Dread confirmado en nuestro iPhone. Los demás dependen del perfil, versión y dispositivo.")
|
||||
.font(.system(size:11)).foregroundStyle(.gray).lineSpacing(4)
|
||||
}.padding(32)
|
||||
}
|
||||
}.frame(minWidth:820,minHeight:680).preferredColorScheme(.dark)
|
||||
.onReceive(NotificationCenter.default.publisher(for:NSApplication.willTerminateNotification)) {_ in model.cancel()}
|
||||
}
|
||||
}
|
||||
|
||||
@main struct NxNativeEasyApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup("nxnative · Preparar para iPhone") {ContentView()}
|
||||
.defaultSize(width:920,height:780)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 315 KiB |
@@ -1,3 +1,15 @@
|
||||
# Desktop interface
|
||||
|
||||
For daily use, open `nxnative.app`, choose a game folder and press **Preparar para iPhone**.
|
||||
The native macOS interface shows progress and provides buttons for the lightweight IPA,
|
||||
RomFS folder and Sideloadly. First-time setup still needs full Xcode, Homebrew and the
|
||||
user's game/key files. It never signs, installs or enters an Apple account.
|
||||
|
||||
To create it on another Mac, double-click `scripts/Abrir-interfaz.command` from the
|
||||
checkout. Build outputs stay beside the repository, under `nxnative-easy/`; the local
|
||||
app points back to this checkout, so recreate it if the checkout is moved. The existing
|
||||
CLI remains available below. Cancellation signals the entire build process group.
|
||||
|
||||
# Personal iOS builds
|
||||
|
||||
This private branch retains nxnative's MIT license and attribution. It starts from
|
||||
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
nxe_repo=$(cd "$(dirname "$0")/.." && pwd -P)
|
||||
nxe_output="$(dirname "$nxe_repo")/nxnative-easy/nxnative.app"
|
||||
/usr/bin/python3 "$nxe_repo/scripts/build_desktop_app.py" --output "$nxe_output" --open
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build a local native launcher without Apple signing or game resources."""
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import plistlib
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
REPO=Path(__file__).resolve().parents[1]
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--output',type=Path,required=True,help='app path outside the source repository')
|
||||
parser.add_argument('--open',action='store_true')
|
||||
args=parser.parse_args()
|
||||
output=args.output.expanduser().absolute()
|
||||
if output==REPO or REPO in output.parents:parser.error('Keep build outputs outside the repository.')
|
||||
if output.suffix!='.app':parser.error('Output must end with .app')
|
||||
contents=output/'Contents';macos=contents/'MacOS';macos.mkdir(parents=True,exist_ok=True)
|
||||
subprocess.run(['xcrun','swiftc','-parse-as-library','-swift-version','5',
|
||||
str(REPO/'desktop/NxNativeEasy/main.swift'),'-o',str(macos/'NxNativeEasy'),
|
||||
'-framework','SwiftUI','-framework','AppKit'],check=True)
|
||||
info=dict(CFBundleIdentifier='com.nalancer08.nxnative.easy',CFBundleName='nxnative',
|
||||
CFBundleDisplayName='nxnative · Preparar para iPhone',CFBundleExecutable='NxNativeEasy',
|
||||
CFBundlePackageType='APPL',CFBundleShortVersionString='1.0',CFBundleVersion='1',
|
||||
LSMinimumSystemVersion='26.0',NSHighResolutionCapable=True,NxNativeSource=str(REPO))
|
||||
(contents/'Info.plist').write_bytes(plistlib.dumps(info))
|
||||
if args.open:subprocess.run(['open',str(output)],check=True)
|
||||
print(output)
|
||||
|
||||
if __name__=='__main__':main()
|
||||
@@ -0,0 +1,31 @@
|
||||
"""The GUI must cancel its worker group without affecting the caller."""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import unittest
|
||||
|
||||
ROOT=Path(__file__).resolve().parents[2]
|
||||
|
||||
@unittest.skipUnless(sys.platform=='darwin','macOS desktop worker')
|
||||
class DesktopSession(unittest.TestCase):
|
||||
def test_worker_has_separate_group_and_cancels(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
path=Path(temporary);marker=path/'ready'
|
||||
script=path/'worker.sh'
|
||||
script.write_text('echo ready > "$1"\nsleep 30\n')
|
||||
task=subprocess.Popen([sys.executable,str(ROOT/'desktop/NxNativeEasy/build_session.py'),str(script),str(marker)])
|
||||
try:
|
||||
deadline=time.monotonic()+5
|
||||
while not marker.exists() and time.monotonic()<deadline:time.sleep(0.02)
|
||||
self.assertTrue(marker.exists())
|
||||
self.assertEqual(os.getpgid(task.pid),task.pid)
|
||||
self.assertNotEqual(os.getpgrp(),task.pid)
|
||||
os.killpg(task.pid,signal.SIGTERM)
|
||||
self.assertEqual(task.wait(timeout=5),-signal.SIGTERM)
|
||||
finally:
|
||||
if task.poll() is None:
|
||||
os.killpg(task.pid,signal.SIGKILL);task.wait()
|
||||
Reference in new issue
Block a user