From fd93fdc3088d8b320e453a2f63f9d058aaeaa238 Mon Sep 17 00:00:00 2001
From: Erick Sanchez <7268597+nalancer08@users.noreply.github.com>
Date: Mon, 26 Oct 2026 12:58:52 -0600
Subject: [PATCH] Require complete Mac game bundles and clarify iPhone-only
splitting
---
README-es.md | 4 ++--
README.md | 4 ++--
desktop/NxNativeEasy/Localization.swift | 3 ++-
desktop/NxNativeEasy/main.swift | 4 +++-
scripts/build_mac.py | 5 +++--
scripts/tests/test_mac_pipeline.py | 15 +++++++++++++++
6 files changed, 27 insertions(+), 8 deletions(-)
diff --git a/README-es.md b/README-es.md
index 2ea0dac..1c9ddc1 100644
--- a/README-es.md
+++ b/README-es.md
@@ -26,7 +26,7 @@ Cuando abra la app:
**Las siguientes veces:** abre **nxnative Easy.app** en la carpeta **Aplicaciones de tu usuario**. La preparación conserva su propia copia del código; puedes borrar el ZIP y la carpeta que descargaste.
-**Mac:** crea una `.app` del juego; pulsa **Abrir juego**. No necesitas Sideloadly ni un iPhone. Las partidas quedan en `~/Library/Application Support/nxnative/
/`.
+**Mac:** crea una sola `.app` completa, con todos los datos del juego dentro; pulsa **Abrir juego**. No necesitas copiar una carpeta RomFS aparte. No necesitas Sideloadly ni un iPhone. Las partidas quedan en `~/Library/Application Support/nxnative//`.
**iPhone / iPad:** crea una IPA ligera sin firmar. Instálala con Sideloadly y copia los datos del juego como te indica la app.
@@ -40,7 +40,7 @@ Partimos de [seedds/nxnative](https://github.com/seedds/nxnative): el motor, los
- **App para Mac:** elegir juego, avance, cancelación y acceso a IPA, datos y Sideloadly.
- **Controles editables en iPhone:** posición, tamaño y opacidad, guardados por juego y layout.
-- **IPA ligera automática:** los datos `romfs` van aparte; se conservan salidas y cachés válidas.
+- **IPA ligera sólo para iPhone/iPad:** los datos `romfs` van aparte; se conservan salidas y cachés válidas.
- **Llaves del usuario:** debes seleccionar la carpeta de tus propias `prod.keys`. No incluimos, descargamos ni distribuimos llaves o juegos.
- **Preparación local:** sin cuenta Apple, sin certificados de empresa y sin servicio que reciba tus archivos.
diff --git a/README.md b/README.md
index 4082723..3f35ec1 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ When the app opens:
**After setup:** open **nxnative Easy.app** in your home **Applications** folder. The setup keeps its source checkout separately, so you can delete the downloaded ZIP and folder.
-**Mac:** creates a game `.app`; click **Open game**. No Sideloadly or iPhone needed. Saves stay in `~/Library/Application Support/nxnative//`.
+**Mac:** creates one complete game `.app`, with its game data included inside; click **Open game**. No separate RomFS folder to copy. No Sideloadly or iPhone needed. Saves stay in `~/Library/Application Support/nxnative//`.
**iPhone / iPad:** creates an unsigned lightweight IPA; install it with Sideloadly, then copy the game data as shown in the app.
@@ -40,7 +40,7 @@ Built on [seedds/nxnative](https://github.com/seedds/nxnative). The original pro
- **Native Mac app:** game-folder selection, progress, cancellation, and buttons for the IPA, RomFS data and Sideloadly.
- **Editable iPhone touch controls:** position, size and opacity, saved per game and 2D/3D layout.
-- **Automatic lightweight IPA export:** RomFS data live separately; existing outputs and valid caches are preserved.
+- **Lightweight IPA export for iPhone/iPad only:** RomFS data live separately; existing outputs and valid caches are preserved.
- **Your own keys:** explicitly select the external folder containing your `prod.keys`. No games or keys are included, downloaded or distributed.
- **Local preparation:** no Apple account or company certificate is used to build the desktop app.
diff --git a/desktop/NxNativeEasy/Localization.swift b/desktop/NxNativeEasy/Localization.swift
index 977ebcf..9d89c4c 100644
--- a/desktop/NxNativeEasy/Localization.swift
+++ b/desktop/NxNativeEasy/Localization.swift
@@ -15,7 +15,8 @@ struct EasyStrings {
"¿Dónde quieres jugar?": "Where do you want to play?",
"Elige un dispositivo": "Choose a device",
"Elige dónde quieres jugar y añade tus archivos.": "Choose where to play, then add your own files.",
- "Creamos una app para jugar en tu Mac.": "We create an app to play on your Mac.",
+ "Creamos una app completa para jugar en tu Mac.": "We create a complete app to play on your Mac.",
+ "Todo el juego va dentro de la app. No necesitas copiar carpetas.": "The entire game is inside the app. No folders to copy.",
"Terminó, pero no encontramos la app.": "Finished, but the app could not be found.",
"Tu juego está listo para Mac.": "Your game is ready for Mac.",
"Listo. Juega en tu Mac.": "Ready. Play on your Mac.",
diff --git a/desktop/NxNativeEasy/main.swift b/desktop/NxNativeEasy/main.swift
index d7bae35..68c88c6 100644
--- a/desktop/NxNativeEasy/main.swift
+++ b/desktop/NxNativeEasy/main.swift
@@ -274,7 +274,7 @@ struct ContentView: View {
}
VStack(alignment:.leading,spacing:6) {
Text(model.text("Prepara tu juego.")).font(.system(size:32,weight:.bold,design:.rounded)).foregroundStyle(ink)
- Text(model.text(model.isMac ? "Creamos una app para jugar en tu Mac." : "Elige dónde quieres jugar y añade tus archivos."))
+ Text(model.text(model.isMac ? "Creamos una app completa para jugar en tu Mac." : "Elige dónde quieres jugar y añade tus archivos."))
.font(.system(size:14)).foregroundStyle(.secondary)
}
VStack(alignment:.leading,spacing:10) {
@@ -352,6 +352,8 @@ struct ContentView: View {
.buttonStyle(.borderedProminent).tint(lime).foregroundStyle(.black)
Button(model.text("Mostrar app")){model.revealIPA()}
}
+ Text(model.text("Todo el juego va dentro de la app. No necesitas copiar carpetas."))
+ .font(.system(size:12)).foregroundStyle(.secondary)
Text(model.text("Tus partidas se guardan en este Mac y se conservan al crear otra versión."))
.font(.system(size:12)).foregroundStyle(.secondary)
} else {
diff --git a/scripts/build_mac.py b/scripts/build_mac.py
index 9d7e8c8..a1e773d 100644
--- a/scripts/build_mac.py
+++ b/scripts/build_mac.py
@@ -36,8 +36,9 @@ def main(argv=None):
subprocess.run([sys.executable,'-m','pip','install','nsz'],check=True)
code=load_builder().cli(command+['-k',str(keys)])
if code: return code
- if not (output/'Contents/MacOS/dmhost').is_file():
- raise ValueError('Build finished without a Mac game app.')
+ romfs=output/'Contents/Resources/romfs'
+ if not (output/'Contents/MacOS/dmhost').is_file() or not romfs.is_dir() or romfs.is_symlink():
+ raise ValueError('Build finished without a complete Mac game app (embedded game data required).')
record={'schema':1,'app_path':str(output)}
state=game/'nxnative-mac.json'
temporary=state.with_suffix('.json.tmp')
diff --git a/scripts/tests/test_mac_pipeline.py b/scripts/tests/test_mac_pipeline.py
index 32458b0..277dfd7 100644
--- a/scripts/tests/test_mac_pipeline.py
+++ b/scripts/tests/test_mac_pipeline.py
@@ -20,6 +20,7 @@ class MacPipeline(unittest.TestCase):
calls.append(args)
host=Path(args[args.index('-o')+1])/'Contents/MacOS/dmhost'
host.parent.mkdir(parents=True);host.write_bytes(b'synthetic')
+ (host.parents[1]/'Resources/romfs').mkdir(parents=True)
return 0
with patch.object(build_mac,'load_builder',return_value=Builder()),patch.object(build_mac.platform,'machine',return_value='arm64'):
self.assertEqual(build_mac.main([temp,'--keys-folder',temp]),0)
@@ -37,3 +38,17 @@ class MacPipeline(unittest.TestCase):
with patch.object(build_mac,'load_builder',return_value=Builder()),patch.object(build_mac.platform,'machine',return_value='arm64'):
self.assertEqual(build_mac.main([temp,'--keys-folder',temp]),1)
self.assertEqual((root/'nxnative-mac.json').read_text(),'{"app_path":"old"}')
+
+ def test_incomplete_app_does_not_replace_previous_record(self):
+ with tempfile.TemporaryDirectory() as temp:
+ root=Path(temp);(root/'base.nsp').write_bytes(b'synthetic');(root/'prod.keys').write_bytes(b'synthetic')
+ (root/'nxnative-mac.json').write_text('{"app_path":"old"}')
+ class Builder:
+ def cli(self,args):
+ host=Path(args[args.index('-o')+1])/'Contents/MacOS/dmhost'
+ host.parent.mkdir(parents=True);host.write_bytes(b'synthetic')
+ return 0
+ with patch.object(build_mac,'load_builder',return_value=Builder()),patch.object(build_mac.platform,'machine',return_value='arm64'):
+ with self.assertRaisesRegex(ValueError,'embedded game data required'):
+ build_mac.main([temp,'--keys-folder',temp])
+ self.assertEqual((root/'nxnative-mac.json').read_text(),'{"app_path":"old"}')