diff --git a/AGENTS.md b/AGENTS.md index c657e3f..db7071b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 `/_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. diff --git a/README-comandos.md b/README-comandos.md new file mode 100644 index 0000000..e5590f9 --- /dev/null +++ b/README-comandos.md @@ -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. diff --git a/README.md b/README.md index e5590f9..50a26a5 100644 --- a/README.md +++ b/README.md @@ -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. +![Nuestra app para preparar juegos](docs/images/nxnative-easy.png) -[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. diff --git a/desktop/NxNativeEasy/build_session.py b/desktop/NxNativeEasy/build_session.py new file mode 100644 index 0000000..0d7ace9 --- /dev/null +++ b/desktop/NxNativeEasy/build_session.py @@ -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:]]) diff --git a/desktop/NxNativeEasy/main.swift b/desktop/NxNativeEasy/main.swift new file mode 100644 index 0000000..5bbb48f --- /dev/null +++ b/desktop/NxNativeEasy/main.swift @@ -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) + } +} diff --git a/docs/images/nxnative-easy.png b/docs/images/nxnative-easy.png new file mode 100644 index 0000000..5397d61 Binary files /dev/null and b/docs/images/nxnative-easy.png differ diff --git a/docs/personal-ios.md b/docs/personal-ios.md index 0b61eb7..79693f7 100644 --- a/docs/personal-ios.md +++ b/docs/personal-ios.md @@ -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 diff --git a/scripts/Abrir-interfaz.command b/scripts/Abrir-interfaz.command new file mode 100755 index 0000000..a2b6ccc --- /dev/null +++ b/scripts/Abrir-interfaz.command @@ -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 diff --git a/scripts/build_desktop_app.py b/scripts/build_desktop_app.py new file mode 100755 index 0000000..9d6d442 --- /dev/null +++ b/scripts/build_desktop_app.py @@ -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() diff --git a/scripts/tests/test_desktop_session.py b/scripts/tests/test_desktop_session.py new file mode 100644 index 0000000..81fb004 --- /dev/null +++ b/scripts/tests/test_desktop_session.py @@ -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()