Add custom font and configure React Native project settings

- Added the Pixel Cyr font to the mobile assets directory.
- Created a new React Native configuration file to specify project settings, including Android source directory, app name, and package name.
- Configured asset linking for the mobile assets.
This commit is contained in:
moonpower
2025-11-23 13:19:06 +03:00
parent 40ff6e2d15
commit 30d4e57a3c
23 changed files with 814 additions and 247 deletions
Vendored
BIN
View File
Binary file not shown.
+9 -6
View File
@@ -28,9 +28,12 @@ snap/
psxe.app
.DS_Store
*/.DS_Store
node_modules
ios/HostApp/Pods/*
ios/HostApp/build/*
helpers/*
android/app/src/main/jniLibs/
android/native-deps/
node_modules
ios/HostApp/Pods/*
ios/HostApp/build/*
helpers/*
android/app/src/main/jniLibs/
android/native-deps/
mobile/temp/*
ios/HostApp/ARMSX.xcodeproj/
ios/HostApp/ARMSX.xcworkspace/
@@ -2,6 +2,7 @@ package com.nanodata.armsx;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -36,12 +37,29 @@ public class ARMSXModule extends ReactContextBaseJavaModule {
return;
}
forceLandscapeInternal(host);
Intent intent = new Intent(host, EmulatorActivity.class);
intent.putExtra(EmulatorActivity.EXTRA_NATIVE_ARGS, readableArrayToStrings(args));
host.startActivity(intent);
promise.resolve(true);
}
@ReactMethod
public void forceLandscape(Promise promise) {
Activity host = getCurrentActivity();
if (host == null) {
promise.reject("no_activity", "Activity not available");
return;
}
forceLandscapeInternal(host);
promise.resolve(true);
}
private void forceLandscapeInternal(@NonNull Activity host) {
host.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
}
private @Nullable String[] readableArrayToStrings(@Nullable ReadableArray array) {
if (array == null) {
return null;
+49 -130
View File
@@ -268,17 +268,31 @@ static bool write_settings_file(
return true;
}
static std::string get_current_directory() {
char buf[PATH_MAX];
if (getcwd(buf, sizeof(buf)))
return std::string(buf);
return std::string(".");
}
struct FilePickerState {
bool open = false;
std::string cwd;
std::string selection;
};
static std::string get_current_directory() {
char buf[PATH_MAX];
if (getcwd(buf, sizeof(buf)))
return std::string(buf);
return std::string(".");
static std::string get_pref_directory() {
const char* pref = psxe_cfg_get_pref_path();
if (pref && pref[0])
return std::string(pref);
return get_current_directory();
}
static std::string default_settings_path() {
const char* pref = psxe_cfg_get_pref_path();
if (pref && pref[0])
return std::string(pref) + "settings.toml";
return std::string("settings.toml");
}
static bool is_directory(const std::string& path) {
@@ -321,7 +335,7 @@ static bool file_picker_popup(const char* popup_id, FilePickerState& state, std:
ImGui::SetNextWindowSize(ImVec2(420.0f, 320.0f), ImGuiCond_Appearing);
if (ImGui::BeginPopupModal(popup_id, nullptr, ImGuiWindowFlags_NoResize)) {
if (state.cwd.empty())
state.cwd = get_current_directory();
state.cwd = get_pref_directory();
ImGui::TextWrapped("Current folder:\n%s", state.cwd.c_str());
@@ -420,7 +434,7 @@ extern "C" PSXE_API int imgui_frontend_main(int argc, const char* argv[]) {
std::string exe_path = cfg->exe ? cfg->exe : "";
std::string region = cfg->region ? cfg->region : "ntsc";
std::string model = cfg->model ? cfg->model : "scph1001";
std::string settings_path = cfg->settings_path ? cfg->settings_path : "settings.toml";
std::string settings_path = cfg->settings_path ? cfg->settings_path : default_settings_path();
std::string bios_search = cfg->bios_search ? cfg->bios_search : "bios";
bool quiet = cfg->quiet != 0;
bool use_args = cfg->use_args != 0;
@@ -491,8 +505,6 @@ extern "C" PSXE_API int imgui_frontend_main(int argc, const char* argv[]) {
bool start_requested = false;
bool show_settings = false;
bool show_about = false;
bool show_bios_popup = false;
bool show_cd_popup = false;
FilePickerState bios_picker{};
FilePickerState cd_picker{};
@@ -545,11 +557,7 @@ extern "C" PSXE_API int imgui_frontend_main(int argc, const char* argv[]) {
#else
bios_picker.open = true;
bios_picker.selection.clear();
bios_picker.cwd.clear();
show_bios_popup = true;
show_cd_popup = false;
show_settings = false;
show_about = false;
bios_picker.cwd = get_pref_directory();
#endif
}
if (ImGui::MenuItem("Load CDROM")) {
@@ -558,11 +566,7 @@ extern "C" PSXE_API int imgui_frontend_main(int argc, const char* argv[]) {
#else
cd_picker.open = true;
cd_picker.selection.clear();
cd_picker.cwd.clear();
show_cd_popup = true;
show_bios_popup = false;
show_settings = false;
show_about = false;
cd_picker.cwd = get_pref_directory();
#endif
}
ImGui::Separator();
@@ -575,129 +579,42 @@ extern "C" PSXE_API int imgui_frontend_main(int argc, const char* argv[]) {
if (ImGui::MenuItem("Settings")) {
show_settings = true;
show_about = false;
show_bios_popup = false;
show_cd_popup = false;
}
if (ImGui::MenuItem("About")) {
show_about = true;
show_settings = false;
show_bios_popup = false;
show_cd_popup = false;
}
ImGui::EndMenuBar();
}
ImGui::End();
if (show_bios_popup || bios_picker.open
#ifdef __EMSCRIPTEN__
|| !g_wasm_pending_bios.empty()
#endif
) {
if (show_bios_popup)
ImGui::OpenPopup("Load BIOS");
ImGui::SetNextWindowSize(ImVec2(420.0f, 210.0f), ImGuiCond_Appearing);
if (ImGui::BeginPopupModal("Load BIOS", &show_bios_popup, ImGuiWindowFlags_NoResize)) {
if (ImGui::InputText("Path", bios_buf.data(), bios_buf.size())) {
bios_path = bios_buf.data();
}
#ifndef __EMSCRIPTEN__
if (ImGui::Button("Browse...")) {
bios_picker.open = true;
}
#else
if (ImGui::Button("Browse...")) {
psxe_wasm_pick_file(1);
}
#endif
ImGui::SameLine();
if (ImGui::Button("Use BIOS")) {
bios_path = bios_buf.data();
settings_dirty = true;
show_bios_popup = false;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Close")) {
show_bios_popup = false;
ImGui::CloseCurrentPopup();
}
ImGui::Separator();
ImGui::TextWrapped("Select a BIOS file to override the default search/model.");
ImGui::EndPopup();
}
if (file_picker_popup("Browse BIOS File", bios_picker, bios_path)) {
sync_buffer(bios_buf, bios_path);
show_bios_popup = false;
settings_dirty = true;
}
#ifdef __EMSCRIPTEN__
if (!g_wasm_pending_bios.empty()) {
bios_path = g_wasm_pending_bios;
sync_buffer(bios_buf, bios_path);
show_bios_popup = false;
settings_dirty = true;
g_wasm_pending_bios.clear();
}
#endif
ImGui::SetNextWindowSize(ImVec2(420.0f, 210.0f), ImGuiCond_Appearing);
if (file_picker_popup("Browse BIOS File", bios_picker, bios_path)) {
sync_buffer(bios_buf, bios_path);
settings_dirty = true;
}
if (show_cd_popup || cd_picker.open
#ifdef __EMSCRIPTEN__
|| !g_wasm_pending_cd.empty()
#endif
) {
if (show_cd_popup)
ImGui::OpenPopup("Load CDROM");
ImGui::SetNextWindowSize(ImVec2(420.0f, 210.0f), ImGuiCond_Appearing);
if (ImGui::BeginPopupModal("Load CDROM", &show_cd_popup, ImGuiWindowFlags_NoResize)) {
if (ImGui::InputText("Path", cdrom_buf.data(), cdrom_buf.size())) {
cdrom_path = cdrom_buf.data();
}
#ifndef __EMSCRIPTEN__
if (ImGui::Button("Browse...")) {
cd_picker.open = true;
}
#else
if (ImGui::Button("Browse...")) {
psxe_wasm_pick_file(0);
}
#endif
ImGui::SameLine();
if (ImGui::Button("Use CDROM")) {
cdrom_path = cdrom_buf.data();
show_cd_popup = false;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Close")) {
show_cd_popup = false;
ImGui::CloseCurrentPopup();
}
ImGui::Separator();
ImGui::TextWrapped("Choose a CDROM image to boot.");
ImGui::EndPopup();
}
if (file_picker_popup("Browse CDROM File", cd_picker, cdrom_path)) {
sync_buffer(cdrom_buf, cdrom_path);
show_cd_popup = false;
}
#ifdef __EMSCRIPTEN__
if (!g_wasm_pending_cd.empty()) {
cdrom_path = g_wasm_pending_cd;
sync_buffer(cdrom_buf, cdrom_path);
show_cd_popup = false;
g_wasm_pending_cd.clear();
}
#endif
if (!g_wasm_pending_bios.empty()) {
bios_path = g_wasm_pending_bios;
sync_buffer(bios_buf, bios_path);
settings_dirty = true;
g_wasm_pending_bios.clear();
}
#endif
ImGui::SetNextWindowSize(ImVec2(420.0f, 210.0f), ImGuiCond_Appearing);
if (file_picker_popup("Browse CDROM File", cd_picker, cdrom_path)) {
sync_buffer(cdrom_buf, cdrom_path);
}
#ifdef __EMSCRIPTEN__
if (!g_wasm_pending_cd.empty()) {
cdrom_path = g_wasm_pending_cd;
sync_buffer(cdrom_buf, cdrom_path);
g_wasm_pending_cd.clear();
}
#endif
if (show_settings) {
center_next_window(ImVec2(392.0f, 448.0f));
@@ -817,7 +734,9 @@ extern "C" PSXE_API int imgui_frontend_main(int argc, const char* argv[]) {
ImGui::Render();
if (settings_dirty && !settings_path.empty()) {
if (settings_dirty) {
if (settings_path.empty())
settings_path = default_settings_path();
write_settings_file(
settings_path,
bios_search,
@@ -910,7 +829,7 @@ extern "C" PSXE_API void imgui_ingame_menu_render(int paused, const char* curren
if (ImGui::MenuItem("Load CDROM...")) {
g_ingame_cd_picker.open = true;
g_ingame_cd_picker.selection.clear();
g_ingame_cd_picker.cwd.clear();
g_ingame_cd_picker.cwd = get_pref_directory();
}
if (ImGui::MenuItem("Quit")) {
actions->quit = 1;
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+51 -31
View File
@@ -8,15 +8,15 @@
/* Begin PBXBuildFile section */
0F9A2930A40B75845584BE6B /* RNOverlayController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0D4D51B594CEA0DA332E5D3E /* RNOverlayController.mm */; };
316438D36EEBE0DEF9F94F71 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D0A213EE07DA830C655C2A2A /* PrivacyInfo.xcprivacy */; };
3464725A44F4A69FBE2AB974 /* libarmsx.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 653A133BDB61BD1059CFAF23 /* libarmsx.dylib */; };
3A83F5253BCB4AD47A6742B1 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F686B87C80EB4D3204CDEEC0 /* main.m */; };
4193FB5FD92D4849FE747E3C /* libPods-ARMSX.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AC80B70529AD04509472C06 /* libPods-ARMSX.a */; };
48352C131E8B0327373C61EC /* SDL2.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 518B2258235B15B15373B04A /* SDL2.xcframework */; };
5480B78F3661F4CB9446657C /* libarmsx.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 653A133BDB61BD1059CFAF23 /* libarmsx.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
69C36E3140D59BA64EBFD982 /* libPods-ARMSX.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E55729F629E2EF87949515F /* libPods-ARMSX.a */; };
7435C97274E9B2668E6C9C86 /* SDL2.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 518B2258235B15B15373B04A /* SDL2.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
8BC157B9E48AA3D81793AA6A /* ARMSXModule.m in Sources */ = {isa = PBXBuildFile; fileRef = B35E8D4127C001F1E6F6F2AF /* ARMSXModule.m */; };
B16F42A290ECFF7DF154F24F /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9484FEBF96C002F04D73B70C /* AppDelegate.mm */; };
D01D643FB3D4148C346B6200 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 150B82E62E95502476AFB049 /* PrivacyInfo.xcprivacy */; };
E46372898534C89975BD3888 /* EmulatorRunner.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6CFA7D190974AD439774FB40 /* EmulatorRunner.mm */; };
/* End PBXBuildFile section */
@@ -39,22 +39,22 @@
019479875FC7433ACB65A02E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
03BD4A1BC72E3DE47140D96E /* EmulatorRunner.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EmulatorRunner.h; sourceTree = "<group>"; };
0D4D51B594CEA0DA332E5D3E /* RNOverlayController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RNOverlayController.mm; sourceTree = "<group>"; };
150B82E62E95502476AFB049 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
1760EE6722F9E2BEDFDCCC6D /* armsx_bridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = armsx_bridge.h; sourceTree = "<group>"; };
2EC8F1F7ACC46D5FABC3595C /* Pods-ARMSX.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ARMSX.release.xcconfig"; path = "Target Support Files/Pods-ARMSX/Pods-ARMSX.release.xcconfig"; sourceTree = "<group>"; };
518B2258235B15B15373B04A /* SDL2.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = SDL2.xcframework; path = ../Frameworks/SDL2.xcframework; sourceTree = "<group>"; };
64E02E161FEB892FC40F8BE1 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
653A133BDB61BD1059CFAF23 /* libarmsx.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libarmsx.dylib; path = ../Frameworks/libarmsx.dylib; sourceTree = "<group>"; };
6AC80B70529AD04509472C06 /* libPods-ARMSX.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ARMSX.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6CFA7D190974AD439774FB40 /* EmulatorRunner.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = EmulatorRunner.mm; sourceTree = "<group>"; };
6E26D0ECD2CD966E5EA1C326 /* Pods-ARMSX.release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Pods-ARMSX.release.xcconfig"; sourceTree = "<group>"; };
6E55729F629E2EF87949515F /* libPods-ARMSX.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ARMSX.a"; sourceTree = BUILT_PRODUCTS_DIR; };
87BA3EE4597141AA97A5557D /* ARMSX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ARMSX.app; sourceTree = BUILT_PRODUCTS_DIR; };
8E39205AA890680B1D4F8540 /* Pods-ARMSX.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ARMSX.debug.xcconfig"; path = "Target Support Files/Pods-ARMSX/Pods-ARMSX.debug.xcconfig"; sourceTree = "<group>"; };
9484FEBF96C002F04D73B70C /* AppDelegate.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AppDelegate.mm; sourceTree = "<group>"; };
996CB57F5DD4B88A0931BB95 /* ARMSXModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ARMSXModule.h; sourceTree = "<group>"; };
B35E8D4127C001F1E6F6F2AF /* ARMSXModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARMSXModule.m; sourceTree = "<group>"; };
C65B43834B690E6C938FFCEB /* Pods-ARMSX.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ARMSX.debug.xcconfig"; path = "Target Support Files/Pods-ARMSX/Pods-ARMSX.debug.xcconfig"; sourceTree = "<group>"; };
D0A213EE07DA830C655C2A2A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
D5E6D32A2F0405EA658F4F9F /* RNOverlayController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNOverlayController.h; sourceTree = "<group>"; };
E3FAF87D7E4A2345FC43CA68 /* Pods-ARMSX.debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Pods-ARMSX.debug.xcconfig"; sourceTree = "<group>"; };
F639A592EA750B3ED0B3243E /* Pods-ARMSX.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ARMSX.release.xcconfig"; path = "Target Support Files/Pods-ARMSX/Pods-ARMSX.release.xcconfig"; sourceTree = "<group>"; };
F686B87C80EB4D3204CDEEC0 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -65,7 +65,7 @@
files = (
48352C131E8B0327373C61EC /* SDL2.xcframework in Frameworks */,
3464725A44F4A69FBE2AB974 /* libarmsx.dylib in Frameworks */,
4193FB5FD92D4849FE747E3C /* libPods-ARMSX.a in Frameworks */,
69C36E3140D59BA64EBFD982 /* libPods-ARMSX.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -77,7 +77,7 @@
children = (
653A133BDB61BD1059CFAF23 /* libarmsx.dylib */,
518B2258235B15B15373B04A /* SDL2.xcframework */,
6AC80B70529AD04509472C06 /* libPods-ARMSX.a */,
6E55729F629E2EF87949515F /* libPods-ARMSX.a */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -89,8 +89,8 @@
D0581165A1AB8CE875BD92E0 /* Sources */,
473DBC863A1B097C0DD3710F /* Frameworks */,
9B7D6BDFE7232B2E5D8A379F /* Products */,
150B82E62E95502476AFB049 /* PrivacyInfo.xcprivacy */,
A03BFBC5506510D43087F66D /* Pods */,
D0A213EE07DA830C655C2A2A /* PrivacyInfo.xcprivacy */,
D56003A6393AA918CDCBC1DD /* Pods */,
);
sourceTree = "<group>";
};
@@ -102,16 +102,6 @@
name = Products;
sourceTree = "<group>";
};
A03BFBC5506510D43087F66D /* Pods */ = {
isa = PBXGroup;
children = (
C65B43834B690E6C938FFCEB /* Pods-ARMSX.debug.xcconfig */,
2EC8F1F7ACC46D5FABC3595C /* Pods-ARMSX.release.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
B6E3FD0B464E641CA58C9EA3 /* Pods-ARMSX */ = {
isa = PBXGroup;
children = (
@@ -140,6 +130,16 @@
path = Sources;
sourceTree = "<group>";
};
D56003A6393AA918CDCBC1DD /* Pods */ = {
isa = PBXGroup;
children = (
8E39205AA890680B1D4F8540 /* Pods-ARMSX.debug.xcconfig */,
F639A592EA750B3ED0B3243E /* Pods-ARMSX.release.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -147,13 +147,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 1A8EEE030E970E82EC7B77F8 /* Build configuration list for PBXNativeTarget "ARMSX" */;
buildPhases = (
317B2A99C6743282DB0B4378 /* [CP] Check Pods Manifest.lock */,
28E2861D40C7116E64D8A8A7 /* [CP] Check Pods Manifest.lock */,
CFEF6A563B03F0739AAB6D78 /* Bundle React Native overlay */,
21709ABD74D1031F0389C4EC /* Sources */,
D6910F21954EF81975654CFA /* Frameworks */,
8F7F79E31569D7CF1ACBC707 /* Embed Frameworks */,
CC0F3B60AB5121E52FB88488 /* Resources */,
A1F84CFC47166FD1D4F87D5C /* [CP] Embed Pods Frameworks */,
A2B4EFBBBDD2A4D81A6CEE13 /* [CP] Copy Pods Resources */,
08BB0BDAE55155CA21616714 /* Resources */,
38A74F8F145E32A3662964C2 /* [CP] Embed Pods Frameworks */,
77DB61B4189D46655E081DCE /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -193,18 +194,18 @@
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
CC0F3B60AB5121E52FB88488 /* Resources */ = {
08BB0BDAE55155CA21616714 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D01D643FB3D4148C346B6200 /* PrivacyInfo.xcprivacy in Resources */,
316438D36EEBE0DEF9F94F71 /* PrivacyInfo.xcprivacy in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
317B2A99C6743282DB0B4378 /* [CP] Check Pods Manifest.lock */ = {
28E2861D40C7116E64D8A8A7 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -226,7 +227,7 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
A1F84CFC47166FD1D4F87D5C /* [CP] Embed Pods Frameworks */ = {
38A74F8F145E32A3662964C2 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -243,7 +244,7 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ARMSX/Pods-ARMSX-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
A2B4EFBBBDD2A4D81A6CEE13 /* [CP] Copy Pods Resources */ = {
77DB61B4189D46655E081DCE /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -260,6 +261,25 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ARMSX/Pods-ARMSX-resources.sh\"\n";
showEnvVarsInLog = 0;
};
CFEF6A563B03F0739AAB6D78 /* Bundle React Native overlay */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Bundle React Native overlay";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -euo pipefail\nNODE_CANDIDATES=(\n \"${NODE_BINARY:-}\"\n \"$(command -v node 2>/dev/null || true)\"\n \"/opt/homebrew/bin/node\"\n \"/usr/local/bin/node\"\n \"/usr/bin/node\"\n \"/usr/bin/env node\"\n)\n\nfor candidate in \"${NODE_CANDIDATES[@]}\"; do\n if [ -n \"$candidate\" ] && [ -x \"$candidate\" ]; then\n export NODE_BINARY=\"$candidate\"\n break\n fi\ndone\n\nif [ -z \"${NODE_BINARY:-}\" ]; then\n echo \"error: Unable to locate node. Set NODE_BINARY to your node path (e.g. $(which node)).\" >&2\n exit 1\nfi\nexport RCT_METRO_PORT=${RCT_METRO_PORT:-8081}\nexport PROJECT_ROOT=\"$PROJECT_DIR/../../mobile\"\ncd \"$PROJECT_ROOT/..\"\n\nif [ ! -f \"$PROJECT_ROOT/../node_modules/react-native/scripts/react-native-xcode.sh\" ]; then\n echo \"error: react-native-xcode.sh missing. Did you run npm install?\" >&2\n exit 1\nfi\n\nexport ENTRY_FILE=\"index.js\"\nexport BUNDLE_OUTPUT=\"$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/main.jsbundle\"\nexport ASSETS_DEST=\"$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH\"\nexport DEV=false\n\n\"$PROJECT_ROOT/../node_modules/react-native/scripts/react-native-xcode.sh\"\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -280,7 +300,7 @@
/* Begin XCBuildConfiguration section */
6C84D25B5BF8B28E170A1AC3 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C65B43834B690E6C938FFCEB /* Pods-ARMSX.debug.xcconfig */;
baseConfigurationReference = 8E39205AA890680B1D4F8540 /* Pods-ARMSX.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -447,7 +467,7 @@
};
EA79FDBEDBB17F1B501A7D1A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 2EC8F1F7ACC46D5FABC3595C /* Pods-ARMSX.release.xcconfig */;
baseConfigurationReference = F639A592EA750B3ED0B3243E /* Pods-ARMSX.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -10,5 +10,13 @@
<integer>70</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>6B7EECC7E0E8E94CFC0841B0</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>
+2 -1
View File
@@ -1,5 +1,6 @@
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <UIKit/UIKit.h>
@interface ARMSXModule : NSObject <RCTBridgeModule>
@interface ARMSXModule : NSObject <RCTBridgeModule, UIDocumentPickerDelegate>
@end
+147
View File
@@ -1,5 +1,12 @@
#import "ARMSXModule.h"
#import "AppDelegate.h"
#import <React/RCTUtils.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
@interface ARMSXModule ()
@property(nonatomic, copy) RCTPromiseResolveBlock pendingResolve;
@property(nonatomic, copy) RCTPromiseRejectBlock pendingReject;
@end
@implementation ARMSXModule
@@ -9,6 +16,8 @@ RCT_EXPORT_METHOD(loadEmu:(NSArray<NSString *> *)args
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
dispatch_async(dispatch_get_main_queue(), ^{
[self forceLandscapeOrientation];
AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
if (!delegate) {
reject(@"no_delegate", @"AppDelegate unavailable", nil);
@@ -20,4 +29,142 @@ RCT_EXPORT_METHOD(loadEmu:(NSArray<NSString *> *)args
});
}
RCT_EXPORT_METHOD(forceLandscape:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
dispatch_async(dispatch_get_main_queue(), ^{
[self forceLandscapeOrientation];
resolve(@(YES));
});
}
RCT_EXPORT_METHOD(pickPath:(NSString *)kind
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.pendingResolve) {
self.pendingReject(@"picker_busy", @"A picker is already active", nil);
self.pendingResolve = nil;
self.pendingReject = nil;
}
self.pendingResolve = resolve;
self.pendingReject = reject;
NSArray *types;
if (@available(iOS 14.0, *)) {
types = @[UTTypeData];
} else {
types = @[@"public.data"];
}
UIDocumentPickerViewController *picker;
if (@available(iOS 14.0, *)) {
picker = [[UIDocumentPickerViewController alloc] initForOpeningContentTypes:types asCopy:NO];
} else {
picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:types inMode:UIDocumentPickerModeOpen];
}
picker.allowsMultipleSelection = NO;
picker.delegate = self;
picker.modalPresentationStyle = UIModalPresentationFormSheet;
UIViewController *root = RCTPresentedViewController();
if (root) {
[root presentViewController:picker animated:YES completion:nil];
} else {
self.pendingReject(@"no_view_controller", @"Unable to present picker", nil);
self.pendingResolve = nil;
self.pendingReject = nil;
}
});
}
RCT_EXPORT_METHOD(ensureGameFolder:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
NSError *error = nil;
NSURL *url = [self gamesDirectoryURLWithError:&error];
if (error || !url) {
reject(@"folder_error", @"Unable to create games folder", error);
return;
}
resolve([url path]);
}
RCT_EXPORT_METHOD(listGames:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
NSError *error = nil;
NSURL *dir = [self gamesDirectoryURLWithError:&error];
if (error || !dir) {
reject(@"folder_error", @"Unable to access games folder", error);
return;
}
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *contents = [fm contentsOfDirectoryAtURL:dir
includingPropertiesForKeys:@[NSURLIsDirectoryKey]
options:0
error:&error];
if (error) {
reject(@"list_error", @"Unable to read games folder", error);
return;
}
NSMutableArray *games = [NSMutableArray array];
NSSet *allowedExts = [NSSet setWithArray:@[@"bin", @"cue", @"iso", @"img", @"pbp"]];
for (NSURL *item in contents) {
NSNumber *isDir = nil;
[item getResourceValue:&isDir forKey:NSURLIsDirectoryKey error:nil];
if ([isDir boolValue]) continue;
NSString *ext = [[item pathExtension] lowercaseString];
if (![allowedExts containsObject:ext]) continue;
[games addObject:@{
@"name": [item lastPathComponent],
@"path": [item path]
}];
}
resolve(games);
}
- (NSURL *)gamesDirectoryURLWithError:(NSError **)errorPtr {
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *docs = [fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
NSURL *gamesDir = [docs URLByAppendingPathComponent:@"ARMSX/Games" isDirectory:YES];
if (![fm fileExistsAtPath:[gamesDir path]]) {
[fm createDirectoryAtURL:gamesDir withIntermediateDirectories:YES attributes:nil error:errorPtr];
}
return gamesDir;
}
#pragma mark - UIDocumentPickerDelegate
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
if (self.pendingResolve) {
self.pendingResolve(nil);
}
self.pendingResolve = nil;
self.pendingReject = nil;
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
NSURL *picked = [urls firstObject];
if (picked && self.pendingResolve) {
self.pendingResolve([picked path]);
} else if (self.pendingReject) {
self.pendingReject(@"no_selection", @"No file selected", nil);
}
self.pendingResolve = nil;
self.pendingReject = nil;
}
- (void)forceLandscapeOrientation {
NSNumber *value = @(UIInterfaceOrientationLandscapeRight);
[[UIDevice currentDevice] setValue:value forKey:@"orientation"];
[UIViewController attemptRotationToDeviceOrientation];
}
@end
+6
View File
@@ -31,10 +31,16 @@
<true/>
<key>UIStatusBarHidden</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
</dict>
</plist>
+20 -3
View File
@@ -23,9 +23,19 @@
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blackColor];
self.view.frame = [UIScreen mainScreen].bounds;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self mountOverlayIfNeeded];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.view.frame = [UIScreen mainScreen].bounds;
if (self.rootView) {
self.rootView.frame = self.view.bounds;
}
}
- (void)mountOverlayIfNeeded {
if (!self.bridge) {
self.bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:self.launchOptions];
@@ -39,10 +49,16 @@
}
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:self.bridge moduleName:@"ARMSXOverlay" initialProperties:nil];
rootView.frame = self.view.bounds;
rootView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
rootView.backgroundColor = [UIColor blackColor];
rootView.sizeFlexibility = RCTRootViewSizeFlexibilityNone;
rootView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:rootView];
[NSLayoutConstraint activateConstraints:@[
[rootView.topAnchor constraintEqualToAnchor:self.view.topAnchor],
[rootView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
[rootView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[rootView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
]];
self.rootView = rootView;
}
@@ -59,7 +75,8 @@
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
NSURL *bundleURL = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
return bundleURL ?: [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
+46
View File
@@ -19,6 +19,47 @@ targets:
optional: true
- path: ../../icon.icon
optional: true
preBuildScripts:
- name: "Bundle React Native overlay"
shell: /bin/sh
script: |
set -euo pipefail
NODE_CANDIDATES=(
"${NODE_BINARY:-}"
"$(command -v node 2>/dev/null || true)"
"/opt/homebrew/bin/node"
"/usr/local/bin/node"
"/usr/bin/node"
"/usr/bin/env node"
)
for candidate in "${NODE_CANDIDATES[@]}"; do
if [ -n "$candidate" ] && [ -x "$candidate" ]; then
export NODE_BINARY="$candidate"
break
fi
done
if [ -z "${NODE_BINARY:-}" ]; then
echo "error: Unable to locate node. Set NODE_BINARY to your node path (e.g. $(which node))." >&2
exit 1
fi
export RCT_METRO_PORT=${RCT_METRO_PORT:-8081}
export PROJECT_ROOT="$PROJECT_DIR/../../mobile"
cd "$PROJECT_ROOT/.."
if [ ! -f "$PROJECT_ROOT/../node_modules/react-native/scripts/react-native-xcode.sh" ]; then
echo "error: react-native-xcode.sh missing. Did you run npm install?" >&2
exit 1
fi
export ENTRY_FILE="index.js"
export BUNDLE_OUTPUT="$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/main.jsbundle"
export ASSETS_DEST="$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH"
export DEV=false
"$PROJECT_ROOT/../node_modules/react-native/scripts/react-native-xcode.sh"
basedOnDependencyAnalysis: false
settings:
INFOPLIST_FILE: Sources/Info.plist
PRODUCT_BUNDLE_IDENTIFIER: com.nanodata.armsx
@@ -42,3 +83,8 @@ targets:
- framework: ../Frameworks/libarmsx.dylib
embed: true
codeSign: true
schemes:
ARMSX:
build:
targets:
ARMSX: all
+437 -66
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More