New 2.6 changes

This commit is contained in:
stossy11 2024-11-17 15:06:58 +11:00
parent 8dc2995ea8
commit c8ea75aa07
19 changed files with 681 additions and 135 deletions

View File

@ -82,6 +82,8 @@
4EA0AA602CB6845700B51C64 /* JITEnabler.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EA0AA5F2CB6845700B51C64 /* JITEnabler.m */; }; 4EA0AA602CB6845700B51C64 /* JITEnabler.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EA0AA5F2CB6845700B51C64 /* JITEnabler.m */; };
4EC662B02CAA1229000DBC5F /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4EC662AF2CAA1229000DBC5F /* SwiftUIJoystick */; }; 4EC662B02CAA1229000DBC5F /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4EC662AF2CAA1229000DBC5F /* SwiftUIJoystick */; };
4EC662B42CAA1257000DBC5F /* JoystickView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC662B32CAA1254000DBC5F /* JoystickView.swift */; }; 4EC662B42CAA1257000DBC5F /* JoystickView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC662B32CAA1254000DBC5F /* JoystickView.swift */; };
4EDA92352CE830B10024A064 /* DeviceMemory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDA92342CE830AB0024A064 /* DeviceMemory.swift */; };
4EDA92BF2CE981220024A064 /* PomeloRPC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDA92BE2CE981200024A064 /* PomeloRPC.swift */; };
4EE462B52CB548E800BF268E /* MTLViewExtentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE462B42CB548E200BF268E /* MTLViewExtentions.swift */; }; 4EE462B52CB548E800BF268E /* MTLViewExtentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE462B42CB548E200BF268E /* MTLViewExtentions.swift */; };
4EE462B92CB54F2100BF268E /* ScreenShotListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE462B82CB54F1800BF268E /* ScreenShotListView.swift */; }; 4EE462B92CB54F2100BF268E /* ScreenShotListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE462B82CB54F1800BF268E /* ScreenShotListView.swift */; };
4EE462BC2CB5552900BF268E /* MotionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE462BB2CB5552900BF268E /* MotionManager.swift */; }; 4EE462BC2CB5552900BF268E /* MotionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE462BB2CB5552900BF268E /* MotionManager.swift */; };
@ -200,6 +202,8 @@
4EA0AA592CB6785B00B51C64 /* Sudachi.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Sudachi.xcodeproj; path = Sudachi/Sudachi.xcodeproj; sourceTree = "<group>"; }; 4EA0AA592CB6785B00B51C64 /* Sudachi.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Sudachi.xcodeproj; path = Sudachi/Sudachi.xcodeproj; sourceTree = "<group>"; };
4EA0AA5F2CB6845700B51C64 /* JITEnabler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JITEnabler.m; sourceTree = "<group>"; }; 4EA0AA5F2CB6845700B51C64 /* JITEnabler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JITEnabler.m; sourceTree = "<group>"; };
4EC662B32CAA1254000DBC5F /* JoystickView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoystickView.swift; sourceTree = "<group>"; }; 4EC662B32CAA1254000DBC5F /* JoystickView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoystickView.swift; sourceTree = "<group>"; };
4EDA92342CE830AB0024A064 /* DeviceMemory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMemory.swift; sourceTree = "<group>"; };
4EDA92BE2CE981200024A064 /* PomeloRPC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PomeloRPC.swift; sourceTree = "<group>"; };
4EE462B42CB548E200BF268E /* MTLViewExtentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MTLViewExtentions.swift; sourceTree = "<group>"; }; 4EE462B42CB548E200BF268E /* MTLViewExtentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MTLViewExtentions.swift; sourceTree = "<group>"; };
4EE462B82CB54F1800BF268E /* ScreenShotListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenShotListView.swift; sourceTree = "<group>"; }; 4EE462B82CB54F1800BF268E /* ScreenShotListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenShotListView.swift; sourceTree = "<group>"; };
4EE462BB2CB5552900BF268E /* MotionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionManager.swift; sourceTree = "<group>"; }; 4EE462BB2CB5552900BF268E /* MotionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionManager.swift; sourceTree = "<group>"; };
@ -385,6 +389,8 @@
384F188C2C1DCB4F0073375C /* Pomelo */ = { 384F188C2C1DCB4F0073375C /* Pomelo */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4EDA92BD2CE9811A0024A064 /* PomeloRPC */,
4EDA92322CE830A50024A064 /* CurrentDevice */,
4EE4734A2CDECAA3000A010C /* Navigation */, 4EE4734A2CDECAA3000A010C /* Navigation */,
4EE9B1EA2CC4700F008FA07B /* Intents */, 4EE9B1EA2CC4700F008FA07B /* Intents */,
4EE462B62CB54F0400BF268E /* ScreenshotManager */, 4EE462B62CB54F0400BF268E /* ScreenshotManager */,
@ -595,6 +601,22 @@
path = Joystick; path = Joystick;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4EDA92322CE830A50024A064 /* CurrentDevice */ = {
isa = PBXGroup;
children = (
4EDA92342CE830AB0024A064 /* DeviceMemory.swift */,
);
path = CurrentDevice;
sourceTree = "<group>";
};
4EDA92BD2CE9811A0024A064 /* PomeloRPC */ = {
isa = PBXGroup;
children = (
4EDA92BE2CE981200024A064 /* PomeloRPC.swift */,
);
path = PomeloRPC;
sourceTree = "<group>";
};
4EE462B32CB548D800BF268E /* Extentions */ = { 4EE462B32CB548D800BF268E /* Extentions */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -788,6 +810,7 @@
4EE593FF2C5FA1D1000939C4 /* AppIconProvider.swift in Sources */, 4EE593FF2C5FA1D1000939C4 /* AppIconProvider.swift in Sources */,
386F6EEC2C42E0C800C62EBE /* PomeloApp.swift in Sources */, 386F6EEC2C42E0C800C62EBE /* PomeloApp.swift in Sources */,
4E1687CE2CC7A27000485EDB /* SudachiMetalView.swift in Sources */, 4E1687CE2CC7A27000485EDB /* SudachiMetalView.swift in Sources */,
4EDA92352CE830B10024A064 /* DeviceMemory.swift in Sources */,
4E5855E32CB6770F00047C2A /* AskForJIT.swift in Sources */, 4E5855E32CB6770F00047C2A /* AskForJIT.swift in Sources */,
384194702C4E540500396613 /* SudachiEmulationHandler.swift in Sources */, 384194702C4E540500396613 /* SudachiEmulationHandler.swift in Sources */,
38020F562C43A02100029E9A /* BootOSView.swift in Sources */, 38020F562C43A02100029E9A /* BootOSView.swift in Sources */,
@ -811,6 +834,7 @@
4EE462BF2CB5708800BF268E /* Zoomable.swift in Sources */, 4EE462BF2CB5708800BF268E /* Zoomable.swift in Sources */,
3841946C2C4E4D2B00396613 /* ControllerView.swift in Sources */, 3841946C2C4E4D2B00396613 /* ControllerView.swift in Sources */,
38B7FE022C7610A600D274FB /* AirPlay.swift in Sources */, 38B7FE022C7610A600D274FB /* AirPlay.swift in Sources */,
4EDA92BF2CE981220024A064 /* PomeloRPC.swift in Sources */,
3841946D2C4E4D2B00396613 /* SudachiEmulationView.swift in Sources */, 3841946D2C4E4D2B00396613 /* SudachiEmulationView.swift in Sources */,
386F6EEA2C42E0B900C62EBE /* SudachiGame.swift in Sources */, 386F6EEA2C42E0B900C62EBE /* SudachiGame.swift in Sources */,
4EE462C42CB576F400BF268E /* BottomMenuView.swift in Sources */, 4EE462C42CB576F400BF268E /* BottomMenuView.swift in Sources */,
@ -978,7 +1002,7 @@
INFOPLIST_KEY_NSAppleMusicUsageDescription = "We need access to use Sign in with Apple."; INFOPLIST_KEY_NSAppleMusicUsageDescription = "We need access to use Sign in with Apple.";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Pomelo needs local network access for P2P Multiplayer "; INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Pomelo needs local network access for P2P Multiplayer ";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Pomelo needs Microphone so the games can access it."; INFOPLIST_KEY_NSMicrophoneUsageDescription = "Pomelo needs Microphone so the games can access it. (Don't ask me, SDL is weird. i think its trying to put audio through the mic)";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UIRequiresFullScreen = YES; INFOPLIST_KEY_UIRequiresFullScreen = YES;
@ -1045,7 +1069,7 @@
INFOPLIST_KEY_NSAppleMusicUsageDescription = "We need access to use Sign in with Apple."; INFOPLIST_KEY_NSAppleMusicUsageDescription = "We need access to use Sign in with Apple.";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Pomelo needs local network access for P2P Multiplayer "; INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Pomelo needs local network access for P2P Multiplayer ";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Pomelo needs Microphone so the games can access it."; INFOPLIST_KEY_NSMicrophoneUsageDescription = "Pomelo needs Microphone so the games can access it. (Don't ask me, SDL is weird. i think its trying to put audio through the mic)";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UIRequiresFullScreen = YES; INFOPLIST_KEY_UIRequiresFullScreen = YES;

View File

@ -0,0 +1,28 @@
//
// DeviceMemory.swift
// Pomelo
//
// Created by Stossy11 on 16/11/2024.
// Copyright © 2024 Stossy11. All rights reserved.
//
import UIKit
import SwiftUI
import Foundation
enum DeviceMemory {
/// Check if device has 8GB or more RAM
static var has8GBOrMore: Bool {
#if targetEnvironment(simulator)
return ProcessInfo.processInfo.physicalMemory >= 7 * 1024 * 1024 * 1024 // 8GB in bytes
#else
return ProcessInfo.processInfo.physicalMemory >= 7 * 1024 * 1024 * 1024 // 8GB in bytes
#endif
}
/// Get total RAM in GB (rounded)
static var totalRAM: Int {
Int(ProcessInfo.processInfo.physicalMemory / 1024 / 1024 / 1024) + 1
}
}

View File

@ -174,7 +174,7 @@ struct ControllerView: View {
motion.valueChangedHandler = { motion in motion.valueChangedHandler = { motion in
// Get current time // Get current time
let currentTimestamp = Date().timeIntervalSince1970 let currentTimestamp = Date().timeIntervalSince1970
// let deltaTimestamp = Int32((currentTimestamp - lastTimestamp) * 1000) // Difference in milliseconds let deltaTimestamp = Int32((currentTimestamp - lastTimestamp) * 1000) // Difference in milliseconds
// Update last timestamp // Update last timestamp
lastTimestamp = currentTimestamp lastTimestamp = currentTimestamp
@ -192,7 +192,7 @@ struct ControllerView: View {
// print("\(gyroX), \(gyroY), \(gyroZ), \(accelX), \(accelY), \(accelZ)") // print("\(gyroX), \(gyroY), \(gyroZ), \(accelX), \(accelY), \(accelZ)")
// Call your gyroMoved function with the motion data // Call your gyroMoved function with the motion data
sudachi.gyroMoved(x: Float(gyroX), y: Float(gyroY), z: Float(gyroZ), accelX: Float(accelX), accelY: Float(accelY), accelZ: Float(accelZ), controllerId: Int32(controllerId), deltaTimestamp: Int32(lastTimestamp)) sudachi.gyroMoved(x: Float(gyroX), y: Float(gyroY), z: Float(gyroZ), accelX: Float(accelX), accelY: Float(accelY), accelZ: Float(accelZ), controllerId: Int32(controllerId), deltaTimestamp: Int32(deltaTimestamp))
} }
} }
@ -313,6 +313,8 @@ struct OnScreenController: View {
} }
struct ShoulderButtonsViewLeft: View { struct ShoulderButtonsViewLeft: View {
@State var width: CGFloat = 160
@State var height: CGFloat = 20
var body: some View { var body: some View {
HStack { HStack {
ButtonView(button: .triggerZL) ButtonView(button: .triggerZL)
@ -320,11 +322,19 @@ struct ShoulderButtonsViewLeft: View {
ButtonView(button: .triggerL) ButtonView(button: .triggerL)
.padding(.horizontal) .padding(.horizontal)
} }
.frame(width: 160, height: 20) .frame(width: width, height: height)
.onAppear() {
if UIDevice.current.systemName.contains("iPadOS") {
width *= 1.2
height *= 1.2
}
}
} }
} }
struct ShoulderButtonsViewRight: View { struct ShoulderButtonsViewRight: View {
@State var width: CGFloat = 160
@State var height: CGFloat = 20
var body: some View { var body: some View {
HStack { HStack {
ButtonView(button: .triggerR) ButtonView(button: .triggerR)
@ -332,12 +342,18 @@ struct ShoulderButtonsViewRight: View {
ButtonView(button: .triggerZR) ButtonView(button: .triggerZR)
.padding(.horizontal) .padding(.horizontal)
} }
.frame(width: 160, height: 20) .frame(width: width, height: height)
.onAppear() {
if UIDevice.current.systemName.contains("iPadOS") {
width *= 1.2
height *= 1.2
}
}
} }
} }
struct DPadView: View { struct DPadView: View {
@State var size: CGFloat = 145
var body: some View { var body: some View {
VStack { VStack {
ButtonView(button: .directionalPadUp) ButtonView(button: .directionalPadUp)
@ -349,11 +365,17 @@ struct DPadView: View {
ButtonView(button: .directionalPadDown) ButtonView(button: .directionalPadDown)
.padding(.horizontal) .padding(.horizontal)
} }
.frame(width: 145, height: 145) .frame(width: size, height: size)
.onAppear() {
if UIDevice.current.systemName.contains("iPadOS") {
size *= 1.2
}
}
} }
} }
struct ABXYView: View { struct ABXYView: View {
@State var size: CGFloat = 145
var body: some View { var body: some View {
VStack { VStack {
ButtonView(button: .X) ButtonView(button: .X)
@ -365,7 +387,12 @@ struct ABXYView: View {
ButtonView(button: .B) ButtonView(button: .B)
.padding(.horizontal) .padding(.horizontal)
} }
.frame(width: 145, height: 145) .frame(width: size, height: size)
.onAppear() {
if UIDevice.current.systemName.contains("iPadOS") {
size *= 1.2
}
}
} }
} }
@ -430,6 +457,11 @@ struct ButtonView: View {
width = 35 width = 35
height = 35 height = 35
} }
if UIDevice.current.systemName.contains("iPadOS") {
width *= 1.2
height *= 1.2
}
} }
} }

View File

@ -23,7 +23,13 @@ public struct Joystick: View {
let sudachi = Sudachi.shared let sudachi = Sudachi.shared
@ObservedObject public var joystickMonitor = JoystickMonitor() @ObservedObject public var joystickMonitor = JoystickMonitor()
private let dragDiameter: CGFloat = 160 var dragDiameter: CGFloat {
var selfs = CGFloat(160)
if UIDevice.current.systemName.contains("iPadOS") {
return selfs * 1.2
}
return selfs
}
private let shape: JoystickShape = .circle private let shape: JoystickShape = .circle
public var body: some View { public var body: some View {

View File

@ -10,6 +10,7 @@ import Sudachi
import Metal import Metal
import Foundation import Foundation
class SudachiEmulationViewModel: ObservableObject { class SudachiEmulationViewModel: ObservableObject {
@Published var isShowingCustomButton = true @Published var isShowingCustomButton = true
@State var should = false @State var should = false
@ -24,6 +25,12 @@ class SudachiEmulationViewModel: ObservableObject {
var doesneedresources = false var doesneedresources = false
@State var iscustom: Bool = false @State var iscustom: Bool = false
private var gameTimer: Timer?
private var elapsedTime: TimeInterval = 0
private var startTime: Date?
private var rpctimer: Timer?
init(game: PomeloGame?) { init(game: PomeloGame?) {
self.device = MTLCreateSystemDefaultDevice() self.device = MTLCreateSystemDefaultDevice()
self.sudachiGame = game self.sudachiGame = game
@ -38,22 +45,26 @@ class SudachiEmulationViewModel: ObservableObject {
iscustom = ((sudachiGame?.fileURL.startAccessingSecurityScopedResource()) != nil) iscustom = ((sudachiGame?.fileURL.startAccessingSecurityScopedResource()) != nil)
print("Is outside URL? \(iscustom ? "Yes" : "No")")
print("is outside url? \(iscustom ? "Yes" : "no")")
// startGameTimer() // Start the timer when the game starts
DispatchQueue.global(qos: .userInteractive).async { [self] in DispatchQueue.global(qos: .userInteractive).async { [self] in
if let sudachiGame = self.sudachiGame { if let sudachiGame = self.sudachiGame {
if sudachiGame.fileURL == URL(string: "BootMii") { if sudachiGame.fileURL == URL(string: "BootMii") {
self.sudachi.bootMii() self.sudachi.bootMii()
} else { } else {
self.sudachi.insert(game: sudachiGame.fileURL) self.sudachi.insert(game: sudachiGame.fileURL)
} }
} else { } else {
self.sudachi.bootOS() self.sudachi.bootOS()
} }
} }
if UserDefaults.standard.bool(forKey: "pomeloRPC") {
startRPCTimer()
}
thread = .init(block: self.step) thread = .init(block: self.step)
thread.name = "Pomelo" thread.name = "Pomelo"
thread.qualityOfService = .userInteractive thread.qualityOfService = .userInteractive
@ -74,6 +85,9 @@ class SudachiEmulationViewModel: ObservableObject {
private func stopEmulation() { private func stopEmulation() {
if isRunning { if isRunning {
isRunning = false isRunning = false
stopGameTimer() // Stop the timer and save elapsed time
rpctimer?.invalidate()
rpctimer = nil
sudachi.exit() sudachi.exit()
thread.cancel() thread.cancel()
if iscustom { if iscustom {
@ -82,6 +96,77 @@ class SudachiEmulationViewModel: ObservableObject {
} }
} }
public func startRPCTimer() {
if let sudachiGame {
startRPC(game: sudachiGame)
} else {
startRPC(game: PomeloGame(programid: 0, ishomebrew: false, developer: "Nintendo", fileURL: URL(string: "/")!, imageData: Data(), title: "Home Menu"))
}
rpctimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in
guard let self = self else { return }
self.runEveryFiveSeconds()
}
}
private func runEveryFiveSeconds() {
rpcHeartBeat()
}
public func startGameTimer() {
startTime = Date()
gameTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
guard let self = self else { return }
if let startTime = self.startTime {
self.elapsedTime = Date().timeIntervalSince(startTime)
saveElapsedTimeToJSON()
}
}
}
private func stopGameTimer() {
gameTimer?.invalidate()
gameTimer = nil
saveElapsedTimeToJSON()
}
private func saveElapsedTimeToJSON() {
var newEntry: [String: TimeInterval]
if let game = sudachiGame {
newEntry = [game.title: elapsedTime]
} else {
newEntry = ["Home Menu": elapsedTime]
}
let fileURL = getDocumentsDirectory().appendingPathComponent("PomeloInfo.json")
do {
var existingData: [String: TimeInterval] = [:]
// Check if the file exists
if FileManager.default.fileExists(atPath: fileURL.path) {
// Read existing data
let data = try Data(contentsOf: fileURL)
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: TimeInterval] {
existingData = json
}
}
// Merge new data with existing data
for (key, value) in newEntry {
existingData[key] = value
}
// Write updated data back to the file
let updatedJsonData = try JSONSerialization.data(withJSONObject: existingData, options: .prettyPrinted)
try updatedJsonData.write(to: fileURL, options: .atomic)
print("Successfully saved elapsed time to \(fileURL.path)")
} catch {
print("Error saving elapsed time: \(error.localizedDescription)")
}
}
func handleOrientationChange() { func handleOrientationChange() {
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self = self else { return } guard let self = self else { return }
@ -105,3 +190,7 @@ class SudachiEmulationViewModel: ObservableObject {
} }
} }
} }
func getDocumentsDirectory() -> URL {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
}

View File

@ -24,6 +24,8 @@ struct SudachiEmulationView: View {
@State private var thread: Thread! @State private var thread: Thread!
@State var uiTabBarController: UITabBarController? @State var uiTabBarController: UITabBarController?
@State private var isFirstFrameShown = false @State private var isFirstFrameShown = false
@State private var switched = false
@State private var wasairplay = false
@State private var timer: Timer? @State private var timer: Timer?
@Environment(\.scenePhase) var scenePhase @Environment(\.scenePhase) var scenePhase
@AppStorage("isairplay") private var isairplay: Bool = true @AppStorage("isairplay") private var isairplay: Bool = true
@ -31,6 +33,10 @@ struct SudachiEmulationView: View {
@AppStorage("showfunnibackground") private var showbackground: Bool = false @AppStorage("showfunnibackground") private var showbackground: Bool = false
let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
@Environment(\.presentationMode) var presentationMode @Environment(\.presentationMode) var presentationMode
@State private var isInBackground = false
@AppStorage("disableMainScreen") var disableMainScreen: Bool = false
@AppStorage("disableElapsedTime") var disableElapsedTime: Bool = false
init(game: PomeloGame?) { init(game: PomeloGame?) {
_game = State(wrappedValue: game) _game = State(wrappedValue: game)
@ -38,83 +44,103 @@ struct SudachiEmulationView: View {
} }
var body: some View { var body: some View {
ZStack { ZStack {
if #available(iOS 18.0, *), showbackground { if (!disableMainScreen && !isairplay) {
BackgroundView() if #available(iOS 18.0, *), showbackground {
.ignoresSafeArea(.all) BackgroundView()
.ignoresSafeArea(.all)
}
if !isairplay, !switched {
SudachiMetalView(viewModel: viewModel, mtkview: $mtkview, device: $device, airplaylater: false)
}
} }
if !isairplay {
SudachiMetalView(viewModel: viewModel, mtkview: $mtkview, device: $device) if disableMainScreen, isairplay {
ControllerView()
.hidden()
} else {
ControllerView()
} }
ControllerView()
if ShowMenuButton { if (!disableMainScreen && !isairplay) {
VStack { if ShowMenuButton {
HStack { VStack {
Spacer() HStack {
Menu { Spacer()
Button { Menu {
if let metalView = mtkview { Button {
print("button pressed") if let metalView = mtkview {
print("button pressed")
let lastDrawableDisplayed = metalView.currentDrawable?.texture
if let imageRef = lastDrawableDisplayed?.toImage() {
let uiImage: UIImage = UIImage.init(cgImage: imageRef)
if let pngData = uiImage.pngData() { let lastDrawableDisplayed = metalView.currentDrawable?.texture
// Define the path to save the PNG file
if let game = self.game { if let imageRef = lastDrawableDisplayed?.toImage() {
let fileURL = documentsDir.appendingPathComponent("screenshots/\(game.title)-(\(Date())).png") let uiImage: UIImage = UIImage.init(cgImage: imageRef)
print(Date())
do { if let pngData = uiImage.pngData() {
// Write the PNG data to the file // Define the path to save the PNG file
try pngData.write(to: fileURL) if let game = self.game {
print("Image saved to: \(fileURL.path)") let fileURL = documentsDir.appendingPathComponent("screenshots/\(game.title)-(\(Date())).png")
} catch { print(Date())
print("Error saving PNG: \(error)") do {
// Write the PNG data to the file
try pngData.write(to: fileURL)
print("Image saved to: \(fileURL.path)")
} catch {
print("Error saving PNG: \(error)")
}
} else {
let fileURL = documentsDir.appendingPathComponent("screenshots/Home_Menu-(\(Date())).png")
do {
// Write the PNG data to the file
try pngData.write(to: fileURL)
print("Image saved to: \(fileURL.path)")
} catch {
print("Error saving PNG: \(error)")
}
} }
} else { } else {
let fileURL = documentsDir.appendingPathComponent("screenshots/Home_Menu-(\(Date())).png") print("Failed to convert UIImage to PNG data")
do {
// Write the PNG data to the file
try pngData.write(to: fileURL)
print("Image saved to: \(fileURL.path)")
} catch {
print("Error saving PNG: \(error)")
}
} }
} else {
print("Failed to convert UIImage to PNG data")
} }
} }
} label: {
Text("Take Screenshot")
.font(.title)
.padding()
}
Button {
viewModel.customButtonTapped()
presentationMode.wrappedValue.dismiss()
} label: {
Text("Exit (Unstable)")
} }
} label: {
Text("Take Screenshot")
.font(.title)
.padding()
}
Button {
viewModel.customButtonTapped()
presentationMode.wrappedValue.dismiss()
} label: { } label: {
Text("Exit (Unstable)") Image(systemName: "ellipsis.circle.fill")
.resizable()
.frame(width: 45, height: 45)
.foregroundColor(Color.gray)
} }
} label: {
Image(systemName: "ellipsis.circle.fill")
.resizable()
.frame(width: 45, height: 45)
.foregroundColor(Color.gray)
} }
Spacer()
} }
Spacer()
} }
} }
if (disableMainScreen && isairplay) {
Color.black
.edgesIgnoringSafeArea(.all)
}
} }
.overlay( .overlay(
// Loading screen overlay on top of MetalView // Loading screen overlay on top of MetalView
@ -129,37 +155,54 @@ struct SudachiEmulationView: View {
UIApplication.shared.isIdleTimerDisabled = true UIApplication.shared.isIdleTimerDisabled = true
print("AirPlay + \(Air.shared.connected)") print("AirPlay + \(Air.shared.connected)")
print(disableMainScreen)
print(isairplay)
// Your existing variables
isairplay = Air.shared.connected isairplay = Air.shared.connected
DispatchQueue.main.async {
Air.connection({ fun in // Set up initial AirPlay view
print("AirPlay + \(fun)") airplayafter(airplay: false)
isairplay = fun
})
if isairplay {
Air.play(AnyView(SudachiMetalView(viewModel: viewModel, mtkview: $mtkview, device: $device)))
}
}
if !isairplay { startPollingFirstFrameShowed()
startPollingFirstFrameShowed()
}
} }
.onDisappear { .onDisappear {
if !isairplay { stopPollingFirstFrameShowed()
stopPollingFirstFrameShowed() UIApplication.shared.isIdleTimerDisabled = false
}
uiTabBarController?.tabBar.isHidden = false uiTabBarController?.tabBar.isHidden = false
} }
.navigationBarBackButtonHidden(true) .navigationBarBackButtonHidden(true)
} }
private func airplayafter(airplay: Bool) {
isairplay = Air.shared.connected
// Set up initial AirPlay view
if isairplay, !isFirstFrameShown {
let airplayView = AnyView(SudachiMetalView(
viewModel: viewModel,
mtkview: $mtkview,
device: $device,
airplaylater: airplay
))
DispatchQueue.main.async {
Air.play(airplayView)
}
}
}
private func startPollingFirstFrameShowed() { private func startPollingFirstFrameShowed() {
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
if sudachi.FirstFrameShowed() { if sudachi.FirstFrameShowed() {
withAnimation { withAnimation {
isFirstFrameShown = true isFirstFrameShown = true
} }
if !disableElapsedTime {
viewModel.startGameTimer()
}
stopPollingFirstFrameShowed() stopPollingFirstFrameShowed()
} }
} }

View File

@ -17,15 +17,19 @@ struct SudachiMetalView: View {
@Binding var mtkview: MTKView? @Binding var mtkview: MTKView?
@Binding var device: MTLDevice? @Binding var device: MTLDevice?
@AppStorage("isairplay") private var isairplay: Bool = true @AppStorage("isairplay") private var isairplay: Bool = true
@State var airplaylater: Bool
var body: some View { var body: some View {
if #available(iOS 16.0, *) { if #available(iOS 16.0, *) {
MetalView(device: device) { view in MetalView(device: device) { view in
DispatchQueue.main.async { DispatchQueue.main.async {
if let metalView = view as? MTKView { if let metalView = view as? MTKView {
mtkview = metalView if !airplaylater {
viewModel.configureSudachi(with: metalView) mtkview = metalView
} else { viewModel.configureSudachi(with: metalView)
print("Error: view is not of type MTKView") } else {
let sudachi = Sudachi.shared
sudachi.configure(layer: metalView.layer as! CAMetalLayer, with: metalView.frame.size)
}
} }
} }
} }
@ -40,10 +44,14 @@ struct SudachiMetalView: View {
MetalView(device: device) { view in MetalView(device: device) { view in
DispatchQueue.main.async { DispatchQueue.main.async {
if let metalView = view as? MTKView { if let metalView = view as? MTKView {
mtkview = metalView if !airplaylater {
viewModel.configureSudachi(with: metalView) mtkview = metalView
} else { viewModel.configureSudachi(with: metalView)
print("Error: view is not of type MTKView") } else {
let sudachi = Sudachi.shared
sudachi.configure(layer: metalView.layer as! CAMetalLayer, with: metalView.frame.size)
}
} }
} }
} }

View File

@ -53,14 +53,18 @@ class SudachiScreenView: UIView {
return return
} }
print("Location: \(touch.location(in: primaryScreen))") if !userDefaults.bool(forKey: "disabletouch") {
sudachi.touchBegan(at: touch.location(in: primaryScreen), for: 0) print("Location: \(touch.location(in: primaryScreen))")
sudachi.touchBegan(at: touch.location(in: primaryScreen), for: 0)
}
} }
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event) super.touchesEnded(touches, with: event)
print("Touch Ended") if !userDefaults.bool(forKey: "disabletouch") {
sudachi.touchEnded(for: 0) print("Touch Ended")
sudachi.touchEnded(for: 0)
}
} }
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
@ -69,15 +73,22 @@ class SudachiScreenView: UIView {
return return
} }
let location = touch.location(in: primaryScreen) if !userDefaults.bool(forKey: "disabletouch") {
print("Location Moved: \(location)") let location = touch.location(in: primaryScreen)
sudachi.touchMoved(at: location, for: 0) print("Location Moved: \(location)")
sudachi.touchMoved(at: location, for: 0)
}
} }
func setupSudachiScreen2() { func setupSudachiScreen2() {
primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice()) primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
primaryScreen.translatesAutoresizingMaskIntoConstraints = false primaryScreen.translatesAutoresizingMaskIntoConstraints = false
primaryScreen.clipsToBounds = true primaryScreen.clipsToBounds = true
if userDefaults.bool(forKey: "isairplay") {
primaryScreen.layer.backgroundColor = UIColor.black.cgColor
} else {
primaryScreen.layer.backgroundColor = UIColor.secondarySystemBackground.cgColor
}
addSubview(primaryScreen) addSubview(primaryScreen)
fullscreenconstraints = [ fullscreenconstraints = [

View File

@ -7,7 +7,6 @@ __attribute__((constructor)) static void entry(int argc, char **argv)
double systemVersion = [[[UIDevice currentDevice] systemVersion] doubleValue]; double systemVersion = [[[UIDevice currentDevice] systemVersion] doubleValue];
if (isJITEnabled()) { if (isJITEnabled()) {
NSLog(@"yippee"); NSLog(@"yippee");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
@ -20,10 +19,10 @@ __attribute__((constructor)) static void entry(int argc, char **argv)
[defaults synchronize]; // Ensure the value is saved immediately [defaults synchronize]; // Ensure the value is saved immediately
} }
if (!getEntitlementValue(@"com.apple.developer.kernel.increased-memory-limit")) { if (getEntitlementValue(@"com.apple.developer.kernel.increased-memory-limit")) {
NSLog(@"Entitlement Does Not Exist"); NSLog(@"Entitlement Does Exist");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:@"entitlementNotExists"]; [defaults setBool:YES forKey:@"entitlementExists"];
[defaults synchronize]; // Ensure the value is saved immediately [defaults synchronize]; // Ensure the value is saved immediately
} }

View File

@ -17,6 +17,8 @@ struct GameGridView: View {
@State var selectedGame: PomeloGame? @State var selectedGame: PomeloGame?
@State var alertMessage: Alert? = nil @State var alertMessage: Alert? = nil
@State var alapsedtime: [String: TimeInterval] = loadElapsedTimeFromJSON()
var body: some View { var body: some View {
let filteredGames = core.games.filter { game in let filteredGames = core.games.filter { game in
return searchText.isEmpty || game.title.localizedCaseInsensitiveContains(searchText) return searchText.isEmpty || game.title.localizedCaseInsensitiveContains(searchText)
@ -28,14 +30,47 @@ struct GameGridView: View {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 200))], spacing: 2) { LazyVGrid(columns: [GridItem(.adaptive(minimum: 200))], spacing: 2) {
ForEach(0..<filteredGames.count, id: \.self) { index in ForEach(0..<filteredGames.count, id: \.self) { index in
let game = filteredGames[index] // Use filteredGames here let game = filteredGames[index] // Use filteredGames here
var gametime: TimeInterval?
NavigationLink(destination: SudachiEmulationView(game: game)) { NavigationLink(destination: SudachiEmulationView(game: game)) {
GameIconView(game: game, selectedGame: $selectedGame) GameIconView(game: game, selectedGame: $selectedGame)
.frame(maxWidth: 200, minHeight: 250) .frame(maxWidth: 200, minHeight: 250)
} }
.onAppear { .onAppear {
selectedGame = filteredGames.first selectedGame = filteredGames.first
if let game = alapsedtime.first(where: { $0.key == game.title }) {
gametime = game.value
}
} }
.contextMenu { .contextMenu {
var hourminssecs: Int = 0
if let gametime {
if hourminssecs == 0 {
Text("Time Elapsed: \(String(gametime / 3600))")
.onTapGesture {
hourminssecs = 1
}
} else if hourminssecs == 1 {
Text("Time Elapsed: \(String(gametime / 60))")
.onTapGesture {
hourminssecs = 2
}
} else if hourminssecs == 2 {
Text("Time Elapsed: \(String(gametime))")
.onTapGesture {
hourminssecs = 0
}
}
} else {
Text("Time Elapsed: 0")
}
Button(action: { Button(action: {
do { do {
try LibraryManager.shared.removerom(filteredGames[index]) try LibraryManager.shared.removerom(filteredGames[index])
@ -76,6 +111,7 @@ struct GameGridView: View {
} }
} }
} }
// urlgame = core.games.first(where: { String(describing: $0.programid) == text })
func refreshcore() { func refreshcore() {
do { do {
@ -86,3 +122,25 @@ struct GameGridView: View {
} }
} }
} }
func loadElapsedTimeFromJSON() -> [String: TimeInterval] {
let fileURL = getDocumentsDirectory().appendingPathComponent("PomeloInfo.json")
do {
// Check if the file exists
if FileManager.default.fileExists(atPath: fileURL.path) {
// Read data from the file
let data = try Data(contentsOf: fileURL)
// Parse the JSON data into a dictionary
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: TimeInterval] {
return json
}
}
} catch {
print("Error loading elapsed time: \(error.localizedDescription)")
}
// Return an empty dictionary if the file doesn't exist or an error occurs
return [:]
}

View File

@ -13,22 +13,57 @@ import UniformTypeIdentifiers
import Sudachi import Sudachi
struct GameListView: View { struct GameListView: View {
// let games: [PomeloGame]
@Binding var core: Core @Binding var core: Core
@Binding var selectedGame: PomeloGame? @Binding var selectedGame: PomeloGame?
@State private var alapsedtime: [String: TimeInterval] = [:] // Loaded later
@State private var hourminssecs: Int = 0 // Tracks display format for elapsed time
@State var timeString: String = ""
var body: some View { var body: some View {
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) { HStack(spacing: 0) {
ForEach(core.games.prefix(12)) { game in ForEach(core.games.prefix(12)) { game in
GameIconView(game: game, selectedGame: $selectedGame) GameIconView(game: game, selectedGame: $selectedGame)
.onAppear {
if let gametime = alapsedtime[game.title] {
switch hourminssecs {
case 0:
timeString = String(format: "%.2f hours", gametime / 3600)
case 1:
timeString = String(format: "%.2f minutes", gametime / 60)
case 2:
timeString = String(format: "%.2f seconds", gametime)
default:
timeString = "0"
}
}
}
.contextMenu { .contextMenu {
if let gametime = alapsedtime[game.title] {
Button {
hourminssecs = (hourminssecs + 1) % 3
switch hourminssecs {
case 0:
timeString = String(format: "%.2f hours", gametime / 3600)
case 1:
timeString = String(format: "%.2f minutes", gametime / 60)
case 2:
timeString = String(format: "%.2f seconds", gametime)
default:
timeString = "0"
}
} label: {
Text("Time Elapsed: \(timeString)")
}
} else {
Text("Time Elapsed: 0")
}
Button { Button {
UIPasteboard.general.string = String(describing: game.programid) UIPasteboard.general.string = String(describing: game.programid)
} label: { } label: {
Text("Game ID: \(String(describing: game.programid))") Text("Game ID: \(String(describing: game.programid))")
} }
Button { Button {
saveImageToIconsFolder(gameImageData: game.imageData, imageName: game.title) saveImageToIconsFolder(gameImageData: game.imageData, imageName: game.title)
} label: { } label: {
@ -36,7 +71,7 @@ struct GameListView: View {
} }
} }
} }
if core.games.count > 12 { if core.games.count > 12 {
NavigationLink { NavigationLink {
GameGridView(core: core) GameGridView(core: core)
@ -49,16 +84,20 @@ struct GameListView: View {
.foregroundColor(.white) .foregroundColor(.white)
.frame(width: 70, height: 70) .frame(width: 70, height: 70)
} }
.foregroundColor(Color.init(uiColor: .darkGray)) .foregroundColor(Color(uiColor: .darkGray))
} }
} }
} }
} }
.onAppear { .onAppear {
loadElapsedTime()
selectedGame = core.games.first selectedGame = core.games.first
} }
}
// Function to load elapsed time
private func loadElapsedTime() {
alapsedtime = loadElapsedTimeFromJSON()
} }
func saveImageToIconsFolder(gameImageData: Data, imageName: String) { func saveImageToIconsFolder(gameImageData: Data, imageName: String) {

View File

@ -30,7 +30,7 @@ struct TopBarView: View {
HStack { HStack {
NavigationLink { NavigationLink {
SudachiEmulationView(game: PomeloGame(programid: 0, ishomebrew: false, developer: "", fileURL: URL(string: "BootMii")!, imageData: Data(), title: "BootOS")) SudachiEmulationView(game: PomeloGame(programid: 0, ishomebrew: false, developer: "", fileURL: URL(string: "BootMii")!, imageData: Data(), title: "Mii Maker"))
} label: { } label: {
Image(systemName: "person.crop.circle.fill") Image(systemName: "person.crop.circle.fill")
.resizable() .resizable()

View File

@ -7,6 +7,7 @@
import SwiftUI import SwiftUI
import AppIntents import AppIntents
infix operator --: LogicalDisjunctionPrecedence infix operator --: LogicalDisjunctionPrecedence
func --(lhs: Bool, rhs: Bool) -> Bool { func --(lhs: Bool, rhs: Bool) -> Bool {
@ -17,22 +18,7 @@ func --(lhs: Bool, rhs: Bool) -> Bool {
struct PomeloApp: App { struct PomeloApp: App {
init() { init() {
setMoltenVKSettings()
// setenv("MVK_DEBUG", "1", 1)
// setenv("MVK_CONFIG_TRACE_VULKAN_CALLS", "1", 1)
// setenv("MVK_CONFIG_LOG_LEVEL", "4", 1)
// setenv("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", "3", 1)
// setenv("MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", "1024", 1)
setenv("MVK_USE_METAL_PRIVATE_API", "1", 1)
setenv("MVK_CONFIG_RESUME_LOST_DEVICE", "1", 1)
//MVK_CONFIG_RESUME_LOST_DEVICE
setenv("MVK_CONFIG_USE_METAL_PRIVATE_API", "1", 1)
} }
var body: some Scene { var body: some Scene {
@ -45,5 +31,26 @@ struct PomeloApp: App {
} }
} }
} }
private func setMoltenVKSettings() {
let settings: [String: String] = [
"MVK_DEBUG": "0",
"MVK_CONFIG_DEBUG": "0",
"MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE": "0",
"MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS": "1",
"MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE": "512",
"MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS": "1",
"MVK_USE_METAL_PRIVATE_API": "1",
"MVK_CONFIG_RESUME_LOST_DEVICE": "1",
"MVK_CONFIG_USE_METAL_PRIVATE_API": "1"
]
settings.forEach { strins in
setenv(strins.key, strins.value, 1)
}
}
} }
// .appShortcuts([]) // .appShortcuts([])

View File

@ -0,0 +1,77 @@
//
// PomeloRPC.swift
// Pomelo
//
// Created by Stossy11 on 17/11/2024.
// Copyright © 2024 Stossy11. All rights reserved.
//
import Foundation
func startRPC(game: PomeloGame) {
// The URL of the server endpoint
let urlString = UserDefaults.standard.string(forKey: "pomeloRPCIP")
guard let url = URL(string: urlString ?? "") else {
print("Invalid URL")
return
}
// The data to send in the request
let json: [String: Any] = [
"name": game.title,
"id": String(game.programid),
"developer": game.developer
]
// Convert the dictionary to JSON data
do {
let jsonData = try JSONSerialization.data(withJSONObject: json, options: [])
// Create the POST request
var request = URLRequest(url: url.appendingPathComponent("start-rpc"))
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
// Create a URLSession data task
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error: \(error.localizedDescription)")
return
}
if let response = response as? HTTPURLResponse, response.statusCode == 200 {
print("Data successfully sent to server")
} else {
print("Failed to send data. Server responded with: \(String(describing: response))")
}
}
// Start the task
task.resume()
} catch {
print("Error encoding JSON: \(error.localizedDescription)")
}
}
func rpcHeartBeat() {
let urlString = UserDefaults.standard.string(forKey: "pomeloRPCIP")
guard let url = URL(string: urlString ?? "") else {
print("Invalid URL string")
return
}
let urlRequest = URLRequest(url: url.appendingPathComponent("heartbeat"))
let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in
print(data)
print(response)
print(error)
}
task.resume()
}

View File

@ -23,6 +23,16 @@ struct AdvancedSettingsView: View {
@AppStorage("canShowMetalHUD") var canShowMetalHUD: Bool = false @AppStorage("canShowMetalHUD") var canShowMetalHUD: Bool = false
@AppStorage("ShowMenuButton") private var ShowMenuButton: Bool = true @AppStorage("ShowMenuButton") private var ShowMenuButton: Bool = true
@AppStorage("hideLastNameTop") var hideLastName: Bool = false @AppStorage("hideLastNameTop") var hideLastName: Bool = false
@AppStorage("disableMainScreen") var disableMainScreen: Bool = false
@AppStorage("disabletouch") var disableTouch: Bool = false
@AppStorage("pomeloRPC") var pomeloRPC: Bool = false
@AppStorage("pomeloRPCIP") var pomeloRPCip: String = ""
@Environment(\.colorScheme) var colorScheme
@State var isshowing = false @State var isshowing = false
var body: some View { var body: some View {
@ -57,6 +67,23 @@ struct AdvancedSettingsView: View {
.font(.footnote) .font(.footnote)
.foregroundColor(.gray) .foregroundColor(.gray)
if colorScheme == .light {
Rectangle()
.fill(Color(uiColor: UIColor.secondarySystemBackground))
.cornerRadius(10)
.frame(width: .infinity, height: 50)
.overlay() {
HStack {
Toggle("Disable \(deviceType()) Screen", isOn: $disableMainScreen)
.padding()
}
}
Text("When AirPlaying the \(deviceType()) Screen will be disabled (Including On Screen Controller).")
.padding(.bottom)
.font(.footnote)
.foregroundColor(.gray)
}
if canShowMetalHUD {// (Int(UIDevice.current.systemVersion) ?? 0 >= 16) || canShowMetalHUD { if canShowMetalHUD {// (Int(UIDevice.current.systemVersion) ?? 0 >= 16) || canShowMetalHUD {
Rectangle() Rectangle()
.fill(Color(uiColor: UIColor.secondarySystemBackground)) .fill(Color(uiColor: UIColor.secondarySystemBackground))
@ -157,6 +184,38 @@ struct AdvancedSettingsView: View {
.font(.footnote) .font(.footnote)
.foregroundColor(.gray) .foregroundColor(.gray)
Rectangle()
.fill(Color(uiColor: UIColor.secondarySystemBackground))
.cornerRadius(10)
.frame(width: .infinity, height: 50)
.overlay() {
HStack {
Toggle("Pomelo RPC", isOn: $pomeloRPC)
.padding()
}
}
Text("This adds support for Discord Rich Presence (RPC). (This needs a desktop companion app)")
.padding(.bottom)
.font(.footnote)
.foregroundColor(.gray)
if pomeloRPC {
Rectangle()
.fill(Color(uiColor: UIColor.secondarySystemBackground))
.cornerRadius(10)
.frame(width: .infinity, height: 50)
.overlay() {
HStack {
Text("Pomelo RPC IP")
.padding()
TextField("Server Address", text: $pomeloRPCip)
}
}
Text("Please enter the Pomelo RPC Desktop Companion App Server Address.")
.padding(.bottom)
.font(.footnote)
.foregroundColor(.gray)
}
Rectangle() Rectangle()
.fill(Color(uiColor: UIColor.secondarySystemBackground)) .fill(Color(uiColor: UIColor.secondarySystemBackground))
@ -164,11 +223,32 @@ struct AdvancedSettingsView: View {
.frame(width: .infinity, height: 50) .frame(width: .infinity, height: 50)
.overlay() { .overlay() {
HStack { HStack {
Toggle("Memory Usage Increase", isOn: $kpagetable) Toggle("Disable Touch", isOn: $disableTouch)
.padding() .padding()
} }
} }
Text("This makes games way more stable but a lot of games will crash as you will run out of Memory way quicker. (Don't Enable this on devices with less then 8GB of memory as most games will crash)") Text("This option disables the touch screen in games.")
.padding(.bottom)
.font(.footnote)
.foregroundColor(.gray)
Rectangle()
.fill(Color(uiColor: UIColor.secondarySystemBackground))
.cornerRadius(10)
.frame(width: .infinity, height: 50)
.overlay() {
HStack {
if DeviceMemory.has8GBOrMore {
Toggle("Memory Usage Increase", isOn: $kpagetable)
.padding()
} else {
Toggle("Memory Usage Increase", isOn: $kpagetable)
.padding()
.disabled(true)
}
}
}
Text("This makes games a little more stable but a lot of games will crash as you will run out of Memory way quicker, 8GB Memory is needed for this feature.")
.padding(.bottom) .padding(.bottom)
.font(.footnote) .font(.footnote)
.foregroundColor(.gray) .foregroundColor(.gray)
@ -296,3 +376,12 @@ func restoreFoldersFromiCloud() {
print("Could not find iCloud Drive.") print("Could not find iCloud Drive.")
} }
} }
func deviceType() -> String {
if UIDevice.current.userInterfaceIdiom == .phone {
return "iPhone"
} else if UIDevice.current.userInterfaceIdiom == .pad {
return "iPad"
} else {
return "Mac"
}
}

View File

@ -9,7 +9,7 @@
import SwiftUI import SwiftUI
struct InfoView: View { struct InfoView: View {
@AppStorage("entitlementNotExists") private var entitlementNotExists: Bool = false @AppStorage("entitlementExists") private var entitlementExists: Bool = false
@AppStorage("increaseddebugmem") private var increaseddebugmem: Bool = false @AppStorage("increaseddebugmem") private var increaseddebugmem: Bool = false
@AppStorage("extended-virtual-addressing") private var extended: Bool = false @AppStorage("extended-virtual-addressing") private var extended: Bool = false
@State var gd = false @State var gd = false
@ -33,7 +33,7 @@ struct InfoView: View {
.font(Font.headline.weight(.bold)) .font(Font.headline.weight(.bold))
Spacer() Spacer()
.frame(height: 10) .frame(height: 10)
Text("Increased Memory Limit: \(String(describing: !entitlementNotExists))") Text("Increased Memory Limit: \(String(describing: entitlementExists))")
Spacer() Spacer()
.frame(height: 10) .frame(height: 10)
} }
@ -50,6 +50,24 @@ struct InfoView: View {
Text("Extended Virtual Addressing: \(String(describing: extended))") Text("Extended Virtual Addressing: \(String(describing: extended))")
} }
Divider()
Text("Memory:")
.font(.title)
.font(Font.headline.weight(.bold))
Spacer()
.frame(height: 10)
Group {
Text("Current:")
.font(.title2)
.foregroundColor(.blue)
.font(Font.headline.weight(.bold))
Spacer()
.frame(height: 10)
Text(String(DeviceMemory.totalRAM) + "GB")
Spacer()
.frame(height: 10)
}
} }
.padding() .padding()

View File

@ -41,6 +41,15 @@ public struct Sudachi {
public func bootMii() { public func bootMii() {
sudachiObjC.bootMii() sudachiObjC.bootMii()
} }
public func halt() {
sudachiObjC.halt()
}
public func start() {
sudachiObjC.start()
}
public func pause() { public func pause() {
sudachiObjC.pause() sudachiObjC.pause()
} }
@ -87,7 +96,6 @@ public struct Sudachi {
} }
public func gyroMoved(x: Float, y: Float, z: Float, accelX: Float, accelY: Float, accelZ: Float, controllerId: Int32, deltaTimestamp: Int32) { public func gyroMoved(x: Float, y: Float, z: Float, accelX: Float, accelY: Float, accelZ: Float, controllerId: Int32, deltaTimestamp: Int32) {
// Calling the Objective-C function with both gyroscope and accelerometer data
sudachiObjC.virtualControllerGyro(controllerId, sudachiObjC.virtualControllerGyro(controllerId,
deltaTimestamp: deltaTimestamp, deltaTimestamp: deltaTimestamp,
gyroX: x, gyroX: x,

View File

@ -53,6 +53,8 @@ typedef NS_ENUM(NSUInteger, VirtualControllerButtonType) {
-(void) pause; -(void) pause;
-(void) play; -(void) play;
-(BOOL) ispaused; -(BOOL) ispaused;
-(BOOL) halt;
-(BOOL) start;
-(BOOL) canGetFullPath; -(BOOL) canGetFullPath;
-(void) bootMii; -(void) bootMii;
-(void) quit; -(void) quit;

View File

@ -112,6 +112,14 @@
EmulationSession::GetInstance().PauseEmulation(); EmulationSession::GetInstance().PauseEmulation();
} }
-(void) halt {
EmulationSession::GetInstance().HaltEmulation();
}
-(void) start {
EmulationSession::GetInstance().RunEmulation();
}
-(void) play { -(void) play {
EmulationSession::GetInstance().UnPauseEmulation(); EmulationSession::GetInstance().UnPauseEmulation();
} }