mirror of
https://git.743378673.xyz/MeloNX/MeloNX.git
synced 2025-04-24 08:25:14 +00:00
Update a lot, new logging and such
This commit is contained in:
parent
0bb5389370
commit
b9282a25e8
49
.gitea/workflows/updateApp.yml
Normal file
49
.gitea/workflows/updateApp.yml
Normal file
@ -0,0 +1,49 @@
|
||||
name: Update apps.json on new release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: debian-trixie
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get install -y jq
|
||||
|
||||
- name: Extract release data
|
||||
id: release
|
||||
run: |
|
||||
echo "VERSION=${GITEA_REF_NAME}" >> $GITHUB_OUTPUT
|
||||
echo "DESCRIPTION=$(echo '${GITEA_EVENT_RELEASE_BODY}' | jq -Rs .)" >> $GITHUB_OUTPUT
|
||||
echo "DATE=$(date '+%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
IPA_URL=$(echo '${GITEA_EVENT_RELEASE_ASSETS}' | jq -r '.[0].browser_download_url')
|
||||
echo "DOWNLOAD_URL=$IPA_URL" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update apps.json
|
||||
run: |
|
||||
jq --arg version "${{ steps.release.outputs.VERSION }}" \
|
||||
--arg buildVersion "1" \
|
||||
--arg date "${{ steps.release.outputs.DATE }}" \
|
||||
--arg localizedDescription "${{ steps.release.outputs.DESCRIPTION }}" \
|
||||
--arg downloadURL "${{ steps.release.outputs.DOWNLOAD_URL }}" \
|
||||
'.apps[0].versions |= [{"version": $version, "buildVersion": $buildVersion, "date": $date, "localizedDescription": $localizedDescription, "downloadURL": $downloadURL, "minOSVersion": "15.0"}]' \
|
||||
apps.json > tmp.json && mv tmp.json apps.json
|
||||
|
||||
- name: Commit and push
|
||||
run: |
|
||||
git config user.name "gitea-actions"
|
||||
git config user.email "gitea-actions@localhost"
|
||||
git add apps.json
|
||||
git commit -m "Update apps.json for release ${{ steps.release.outputs.VERSION }}"
|
||||
git push
|
||||
env:
|
||||
GIT_AUTHOR_NAME: gitea-actions
|
||||
GIT_AUTHOR_EMAIL: gitea-actions@localhost
|
||||
GIT_COMMITTER_NAME: gitea-actions
|
||||
GIT_COMMITTER_EMAIL: gitea-actions@localhost
|
48
source.json
Normal file
48
source.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "MeloNX",
|
||||
"subtitle": "A source for the MeloNX Application",
|
||||
"description": "Welcome to the MeloNX source! The latest download for MeloNX.",
|
||||
"iconURL": "https://git.743378673.xyz/CycloKid/assets/media/branch/main/Melo/AppIcons/MeloNX.png",
|
||||
"headerURL": "https://cdn.discordapp.com/attachments/1320760161836466257/1331670540447912090/melon-x-not-melo-nx-amiright-guys.png?ex=67f556d6&is=67f40556&hm=71be8f109a14f1c47d8f4965aa017bccb5617962b7a9f5cdfb936a5a8135dad7&",
|
||||
"website": "https://MeloNX.org",
|
||||
"tintColor": "#AE34EB",
|
||||
"featuredApps": [
|
||||
"com.stossy11.MeloNX"
|
||||
],
|
||||
"apps": [
|
||||
{
|
||||
"name": "MeloNX",
|
||||
"bundleIdentifier": "com.stossy11.MeloNX",
|
||||
"developerName": "Stossy11",
|
||||
"subtitle": "An NX Emulator.",
|
||||
"localizedDescription": "MeloNX is an iOS Nintendo Switch emulator based on Ryujinx, written primarily in C#. Designed to bring accurate performance and a user-friendly interface to iOS, MeloNX makes Switch games accessible on Apple devices. Developed from the ground up, MeloNX is open-source and available on Github under the MeloNX license (Based on MIT) (requires increased memory limit)",
|
||||
"iconURL": "https://example.com/myapp_icon.png",
|
||||
"tintColor": "#AE34EB",
|
||||
"category": "games",
|
||||
"screenshots": [
|
||||
"https://git.743378673.xyz/stossy11/screenshots/raw/branch/main/IMG_0380.PNG",
|
||||
"https://git.743378673.xyz/stossy11/screenshots/raw/branch/main/IMG_0381.PNG"
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.7.0",
|
||||
"buildVersion": "1",
|
||||
"date": "2025-04-8",
|
||||
"localizedDescription": "First AltStore release!",
|
||||
"downloadURL": "https://git.743378673.xyz/MeloNX/MeloNX/releases/download/1.7.0/MeloNX.ipa",
|
||||
"minOSVersion": "15.0"
|
||||
}
|
||||
],
|
||||
"appPermissions": {
|
||||
"entitlements": [
|
||||
"com.apple.developer.kernel.increased-memory-limit"
|
||||
],
|
||||
"privacy": {
|
||||
"NSPhotoLibraryAddUsageDescription": "MeloNX needs access to your Photo Library in order to save images."
|
||||
}
|
||||
},
|
||||
"patreon": {}
|
||||
}
|
||||
],
|
||||
"news": []
|
||||
}
|
@ -128,10 +128,6 @@
|
||||
"Dependencies/Dynamic Libraries/libavutil.dylib" = (
|
||||
CodeSignOnCopy,
|
||||
);
|
||||
Dependencies/XCFrameworks/MoltenVK.xcframework = (
|
||||
CodeSignOnCopy,
|
||||
RemoveHeadersOnCopy,
|
||||
);
|
||||
Dependencies/XCFrameworks/SDL2.xcframework = (
|
||||
CodeSignOnCopy,
|
||||
RemoveHeadersOnCopy,
|
||||
@ -185,7 +181,6 @@
|
||||
Dependencies/XCFrameworks/libswresample.xcframework,
|
||||
Dependencies/XCFrameworks/libswscale.xcframework,
|
||||
Dependencies/XCFrameworks/libteakra.xcframework,
|
||||
Dependencies/XCFrameworks/MoltenVK.xcframework,
|
||||
Dependencies/XCFrameworks/SDL2.xcframework,
|
||||
);
|
||||
};
|
||||
@ -648,7 +643,7 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_TESTABILITY = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@ -727,6 +722,8 @@
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
);
|
||||
GCC_OPTIMIZATION_LEVEL = z;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -742,7 +739,7 @@
|
||||
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -889,6 +886,16 @@
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
);
|
||||
MARKETING_VERSION = "$(VERSION)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||
@ -991,6 +998,8 @@
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
);
|
||||
GCC_OPTIMIZATION_LEVEL = z;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -1006,7 +1015,7 @@
|
||||
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -1153,6 +1162,16 @@
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
);
|
||||
MARKETING_VERSION = "$(VERSION)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||
|
Binary file not shown.
@ -31,12 +31,12 @@ func SecTaskCopyValuesForEntitlements(
|
||||
|
||||
func checkAppEntitlements(_ ents: [String]) -> [String: Any] {
|
||||
guard let task = SecTaskCreateFromSelf(nil) else {
|
||||
print("Failed to create SecTask")
|
||||
// print("Failed to create SecTask")
|
||||
return [:]
|
||||
}
|
||||
|
||||
guard let entitlements = SecTaskCopyValuesForEntitlements(task, ents as CFArray, nil) else {
|
||||
print("Failed to get entitlements")
|
||||
// print("Failed to get entitlements")
|
||||
return [:]
|
||||
}
|
||||
|
||||
@ -45,12 +45,12 @@ func checkAppEntitlements(_ ents: [String]) -> [String: Any] {
|
||||
|
||||
func checkAppEntitlement(_ ent: String) -> Bool {
|
||||
guard let task = SecTaskCreateFromSelf(nil) else {
|
||||
print("Failed to create SecTask")
|
||||
// print("Failed to create SecTask")
|
||||
return false
|
||||
}
|
||||
|
||||
guard let entitlements = SecTaskCopyValueForEntitlement(task, ent as NSString, nil) else {
|
||||
print("Failed to get entitlements")
|
||||
// print("Failed to get entitlements")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ func checkMemoryPermissions(at address: UnsafeRawPointer) -> Bool {
|
||||
}
|
||||
|
||||
if result != KERN_SUCCESS {
|
||||
print("Failed to reach \(address)")
|
||||
// print("Failed to reach \(address)")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ func enableJITEB() {
|
||||
|
||||
func enableJITEBRequest() {
|
||||
let pid = Int(getpid())
|
||||
print(pid)
|
||||
// print(pid)
|
||||
|
||||
let address = URL(string: "http://[fd00::]:9172/attach/\(pid)")!
|
||||
var request = URLRequest(url: address)
|
||||
@ -90,7 +90,7 @@ func pingSite(host: String = "http://[fd00::]:9172/hello", completion: @escaping
|
||||
|
||||
let task = session.dataTask(with: request) { _, response, error in
|
||||
if let error = error {
|
||||
print("Ping failed: \(error.localizedDescription)")
|
||||
// print("Ping failed: \(error.localizedDescription)")
|
||||
completion(false)
|
||||
} else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
|
||||
completion(true)
|
||||
@ -140,12 +140,12 @@ func showLaunchAppAlert(jsonData: Data, in viewController: UIViewController) {
|
||||
viewController.present(alert, animated: true)
|
||||
}
|
||||
} else {
|
||||
print("Hopefully JIT is enabled now...")
|
||||
// print("Hopefully JIT is enabled now...")
|
||||
Ryujinx.shared.ryuIsJITEnabled()
|
||||
}
|
||||
|
||||
} catch {
|
||||
print(String(data: jsonData, encoding: .utf8))
|
||||
// print(String(data: jsonData, encoding: .utf8))
|
||||
let alert = UIAlertController(title: "Decoding Error", message: error.localizedDescription, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .default))
|
||||
|
||||
|
@ -13,24 +13,7 @@ func enableJITStik() {
|
||||
let bundleid = Bundle.main.bundleIdentifier ?? "Unknown"
|
||||
|
||||
let address = URL(string: "stikjit://enable-jit?bundle-id=\(bundleid)")!
|
||||
var request = URLRequest(url: address)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
if let error = error {
|
||||
presentAlert(title: "Request Error", message: error.localizedDescription)
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if let data = data, let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
|
||||
// JIT, wow
|
||||
} else {
|
||||
fatalError("Unable to get Window")
|
||||
}
|
||||
}
|
||||
if UIApplication.shared.canOpenURL(address) {
|
||||
UIApplication.shared.open(address)
|
||||
}
|
||||
|
||||
task.resume()
|
||||
}
|
||||
|
@ -49,50 +49,50 @@ class NativeController: Hashable {
|
||||
// Update joystick state here
|
||||
},
|
||||
SetPlayerIndex: { userdata, playerIndex in
|
||||
print("Player index set to \(playerIndex)")
|
||||
// print("Player index set to \(playerIndex)")
|
||||
},
|
||||
Rumble: { userdata, lowFreq, highFreq in
|
||||
print("Rumble with \(lowFreq), \(highFreq)")
|
||||
// print("Rumble with \(lowFreq), \(highFreq)")
|
||||
guard let userdata else { return 0 }
|
||||
let _self = Unmanaged<NativeController>.fromOpaque(userdata).takeUnretainedValue()
|
||||
VirtualController.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq), engine: _self.controllerHaptics)
|
||||
return 0
|
||||
},
|
||||
RumbleTriggers: { userdata, leftRumble, rightRumble in
|
||||
print("Trigger rumble with \(leftRumble), \(rightRumble)")
|
||||
// print("Trigger rumble with \(leftRumble), \(rightRumble)")
|
||||
return 0
|
||||
},
|
||||
SetLED: { userdata, red, green, blue in
|
||||
print("Set LED to RGB(\(red), \(green), \(blue))")
|
||||
// print("Set LED to RGB(\(red), \(green), \(blue))")
|
||||
return 0
|
||||
},
|
||||
SendEffect: { userdata, data, size in
|
||||
print("Effect sent with size \(size)")
|
||||
// print("Effect sent with size \(size)")
|
||||
return 0
|
||||
}
|
||||
)
|
||||
|
||||
instanceID = SDL_JoystickAttachVirtualEx(&joystickDesc)// SDL_JoystickAttachVirtual(SDL_JoystickType(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue), 6, 15, 1)
|
||||
if instanceID < 0 {
|
||||
print("Failed to create virtual joystick: \(String(cString: SDL_GetError()))")
|
||||
// print("Failed to create virtual joystick: \(String(cString: SDL_GetError()))")
|
||||
return
|
||||
}
|
||||
|
||||
controller = SDL_GameControllerOpen(Int32(instanceID))
|
||||
|
||||
if controller == nil {
|
||||
print("Failed to create virtual controller: \(String(cString: SDL_GetError()))")
|
||||
// print("Failed to create virtual controller: \(String(cString: SDL_GetError()))")
|
||||
return
|
||||
}
|
||||
|
||||
if #available(iOS 16, *) {
|
||||
guard let gamepad = nativeController.extendedGamepad
|
||||
else { return }
|
||||
|
||||
setupButtonChangeListener(gamepad.buttonA, for: .A)
|
||||
setupButtonChangeListener(gamepad.buttonB, for: .B)
|
||||
setupButtonChangeListener(gamepad.buttonX, for: .X)
|
||||
setupButtonChangeListener(gamepad.buttonY, for: .Y)
|
||||
|
||||
setupButtonChangeListener(gamepad.buttonA, for: UserDefaults.standard.bool(forKey: "swapBandA") ? .B : .A)
|
||||
setupButtonChangeListener(gamepad.buttonB, for: UserDefaults.standard.bool(forKey: "swapBandA") ? .A : .B)
|
||||
setupButtonChangeListener(gamepad.buttonX, for: UserDefaults.standard.bool(forKey: "swapBandA") ? .Y : .X)
|
||||
setupButtonChangeListener(gamepad.buttonY, for: UserDefaults.standard.bool(forKey: "swapBandA") ? .X : .Y)
|
||||
|
||||
setupButtonChangeListener(gamepad.dpad.up, for: .dPadUp)
|
||||
setupButtonChangeListener(gamepad.dpad.down, for: .dPadDown)
|
||||
@ -139,7 +139,7 @@ class NativeController: Hashable {
|
||||
|
||||
func setupTriggerChangeListener(_ button: GCControllerButtonInput, for key: ThumbstickType) {
|
||||
button.valueChangedHandler = { [unowned self] _, value, pressed in
|
||||
// print("Value: \(value), Is pressed: \(pressed)")
|
||||
// // print("Value: \(value), Is pressed: \(pressed)")
|
||||
let axis: SDL_GameControllerAxis = (key == .left) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
||||
let scaledValue = Sint16(value * 32767.0)
|
||||
updateAxisValue(value: scaledValue, forAxis: axis)
|
||||
@ -177,7 +177,7 @@ class NativeController: Hashable {
|
||||
try highFreqPlayer.start(atTime: 0.2)
|
||||
|
||||
} catch {
|
||||
print("Error creating haptic patterns: \(error)")
|
||||
// print("Error creating haptic patterns: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,7 +206,7 @@ class NativeController: Hashable {
|
||||
func setButtonState(_ state: Uint8, for button: VirtualControllerButton) {
|
||||
guard controller != nil else { return }
|
||||
|
||||
// print("Button: \(button.rawValue) {state: \(state)}")
|
||||
// // print("Button: \(button.rawValue) {state: \(state)}")
|
||||
if (button == .leftTrigger || button == .rightTrigger) && (state == 1 || state == 0) {
|
||||
let axis: SDL_GameControllerAxis = (button == .leftTrigger) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
||||
let value: Int = (state == 1) ? 32767 : 0
|
||||
|
@ -41,39 +41,39 @@ class VirtualController {
|
||||
// Update joystick state here
|
||||
},
|
||||
SetPlayerIndex: { userdata, playerIndex in
|
||||
print("Player index set to \(playerIndex)")
|
||||
// print("Player index set to \(playerIndex)")
|
||||
},
|
||||
Rumble: { userdata, lowFreq, highFreq in
|
||||
print("Rumble with \(lowFreq), \(highFreq)")
|
||||
// print("Rumble with \(lowFreq), \(highFreq)")
|
||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||
VirtualController.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq))
|
||||
}
|
||||
return 0
|
||||
},
|
||||
RumbleTriggers: { userdata, leftRumble, rightRumble in
|
||||
print("Trigger rumble with \(leftRumble), \(rightRumble)")
|
||||
// print("Trigger rumble with \(leftRumble), \(rightRumble)")
|
||||
return 0
|
||||
},
|
||||
SetLED: { userdata, red, green, blue in
|
||||
print("Set LED to RGB(\(red), \(green), \(blue))")
|
||||
// print("Set LED to RGB(\(red), \(green), \(blue))")
|
||||
return 0
|
||||
},
|
||||
SendEffect: { userdata, data, size in
|
||||
print("Effect sent with size \(size)")
|
||||
// print("Effect sent with size \(size)")
|
||||
return 0
|
||||
}
|
||||
)
|
||||
|
||||
instanceID = SDL_JoystickAttachVirtualEx(&joystickDesc)// SDL_JoystickAttachVirtual(SDL_JoystickType(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue), 6, 15, 1)
|
||||
if instanceID < 0 {
|
||||
print("Failed to create virtual joystick: \(String(cString: SDL_GetError()))")
|
||||
// print("Failed to create virtual joystick: \(String(cString: SDL_GetError()))")
|
||||
return
|
||||
}
|
||||
|
||||
controller = SDL_GameControllerOpen(Int32(instanceID))
|
||||
|
||||
if controller == nil {
|
||||
print("Failed to create virtual controller: \(String(cString: SDL_GetError()))")
|
||||
// print("Failed to create virtual controller: \(String(cString: SDL_GetError()))")
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -107,7 +107,7 @@ class VirtualController {
|
||||
}
|
||||
|
||||
guard let engine else {
|
||||
return print("Error creating haptic patterns: hapticEngine is nil")
|
||||
return // print("Error creating haptic patterns: hapticEngine is nil")
|
||||
}
|
||||
|
||||
let lowFreqPlayer = try engine.makePlayer(with: lowFreqPattern)
|
||||
@ -117,7 +117,7 @@ class VirtualController {
|
||||
try highFreqPlayer.start(atTime: 0)
|
||||
|
||||
} catch {
|
||||
print("Error creating haptic patterns: \(error)")
|
||||
// print("Error creating haptic patterns: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,7 +146,7 @@ class VirtualController {
|
||||
func setButtonState(_ state: Uint8, for button: VirtualControllerButton) {
|
||||
guard controller != nil else { return }
|
||||
|
||||
print("Button: \(button.rawValue) {state: \(state)}")
|
||||
// // print("Button: \(button.rawValue) {state: \(state)}")
|
||||
if (button == .leftTrigger || button == .rightTrigger) && (state == 1 || state == 0) {
|
||||
let axis: SDL_GameControllerAxis = (button == .leftTrigger) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
||||
let value: Int = (state == 1) ? 32767 : 0
|
||||
|
@ -35,8 +35,8 @@ class MemoryUsageMonitor: ObservableObject {
|
||||
memoryUsage = taskInfo.phys_footprint
|
||||
}
|
||||
else {
|
||||
print("Error with task_info(): " +
|
||||
(String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
// print("Error with task_info(): " +
|
||||
// (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ class MTLHud {
|
||||
}
|
||||
|
||||
func toggle() {
|
||||
print(UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED"))
|
||||
// print(UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED"))
|
||||
if UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED") {
|
||||
enable()
|
||||
} else {
|
||||
@ -44,12 +44,12 @@ class MTLHud {
|
||||
let path = "/usr/lib/libMTLHud.dylib"
|
||||
|
||||
if dlopen(path, RTLD_NOW) != nil {
|
||||
print("Library loaded from \(path)")
|
||||
// print("Library loaded from \(path)")
|
||||
canMetalHud = true
|
||||
return true
|
||||
} else {
|
||||
if let error = String(validatingUTF8: dlerror()) {
|
||||
print("Error loading library: \(error)")
|
||||
// print("Error loading library: \(error)")
|
||||
}
|
||||
canMetalHud = false
|
||||
return false
|
||||
|
@ -11,6 +11,93 @@ import GameController
|
||||
import MetalKit
|
||||
import Metal
|
||||
|
||||
class LogCapture {
|
||||
static let shared = LogCapture()
|
||||
|
||||
private var stdoutPipe: Pipe?
|
||||
private var stderrPipe: Pipe?
|
||||
private let originalStdout: Int32
|
||||
private let originalStderr: Int32
|
||||
|
||||
var capturedLogs: [String] = [] {
|
||||
didSet {
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: .newLogCaptured, object: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private init() {
|
||||
originalStdout = dup(STDOUT_FILENO)
|
||||
originalStderr = dup(STDERR_FILENO)
|
||||
startCapturing()
|
||||
}
|
||||
|
||||
func startCapturing() {
|
||||
stdoutPipe = Pipe()
|
||||
stderrPipe = Pipe()
|
||||
|
||||
redirectOutput(to: stdoutPipe!, fileDescriptor: STDOUT_FILENO)
|
||||
redirectOutput(to: stderrPipe!, fileDescriptor: STDERR_FILENO)
|
||||
|
||||
setupReadabilityHandler(for: stdoutPipe!, isStdout: true)
|
||||
setupReadabilityHandler(for: stderrPipe!, isStdout: false)
|
||||
}
|
||||
|
||||
func stopCapturing() {
|
||||
dup2(originalStdout, STDOUT_FILENO)
|
||||
dup2(originalStderr, STDERR_FILENO)
|
||||
|
||||
stdoutPipe?.fileHandleForReading.readabilityHandler = nil
|
||||
stderrPipe?.fileHandleForReading.readabilityHandler = nil
|
||||
}
|
||||
|
||||
private func redirectOutput(to pipe: Pipe, fileDescriptor: Int32) {
|
||||
dup2(pipe.fileHandleForWriting.fileDescriptor, fileDescriptor)
|
||||
}
|
||||
|
||||
private func setupReadabilityHandler(for pipe: Pipe, isStdout: Bool) {
|
||||
pipe.fileHandleForReading.readabilityHandler = { [weak self] fileHandle in
|
||||
let data = fileHandle.availableData
|
||||
let originalFD = isStdout ? self?.originalStdout : self?.originalStderr
|
||||
write(originalFD ?? STDOUT_FILENO, (data as NSData).bytes, data.count)
|
||||
|
||||
if let logString = String(data: data, encoding: .utf8),
|
||||
let cleanedLog = self?.cleanLog(logString), !cleanedLog.isEmpty {
|
||||
self?.capturedLogs.append(cleanedLog)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func cleanLog(_ raw: String) -> String? {
|
||||
let lines = raw.split(separator: "\n")
|
||||
let filteredLines = lines.filter { line in
|
||||
!line.contains("SwiftUI") &&
|
||||
!line.contains("ForEach") &&
|
||||
!line.contains("VStack") &&
|
||||
!line.contains("Invalid frame dimension (negative or non-finite).")
|
||||
}
|
||||
|
||||
let cleaned = filteredLines.map { line -> String in
|
||||
if let tabRange = line.range(of: "\t") {
|
||||
return line[tabRange.upperBound...].trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
return line.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}.joined(separator: "\n")
|
||||
|
||||
return cleaned.isEmpty ? nil : cleaned.replacingOccurrences(of: "\n\n", with: "\n")
|
||||
}
|
||||
|
||||
deinit {
|
||||
stopCapturing()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Notification.Name {
|
||||
static let newLogCaptured = Notification.Name("newLogCaptured")
|
||||
}
|
||||
|
||||
struct Controller: Identifiable, Hashable {
|
||||
var id: String
|
||||
var name: String
|
||||
@ -46,7 +133,7 @@ class Ryujinx : ObservableObject {
|
||||
|
||||
@Published var defMLContentSize: CGFloat?
|
||||
|
||||
var thread: Thread!
|
||||
var thread: Thread = Thread { }
|
||||
|
||||
@Published var jitenabled = false
|
||||
|
||||
@ -150,6 +237,7 @@ class Ryujinx : ObservableObject {
|
||||
|
||||
self.config = config
|
||||
|
||||
|
||||
thread = Thread { [self] in
|
||||
|
||||
isRunning = true
|
||||
@ -180,7 +268,35 @@ class Ryujinx : ObservableObject {
|
||||
}
|
||||
} catch {
|
||||
self.isRunning = false
|
||||
Self.log("Emulation failed to start: \(error)")
|
||||
Thread.sleep(forTimeInterval: 0.3)
|
||||
let logs = LogCapture.shared.capturedLogs
|
||||
let parsedLogs = extractExceptionInfo(logs)
|
||||
if let parsedLogs {
|
||||
DispatchQueue.main.async {
|
||||
let result = Array(logs.suffix(from: parsedLogs.lineIndex))
|
||||
|
||||
LogCapture.shared.capturedLogs = Array(LogCapture.shared.capturedLogs.prefix(upTo: parsedLogs.lineIndex))
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd_HH-mm-ss"
|
||||
let currentDate = Date()
|
||||
let dateString = dateFormatter.string(from: currentDate)
|
||||
let path = URL.documentsDirectory.appendingPathComponent("StackTrace").appendingPathComponent("StackTrace-\(dateString).txt").path
|
||||
|
||||
self.saveArrayAsTextFile(strings: result, filePath: path)
|
||||
|
||||
|
||||
presentAlert(title: "MeloNX Crashed!", message: parsedLogs.exceptionType + ": " + parsedLogs.message) {
|
||||
|
||||
assert(true, parsedLogs.exceptionType)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
presentAlert(title: "MeloNX Crashed!", message: "Unknown Error") {
|
||||
assert(true, "Exception was not detected")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,8 +304,63 @@ class Ryujinx : ObservableObject {
|
||||
thread.name = "MeloNX"
|
||||
thread.start()
|
||||
}
|
||||
|
||||
func saveArrayAsTextFile(strings: [String], filePath: String) {
|
||||
let text = strings.joined(separator: "\n")
|
||||
|
||||
let path = URL.documentsDirectory.appendingPathComponent("StackTrace").path
|
||||
|
||||
do {
|
||||
try FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: false)
|
||||
} catch {
|
||||
|
||||
}
|
||||
|
||||
do {
|
||||
try text.write(to: URL(fileURLWithPath: filePath), atomically: true, encoding: .utf8)
|
||||
print("File saved successfully.")
|
||||
} catch {
|
||||
print("Error saving file: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
struct ExceptionInfo {
|
||||
let exceptionType: String
|
||||
let message: String
|
||||
let lineIndex: Int
|
||||
}
|
||||
|
||||
func extractExceptionInfo(_ logs: [String]) -> ExceptionInfo? {
|
||||
for i in (0..<logs.count).reversed() {
|
||||
let line = logs[i]
|
||||
let pattern = "([\\w\\.]+Exception): ([^\\s]+(?:\\s+[^\\s]+)*)"
|
||||
|
||||
guard let regex = try? NSRegularExpression(pattern: pattern, options: []),
|
||||
let match = regex.firstMatch(in: line, options: [], range: NSRange(location: 0, length: line.count)) else {
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract exception type and message if pattern matches
|
||||
if let exceptionTypeRange = Range(match.range(at: 1), in: line),
|
||||
let messageRange = Range(match.range(at: 2), in: line) {
|
||||
|
||||
let exceptionType = String(line[exceptionTypeRange])
|
||||
|
||||
var message = String(line[messageRange])
|
||||
if let atIndex = message.range(of: "\\s+at\\s+", options: .regularExpression) {
|
||||
message = String(message[..<atIndex.lowerBound])
|
||||
}
|
||||
|
||||
message = message.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
return ExceptionInfo(exceptionType: exceptionType, message: message, lineIndex: i)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func stop() throws {
|
||||
guard isRunning else {
|
||||
throw RyujinxError.notRunning
|
||||
@ -219,7 +390,7 @@ class Ryujinx : ObservableObject {
|
||||
do {
|
||||
try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||
} catch {
|
||||
print("Failed to create roms directory: \(error)")
|
||||
// print("Failed to create roms directory: \(error)")
|
||||
}
|
||||
}
|
||||
var games: [Game] = []
|
||||
@ -244,13 +415,13 @@ class Ryujinx : ObservableObject {
|
||||
|
||||
games.append(game)
|
||||
} catch {
|
||||
print(error)
|
||||
// print(error)
|
||||
}
|
||||
}
|
||||
|
||||
return games
|
||||
} catch {
|
||||
print("Error loading games from roms folder: \(error)")
|
||||
// print("Error loading games from roms folder: \(error)")
|
||||
return games
|
||||
}
|
||||
|
||||
@ -402,7 +573,7 @@ class Ryujinx : ObservableObject {
|
||||
|
||||
func installFirmware(firmwarePath: String) {
|
||||
guard let cString = firmwarePath.cString(using: .utf8) else {
|
||||
print("Invalid firmware path")
|
||||
// print("Invalid firmware path")
|
||||
return
|
||||
}
|
||||
|
||||
@ -418,12 +589,12 @@ class Ryujinx : ObservableObject {
|
||||
guard let titleIdCString = titleId.cString(using: .utf8),
|
||||
let pathCString = path.cString(using: .utf8)
|
||||
else {
|
||||
print("Invalid path")
|
||||
// print("Invalid path")
|
||||
return []
|
||||
}
|
||||
|
||||
let listPointer = get_dlc_nca_list(titleIdCString, pathCString)
|
||||
print("DLC parcing success: \(listPointer.success)")
|
||||
// print("DLC parcing success: \(listPointer.success)")
|
||||
guard listPointer.success else { return [] }
|
||||
|
||||
let list = Array(UnsafeBufferPointer(start: listPointer.items, count: Int(listPointer.size)))
|
||||
@ -475,7 +646,7 @@ class Ryujinx : ObservableObject {
|
||||
let guid = generateGamepadId(joystickIndex: i)
|
||||
let name = String(cString: SDL_GameControllerName(controller))
|
||||
|
||||
print("Controller \(i): \(name), GUID: \(guid ?? "")")
|
||||
// print("Controller \(i): \(name), GUID: \(guid ?? "")")
|
||||
|
||||
guard let guid else {
|
||||
SDL_GameControllerClose(controller)
|
||||
@ -506,27 +677,27 @@ class Ryujinx : ObservableObject {
|
||||
do {
|
||||
if fileManager.fileExists(atPath: registeredFolder) {
|
||||
try fileManager.removeItem(atPath: registeredFolder)
|
||||
print("Folder removed successfully.")
|
||||
// print("Folder removed successfully.")
|
||||
let version = fetchFirmwareVersion()
|
||||
|
||||
if version.isEmpty {
|
||||
self.firmwareversion = "0"
|
||||
} else {
|
||||
print("Firmware eeeeee \(version)")
|
||||
// print("Firmware eeeeee \(version)")
|
||||
}
|
||||
|
||||
} else {
|
||||
print("Folder does not exist.")
|
||||
// print("Folder does not exist.")
|
||||
}
|
||||
} catch {
|
||||
print("Error removing folder: \(error)")
|
||||
// print("Error removing folder: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static func log(_ message: String) {
|
||||
print("[Ryujinx] \(message)")
|
||||
// print("[Ryujinx] \(message)")
|
||||
}
|
||||
|
||||
public func updateOrientation() -> Bool {
|
||||
|
@ -35,7 +35,7 @@ struct LaunchGameIntentDef: AppIntent {
|
||||
let name = findClosestGameName(input: gameName, games: ryujinx.compactMap(\.titleName))
|
||||
|
||||
let urlString = "melonx://game?name=\(name ?? gameName)"
|
||||
print(urlString)
|
||||
// print(urlString)
|
||||
if let url = URL(string: urlString) {
|
||||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ public struct Game: Identifiable, Equatable, Hashable {
|
||||
|
||||
gameTemp.icon = UIImage(data: imageData)
|
||||
} else {
|
||||
print("Invalid image size.")
|
||||
// print("Invalid image size.")
|
||||
}
|
||||
return gameTemp
|
||||
}
|
||||
@ -67,7 +67,7 @@ public struct Game: Identifiable, Equatable, Hashable {
|
||||
|
||||
let imageSize = Int(gameInfoValue.ImageSize)
|
||||
guard imageSize > 0, imageSize <= 1024 * 1024 else {
|
||||
print("Invalid image size.")
|
||||
// print("Invalid image size.")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ struct ContentView: View {
|
||||
|
||||
_settings = State(initialValue: defaultSettings)
|
||||
|
||||
print(SDL_CONTROLLER_BUTTON_LEFTSTICK.rawValue)
|
||||
// print(SDL_CONTROLLER_BUTTON_LEFTSTICK.rawValue)
|
||||
|
||||
initializeSDL()
|
||||
}
|
||||
@ -120,7 +120,7 @@ struct ContentView: View {
|
||||
|
||||
private var jitErrorView: some View {
|
||||
Text("")
|
||||
.sheet(isPresented:Binding(
|
||||
.fullScreenCover(isPresented:Binding(
|
||||
get: { !ryujinx.jitenabled },
|
||||
set: { newValue in
|
||||
ryujinx.jitenabled = newValue
|
||||
@ -131,7 +131,7 @@ struct ContentView: View {
|
||||
JITPopover() {
|
||||
ryujinx.jitenabled = false
|
||||
}
|
||||
.interactiveDismissDisabled()
|
||||
// .interactiveDismissDisabled()
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,7 +154,7 @@ struct ContentView: View {
|
||||
}
|
||||
|
||||
|
||||
print(MTLHud.shared.isEnabled)
|
||||
// print(MTLHud.shared.isEnabled)
|
||||
|
||||
initControllerObservers()
|
||||
|
||||
@ -289,7 +289,7 @@ struct ContentView: View {
|
||||
queue: .main
|
||||
) { notification in
|
||||
if let controller = notification.object as? GCController {
|
||||
print("Controller connected: \(controller.productCategory)")
|
||||
// print("Controller connected: \(controller.productCategory)")
|
||||
nativeControllers[controller] = .init(controller)
|
||||
refreshControllersList()
|
||||
}
|
||||
@ -301,7 +301,7 @@ struct ContentView: View {
|
||||
queue: .main
|
||||
) { notification in
|
||||
if let controller = notification.object as? GCController {
|
||||
print("Controller disconnected: \(controller.productCategory)")
|
||||
// print("Controller disconnected: \(controller.productCategory)")
|
||||
nativeControllers[controller]?.cleanup()
|
||||
nativeControllers[controller] = nil
|
||||
refreshControllersList()
|
||||
@ -355,7 +355,7 @@ struct ContentView: View {
|
||||
do {
|
||||
try ryujinx.start(with: config)
|
||||
} catch {
|
||||
print("Error: \(error.localizedDescription)")
|
||||
// print("Error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
@ -366,7 +366,7 @@ struct ContentView: View {
|
||||
}
|
||||
|
||||
if syncqsubmits {
|
||||
setenv("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", "2", 1)
|
||||
setenv("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", "1", 1)
|
||||
}
|
||||
}
|
||||
|
||||
@ -389,7 +389,7 @@ struct ContentView: View {
|
||||
} else if jitStreamerEB {
|
||||
enableJITEB()
|
||||
} else {
|
||||
print("no JIT")
|
||||
// print("no JIT")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -305,7 +305,6 @@ struct ButtonView: View {
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
print(String(buttonText.dropFirst(2)))
|
||||
configureSizeForButton()
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ class Haptics {
|
||||
private init() { }
|
||||
|
||||
func play(_ feedbackStyle: UIImpactFeedbackGenerator.FeedbackStyle) {
|
||||
print("haptics")
|
||||
UIImpactFeedbackGenerator(style: feedbackStyle).impactOccurred()
|
||||
}
|
||||
|
||||
|
@ -61,8 +61,7 @@ struct Joystick: View {
|
||||
Circle()
|
||||
.fill(Color.gray.opacity(0.4))
|
||||
.frame(width: boundarySize, height: boundarySize)
|
||||
.animation(.easeInOut(duration: 0.05), value: showBackground)
|
||||
.transition(.scale)
|
||||
.animation(.easeInOut(duration: 0.1), value: showBackground)
|
||||
}
|
||||
|
||||
Circle()
|
||||
|
@ -30,7 +30,7 @@ struct JoystickController: View {
|
||||
.onChange(of: position) { newValue in
|
||||
let scaledX = Float(newValue.x)
|
||||
let scaledY = Float(newValue.y) // my dumbass broke this by having -y instead of y :/
|
||||
print("Joystick Position: (\(scaledX), \(scaledY))")
|
||||
// print("Joystick Position: (\(scaledX), \(scaledY))")
|
||||
|
||||
if iscool != nil {
|
||||
Ryujinx.shared.virtualController.thumbstickMoved(.right, x: newValue.x, y: newValue.y)
|
||||
|
@ -58,7 +58,7 @@ public class Air {
|
||||
}
|
||||
|
||||
@objc func didConnect(sender: NSNotification) {
|
||||
print("AirKit - Connect")
|
||||
// print("AirKit - Connect")
|
||||
self.connected = true
|
||||
guard let screen: UIScreen = sender.object as? UIScreen else { return }
|
||||
add(screen: screen) { success in
|
||||
@ -69,35 +69,35 @@ public class Air {
|
||||
|
||||
func add(screen: UIScreen, completion: @escaping (Bool) -> ()) {
|
||||
|
||||
print("AirKit - Add Screen")
|
||||
// print("AirKit - Add Screen")
|
||||
|
||||
airScreen = screen
|
||||
|
||||
airWindow = UIWindow(frame: airScreen!.bounds)
|
||||
|
||||
guard let viewController: UIViewController = hostingController else {
|
||||
print("AirKit - Add - Failed: Hosting Controller Not Found")
|
||||
// print("AirKit - Add - Failed: Hosting Controller Not Found")
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
findWindowScene(for: airScreen!) { windowScene in
|
||||
guard let airWindowScene: UIWindowScene = windowScene else {
|
||||
print("AirKit - Add - Failed: Window Scene Not Found")
|
||||
// print("AirKit - Add - Failed: Window Scene Not Found")
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
self.airWindow?.rootViewController = viewController
|
||||
self.airWindow?.windowScene = airWindowScene
|
||||
self.airWindow?.isHidden = false
|
||||
print("AirKit - Add Screen - Done")
|
||||
// print("AirKit - Add Screen - Done")
|
||||
completion(true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func findWindowScene(for screen: UIScreen, shouldRecurse: Bool = true, completion: @escaping (UIWindowScene?) -> ()) {
|
||||
print("AirKit - Find Window Scene")
|
||||
// print("AirKit - Find Window Scene")
|
||||
var matchingWindowScene: UIWindowScene? = nil
|
||||
let scenes = UIApplication.shared.connectedScenes
|
||||
for scene in scenes {
|
||||
@ -120,23 +120,23 @@ public class Air {
|
||||
}
|
||||
|
||||
@objc func didDisconnect() {
|
||||
print("AirKit - Disconnect")
|
||||
// print("AirKit - Disconnect")
|
||||
remove()
|
||||
connected = false
|
||||
}
|
||||
|
||||
func remove() {
|
||||
print("AirKit - Remove")
|
||||
// print("AirKit - Remove")
|
||||
airWindow = nil
|
||||
airScreen = nil
|
||||
}
|
||||
|
||||
@objc func didBecomeActive() {
|
||||
print("AirKit - App Active")
|
||||
// print("AirKit - App Active")
|
||||
}
|
||||
|
||||
@objc func willResignActive() {
|
||||
print("AirKit - App Inactive")
|
||||
// print("AirKit - App Inactive")
|
||||
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import SwiftUI
|
||||
public extension View {
|
||||
|
||||
func airPlay() -> some View {
|
||||
print("AirKit - airPlay")
|
||||
// print("AirKit - airPlay")
|
||||
Air.play(AnyView(self))
|
||||
return self
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ struct EmulationView: View {
|
||||
Air.shared.connectionCallbacks.append { cool in
|
||||
DispatchQueue.main.async {
|
||||
isAirplaying = cool
|
||||
print(cool)
|
||||
// print(cool)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ class MeloMTKView: MTKView {
|
||||
let index = activeTouches.firstIndex(of: touch)!
|
||||
|
||||
let scaledLocation = scaleToTargetResolution(location)!
|
||||
print("Touch began at: \(scaledLocation) and \(self.aspectRatio)")
|
||||
// // print("Touch began at: \(scaledLocation) and \(self.aspectRatio)")
|
||||
touch_began(Float(scaledLocation.x), Float(scaledLocation.y), Int32(index))
|
||||
}
|
||||
}
|
||||
@ -119,7 +119,7 @@ class MeloMTKView: MTKView {
|
||||
if let index = activeTouches.firstIndex(of: touch) {
|
||||
activeTouches.remove(at: index)
|
||||
|
||||
print("Touch ended for index \(index)")
|
||||
// // print("Touch ended for index \(index)")
|
||||
touch_ended(Int32(index))
|
||||
}
|
||||
}
|
||||
@ -139,14 +139,14 @@ class MeloMTKView: MTKView {
|
||||
guard let scaledLocation = scaleToTargetResolution(location) else {
|
||||
if let index = activeTouches.firstIndex(of: touch) {
|
||||
activeTouches.remove(at: index)
|
||||
print("Touch left active area, removed index \(index)")
|
||||
// // print("Touch left active area, removed index \(index)")
|
||||
touch_ended(Int32(index))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if let index = activeTouches.firstIndex(of: touch) {
|
||||
print("Touch moved to: \(scaledLocation)")
|
||||
// // print("Touch moved to: \(scaledLocation)")
|
||||
touch_moved(Float(scaledLocation.x), Float(scaledLocation.y), Int32(index))
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import SwiftUI
|
||||
struct GameInfoSheet: View {
|
||||
let game: Game
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
var body: some View {
|
||||
iOSNav {
|
||||
@ -44,7 +44,7 @@ struct GameInfoSheet: View {
|
||||
.multilineTextAlignment(.center)
|
||||
Text(game.developer)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding(.vertical, 3)
|
||||
}
|
||||
@ -56,7 +56,7 @@ struct GameInfoSheet: View {
|
||||
Text("**Version**")
|
||||
Spacer()
|
||||
Text(game.version)
|
||||
.foregroundStyle(Color.secondary)
|
||||
.foregroundColor(Color.secondary)
|
||||
}
|
||||
HStack {
|
||||
Text("**Title ID**")
|
||||
@ -69,36 +69,36 @@ struct GameInfoSheet: View {
|
||||
}
|
||||
Spacer()
|
||||
Text(game.titleId)
|
||||
.foregroundStyle(Color.secondary)
|
||||
.foregroundColor(Color.secondary)
|
||||
}
|
||||
HStack {
|
||||
Text("**Game Size**")
|
||||
Spacer()
|
||||
Text("\(fetchFileSize(for: game.fileURL) ?? 0) bytes")
|
||||
.foregroundStyle(Color.secondary)
|
||||
.foregroundColor(Color.secondary)
|
||||
}
|
||||
HStack {
|
||||
Text("**File Type**")
|
||||
Spacer()
|
||||
Text(getFileType(game.fileURL))
|
||||
.foregroundStyle(Color.secondary)
|
||||
.foregroundColor(Color.secondary)
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("**Game URL**")
|
||||
Text(trimGameURL(game.fileURL))
|
||||
.foregroundStyle(Color.secondary)
|
||||
.foregroundColor(Color.secondary)
|
||||
}
|
||||
} header: {
|
||||
Text("Information")
|
||||
}
|
||||
.headerProminence(.increased)
|
||||
// .headerProminence(.increased)
|
||||
}
|
||||
.navigationTitle(game.titleName)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Done") {
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -113,7 +113,7 @@ struct GameInfoSheet: View {
|
||||
return size
|
||||
}
|
||||
} catch {
|
||||
print("Error getting file size: \(error)")
|
||||
// print("Error getting file size: \(error)")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@ import SwiftUI
|
||||
|
||||
struct JITPopover: View {
|
||||
var onJITEnabled: () -> Void
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
@State var isJIT: Bool = false
|
||||
var body: some View {
|
||||
@ -35,7 +35,7 @@ struct JITPopover: View {
|
||||
|
||||
|
||||
if isJIT {
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
onJITEnabled()
|
||||
|
||||
Ryujinx.shared.ryuIsJITEnabled()
|
||||
|
@ -6,25 +6,20 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
struct LogFileView: View {
|
||||
@State private var logs: [String] = []
|
||||
@StateObject var logsModel = LogViewModel()
|
||||
@State private var showingLogs = false
|
||||
|
||||
public var isfps: Bool
|
||||
|
||||
private let fileManager = FileManager.default
|
||||
private let maxDisplayLines = 10
|
||||
|
||||
private var dateFormatter: DateFormatter {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyy-MM-dd_HH-mm-ss"
|
||||
return formatter
|
||||
}
|
||||
private let maxDisplayLines = 4
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
ForEach(logs.suffix(maxDisplayLines), id: \.self) { log in
|
||||
ForEach(logsModel.logs.suffix(maxDisplayLines), id: \.self) { log in
|
||||
Text(log)
|
||||
.font(.caption)
|
||||
.foregroundColor(.white)
|
||||
@ -34,85 +29,38 @@ struct LogFileView: View {
|
||||
.transition(.opacity)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
startLogFileWatching()
|
||||
}
|
||||
.onChange(of: logs) { newLogs in
|
||||
print("Logs updated: \(newLogs.count) entries")
|
||||
}
|
||||
}
|
||||
|
||||
private func getLatestLogFile() -> URL? {
|
||||
let logsDirectory = URL.documentsDirectory.appendingPathComponent("Logs")
|
||||
let currentDate = Date()
|
||||
|
||||
do {
|
||||
try fileManager.createDirectory(at: logsDirectory, withIntermediateDirectories: true)
|
||||
|
||||
let logFiles = try fileManager.contentsOfDirectory(at: logsDirectory, includingPropertiesForKeys: [.creationDateKey])
|
||||
.filter {
|
||||
let filename = $0.lastPathComponent
|
||||
guard filename.hasPrefix("MeloNX_") && filename.hasSuffix(".log") else {
|
||||
return false
|
||||
}
|
||||
|
||||
let dateString = filename.replacingOccurrences(of: "MeloNX_", with: "").replacingOccurrences(of: ".log", with: "")
|
||||
guard let logDate = dateFormatter.date(from: dateString) else {
|
||||
return false
|
||||
}
|
||||
|
||||
return Calendar.current.isDate(logDate, inSameDayAs: currentDate)
|
||||
}
|
||||
|
||||
let sortedLogFiles = logFiles.sorted {
|
||||
$0.lastPathComponent > $1.lastPathComponent
|
||||
}
|
||||
|
||||
return sortedLogFiles.first
|
||||
} catch {
|
||||
print("Error finding log files: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func readLatestLogFile() {
|
||||
guard let logFileURL = getLatestLogFile() else {
|
||||
print("no logs?")
|
||||
return
|
||||
}
|
||||
print(logFileURL)
|
||||
|
||||
do {
|
||||
let logContents = try String(contentsOf: logFileURL)
|
||||
let allLines = logContents.components(separatedBy: .newlines)
|
||||
|
||||
DispatchQueue.global(qos: .userInteractive).async {
|
||||
self.logs = Array(allLines)
|
||||
}
|
||||
} catch {
|
||||
print("Error reading log file: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func startLogFileWatching() {
|
||||
showingLogs = true
|
||||
|
||||
Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { timer in
|
||||
if showingLogs {
|
||||
self.readLatestLogFile()
|
||||
}
|
||||
|
||||
if isfps {
|
||||
sleep(1)
|
||||
if get_current_fps() != 0 {
|
||||
stopLogFileWatching()
|
||||
timer.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
private func stopLogFileWatching() {
|
||||
showingLogs = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LogViewModel: ObservableObject {
|
||||
@Published var logs: [String] = []
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init() {
|
||||
_ = LogCapture.shared
|
||||
|
||||
NotificationCenter.default.publisher(for: .newLogCaptured)
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] _ in
|
||||
self?.updateLogs()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
updateLogs()
|
||||
}
|
||||
|
||||
func updateLogs() {
|
||||
logs = LogCapture.shared.capturedLogs
|
||||
}
|
||||
|
||||
func clearLogs() {
|
||||
LogCapture.shared.capturedLogs = []
|
||||
updateLogs()
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -33,18 +33,32 @@ struct MeloNXUpdateSheet: View {
|
||||
|
||||
|
||||
Spacer()
|
||||
Button(action: {
|
||||
if let url = URL(string: updateInfo.download_link) {
|
||||
UIApplication.shared.open(url)
|
||||
if #available(iOS 15.0, *) {
|
||||
Button(action: {
|
||||
if let url = URL(string: updateInfo.download_link) {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}) {
|
||||
Text("Download Now")
|
||||
.font(.title3)
|
||||
.bold()
|
||||
.frame(width: 300, height: 40)
|
||||
}
|
||||
}) {
|
||||
Text("Download Now")
|
||||
.font(.title3)
|
||||
.bold()
|
||||
.frame(width: 300, height: 40)
|
||||
.buttonStyle(.borderedProminent)
|
||||
.frame(alignment: .bottom)
|
||||
} else {
|
||||
Button(action: {
|
||||
if let url = URL(string: updateInfo.download_link) {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}) {
|
||||
Text("Download Now")
|
||||
.font(.title3)
|
||||
.bold()
|
||||
.frame(width: 300, height: 40)
|
||||
}
|
||||
.frame(alignment: .bottom)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.frame(alignment: .bottom)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.navigationTitle("Version \(updateInfo.version_number) Available!")
|
||||
|
@ -46,7 +46,7 @@ struct DLCManagerSheet: View {
|
||||
@Binding var game: Game!
|
||||
@State private var isSelectingGameDLC = false
|
||||
@State private var dlcs: [DownloadableContentContainer] = []
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
// MARK: - Body
|
||||
var body: some View {
|
||||
@ -66,7 +66,7 @@ struct DLCManagerSheet: View {
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Done") {
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,27 +127,56 @@ struct DLCManagerSheet: View {
|
||||
|
||||
|
||||
private func dlcRow(_ dlc: DownloadableContentContainer) -> some View {
|
||||
Button {
|
||||
toggleDLC(dlc)
|
||||
} label: {
|
||||
HStack {
|
||||
Text(dlc.filename)
|
||||
.foregroundStyle(.primary)
|
||||
Spacer()
|
||||
Image(systemName: dlc.isEnabled ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundStyle(dlc.isEnabled ? .primary : .secondary)
|
||||
.imageScale(.large)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button(role: .destructive) {
|
||||
if let index = dlcs.firstIndex(where: { $0.id == dlc.id }) {
|
||||
removeDLC(at: IndexSet(integer: index))
|
||||
Group {
|
||||
if #available(iOS 15.0, *) {
|
||||
Button {
|
||||
toggleDLC(dlc)
|
||||
} label: {
|
||||
HStack {
|
||||
Text(dlc.filename)
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
Image(systemName: dlc.isEnabled ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundColor(dlc.isEnabled ? .primary : .secondary)
|
||||
.imageScale(.large)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button(role: .destructive) {
|
||||
if let index = dlcs.firstIndex(where: { $0.id == dlc.id }) {
|
||||
removeDLC(at: IndexSet(integer: index))
|
||||
}
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Button {
|
||||
toggleDLC(dlc)
|
||||
} label: {
|
||||
HStack {
|
||||
Text(dlc.filename)
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
Image(systemName: dlc.isEnabled ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundColor(dlc.isEnabled ? .primary : .secondary)
|
||||
.imageScale(.large)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.contextMenu {
|
||||
Button {
|
||||
if let index = dlcs.firstIndex(where: { $0.id == dlc.id }) {
|
||||
removeDLC(at: IndexSet(integer: index))
|
||||
}
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -261,7 +290,7 @@ private extension DLCManagerSheet {
|
||||
|
||||
return result
|
||||
} catch {
|
||||
print("Error loading DLCs: \(error)")
|
||||
// print("Error loading DLCs: \(error)")
|
||||
return []
|
||||
}
|
||||
}
|
||||
@ -300,7 +329,7 @@ extension Array where Element: AnyObject {
|
||||
|
||||
// MARK: - URL Extension
|
||||
extension URL {
|
||||
@available(iOS, introduced: 15.0, deprecated: 16.0, message: "Use URL.documentsDirectory on iOS 16 and above")
|
||||
@available(iOS, introduced: 14.0, deprecated: 16.0, message: "Use URL.documentsDirectory on iOS 16 and above")
|
||||
static var documentsDirectory: URL {
|
||||
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
return documentDirectory
|
||||
|
@ -14,7 +14,7 @@ struct UpdateManagerSheet: View {
|
||||
@Binding var game: Game?
|
||||
@State private var isSelectingGameUpdate = false
|
||||
@State private var jsonURL: URL? = nil
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
// MARK: - Models
|
||||
class UpdateItem: Identifiable, ObservableObject {
|
||||
@ -51,7 +51,7 @@ struct UpdateManagerSheet: View {
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Done") {
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,15 +106,26 @@ struct UpdateManagerSheet: View {
|
||||
}
|
||||
|
||||
private func updateRow(_ update: UpdateItem) -> some View {
|
||||
Group {
|
||||
if #available(iOS 15, *) {
|
||||
updateRowNew(update)
|
||||
} else {
|
||||
updateRowOld(update)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15, *)
|
||||
private func updateRowNew(_ update: UpdateItem) -> some View {
|
||||
Button {
|
||||
toggleSelection(update)
|
||||
} label: {
|
||||
HStack {
|
||||
Text(update.filename)
|
||||
.foregroundStyle(.primary)
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
Image(systemName: update.isSelected ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundStyle(update.isSelected ? .primary : .secondary)
|
||||
.foregroundColor(update.isSelected ? .primary : .secondary)
|
||||
.imageScale(.large)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
@ -131,6 +142,31 @@ struct UpdateManagerSheet: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func updateRowOld(_ update: UpdateItem) -> some View {
|
||||
Button {
|
||||
toggleSelection(update)
|
||||
} label: {
|
||||
HStack {
|
||||
Text(update.filename)
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
Image(systemName: update.isSelected ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundColor(update.isSelected ? .primary : .secondary)
|
||||
.imageScale(.large)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.contextMenu {
|
||||
Button {
|
||||
if let index = updates.firstIndex(where: { $0.path == update.path }) {
|
||||
removeUpdate(at: IndexSet(integer: index))
|
||||
}
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Functions
|
||||
private func loadData() {
|
||||
guard let game = game else { return }
|
||||
@ -246,12 +282,12 @@ struct UpdateManagerSheet: View {
|
||||
updates = updates.map { item in
|
||||
var mutableItem = item
|
||||
mutableItem.isSelected = item.path == update.path && !update.isSelected
|
||||
print(mutableItem.isSelected)
|
||||
print(update.isSelected)
|
||||
// print(mutableItem.isSelected)
|
||||
// print(update.isSelected)
|
||||
return mutableItem
|
||||
}
|
||||
|
||||
print(updates)
|
||||
// print(updates)
|
||||
|
||||
saveJSON()
|
||||
}
|
||||
|
@ -29,13 +29,6 @@ struct MeloNXApp: App {
|
||||
@State var finished = false
|
||||
@AppStorage("hasbeenfinished") var finishedStorage: Bool = false
|
||||
|
||||
init() {
|
||||
let fixMethod = class_getInstanceMethod(UIDocumentPickerViewController.self, #selector(UIDocumentPickerViewController.fix_init(forOpeningContentTypes:asCopy:)))!
|
||||
let origMethod = class_getInstanceMethod(UIDocumentPickerViewController.self, #selector(UIDocumentPickerViewController.init(forOpeningContentTypes:asCopy:)))!
|
||||
method_exchangeImplementations(origMethod, fixMethod)
|
||||
}
|
||||
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
if finishedStorage {
|
||||
@ -80,18 +73,18 @@ struct MeloNXApp: App {
|
||||
#endif
|
||||
|
||||
guard let url = URL(string: urlString) else {
|
||||
print("Invalid URL")
|
||||
// print("Invalid URL")
|
||||
return
|
||||
}
|
||||
|
||||
let task = URLSession.shared.dataTask(with: url) { data, response, error in
|
||||
if let error = error {
|
||||
print("Error checking for new version: \(error)")
|
||||
// print("Error checking for new version: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = data else {
|
||||
print("No data received")
|
||||
// print("No data received")
|
||||
return
|
||||
}
|
||||
|
||||
@ -106,7 +99,7 @@ struct MeloNXApp: App {
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("Failed to decode response: \(error)")
|
||||
// print("Failed to decode response: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,12 +54,17 @@ struct SetupView: View {
|
||||
) { result in
|
||||
handleFirmwareImport(result: result)
|
||||
}
|
||||
.alert(alertMessage, isPresented: $showAlert) {
|
||||
Button("OK", role: .cancel) {}
|
||||
.alert(isPresented: $showAlert) {
|
||||
Alert(title: Text(alertMessage), dismissButton: .default(Text("OK")))
|
||||
}
|
||||
.alert("Skip Setup?", isPresented: $showSkipAlert) {
|
||||
Button("Skip", role: .destructive) { finished = true }
|
||||
Button("Cancel", role: .cancel) {}
|
||||
.alert(isPresented: $showSkipAlert) {
|
||||
Alert(
|
||||
title: Text("Skip Setup?"),
|
||||
primaryButton: .destructive(Text("Skip")) {
|
||||
finished = true
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
.onAppear {
|
||||
initialize()
|
||||
@ -390,7 +395,7 @@ struct SetupView: View {
|
||||
|
||||
let iconFileName = iconFiles.last else {
|
||||
|
||||
print("Could not find icons in bundle")
|
||||
// print("Could not find icons in bundle")
|
||||
return ""
|
||||
}
|
||||
|
||||
|
Binary file not shown.
@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>AvailableLibraries</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>BinaryPath</key>
|
||||
<string>MoltenVK.framework/MoltenVK</string>
|
||||
<key>LibraryIdentifier</key>
|
||||
<string>ios-arm64</string>
|
||||
<key>LibraryPath</key>
|
||||
<string>MoltenVK.framework</string>
|
||||
<key>SupportedArchitectures</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>SupportedPlatform</key>
|
||||
<string>ios</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XFWK</string>
|
||||
<key>XCFrameworkFormatVersion</key>
|
||||
<string>1.0</string>
|
||||
</dict>
|
||||
</plist>
|
Binary file not shown.
Binary file not shown.
@ -207,7 +207,7 @@ namespace Ryujinx.Headless.SDL2
|
||||
[Option("aspect-ratio", Required = false, Default = AspectRatio.Fixed16x9, HelpText = "Aspect Ratio applied to the renderer window.")]
|
||||
public AspectRatio AspectRatio { get; set; }
|
||||
|
||||
[Option("backend-threading", Required = false, Default = BackendThreading.Auto, HelpText = "Whether or not backend threading is enabled. The \"Auto\" setting will determine whether threading should be enabled at runtime.")]
|
||||
[Option("backend-threading", Required = false, Default = BackendThreading.On, HelpText = "Whether or not backend threading is enabled. The \"Auto\" setting will determine whether threading should be enabled at runtime.")]
|
||||
public BackendThreading BackendThreading { get; set; }
|
||||
|
||||
[Option("disable-macro-hle", Required = false, HelpText = "Disables high-level emulation of Macro code. Leaving this enabled improves performance but may cause graphical glitches in some games.")]
|
||||
|
Loading…
x
Reference in New Issue
Block a user