New 2.6 changes
This commit is contained in:
parent
8dc2995ea8
commit
c8ea75aa07
@ -82,6 +82,8 @@
|
||||
4EA0AA602CB6845700B51C64 /* JITEnabler.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EA0AA5F2CB6845700B51C64 /* JITEnabler.m */; };
|
||||
4EC662B02CAA1229000DBC5F /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4EC662AF2CAA1229000DBC5F /* SwiftUIJoystick */; };
|
||||
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 */; };
|
||||
4EE462B92CB54F2100BF268E /* ScreenShotListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE462B82CB54F1800BF268E /* ScreenShotListView.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -385,6 +389,8 @@
|
||||
384F188C2C1DCB4F0073375C /* Pomelo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4EDA92BD2CE9811A0024A064 /* PomeloRPC */,
|
||||
4EDA92322CE830A50024A064 /* CurrentDevice */,
|
||||
4EE4734A2CDECAA3000A010C /* Navigation */,
|
||||
4EE9B1EA2CC4700F008FA07B /* Intents */,
|
||||
4EE462B62CB54F0400BF268E /* ScreenshotManager */,
|
||||
@ -595,6 +601,22 @@
|
||||
path = Joystick;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -788,6 +810,7 @@
|
||||
4EE593FF2C5FA1D1000939C4 /* AppIconProvider.swift in Sources */,
|
||||
386F6EEC2C42E0C800C62EBE /* PomeloApp.swift in Sources */,
|
||||
4E1687CE2CC7A27000485EDB /* SudachiMetalView.swift in Sources */,
|
||||
4EDA92352CE830B10024A064 /* DeviceMemory.swift in Sources */,
|
||||
4E5855E32CB6770F00047C2A /* AskForJIT.swift in Sources */,
|
||||
384194702C4E540500396613 /* SudachiEmulationHandler.swift in Sources */,
|
||||
38020F562C43A02100029E9A /* BootOSView.swift in Sources */,
|
||||
@ -811,6 +834,7 @@
|
||||
4EE462BF2CB5708800BF268E /* Zoomable.swift in Sources */,
|
||||
3841946C2C4E4D2B00396613 /* ControllerView.swift in Sources */,
|
||||
38B7FE022C7610A600D274FB /* AirPlay.swift in Sources */,
|
||||
4EDA92BF2CE981220024A064 /* PomeloRPC.swift in Sources */,
|
||||
3841946D2C4E4D2B00396613 /* SudachiEmulationView.swift in Sources */,
|
||||
386F6EEA2C42E0B900C62EBE /* SudachiGame.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_NSHumanReadableCopyright = "";
|
||||
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_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
||||
@ -1045,7 +1069,7 @@
|
||||
INFOPLIST_KEY_NSAppleMusicUsageDescription = "We need access to use Sign in with Apple.";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
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_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
||||
|
28
Pomelo/CurrentDevice/DeviceMemory.swift
Normal file
28
Pomelo/CurrentDevice/DeviceMemory.swift
Normal 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
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ struct ControllerView: View {
|
||||
motion.valueChangedHandler = { motion in
|
||||
// Get current time
|
||||
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
|
||||
lastTimestamp = currentTimestamp
|
||||
@ -192,7 +192,7 @@ struct ControllerView: View {
|
||||
// print("\(gyroX), \(gyroY), \(gyroZ), \(accelX), \(accelY), \(accelZ)")
|
||||
|
||||
// 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 {
|
||||
@State var width: CGFloat = 160
|
||||
@State var height: CGFloat = 20
|
||||
var body: some View {
|
||||
HStack {
|
||||
ButtonView(button: .triggerZL)
|
||||
@ -320,11 +322,19 @@ struct ShoulderButtonsViewLeft: View {
|
||||
ButtonView(button: .triggerL)
|
||||
.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 {
|
||||
@State var width: CGFloat = 160
|
||||
@State var height: CGFloat = 20
|
||||
var body: some View {
|
||||
HStack {
|
||||
ButtonView(button: .triggerR)
|
||||
@ -332,12 +342,18 @@ struct ShoulderButtonsViewRight: View {
|
||||
ButtonView(button: .triggerZR)
|
||||
.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 {
|
||||
@State var size: CGFloat = 145
|
||||
var body: some View {
|
||||
VStack {
|
||||
ButtonView(button: .directionalPadUp)
|
||||
@ -349,11 +365,17 @@ struct DPadView: View {
|
||||
ButtonView(button: .directionalPadDown)
|
||||
.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 {
|
||||
@State var size: CGFloat = 145
|
||||
var body: some View {
|
||||
VStack {
|
||||
ButtonView(button: .X)
|
||||
@ -365,7 +387,12 @@ struct ABXYView: View {
|
||||
ButtonView(button: .B)
|
||||
.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
|
||||
height = 35
|
||||
}
|
||||
|
||||
if UIDevice.current.systemName.contains("iPadOS") {
|
||||
width *= 1.2
|
||||
height *= 1.2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,13 @@ public struct Joystick: View {
|
||||
let sudachi = Sudachi.shared
|
||||
|
||||
@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
|
||||
|
||||
public var body: some View {
|
||||
|
@ -10,6 +10,7 @@ import Sudachi
|
||||
import Metal
|
||||
import Foundation
|
||||
|
||||
|
||||
class SudachiEmulationViewModel: ObservableObject {
|
||||
@Published var isShowingCustomButton = true
|
||||
@State var should = false
|
||||
@ -24,6 +25,12 @@ class SudachiEmulationViewModel: ObservableObject {
|
||||
var doesneedresources = 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?) {
|
||||
self.device = MTLCreateSystemDefaultDevice()
|
||||
self.sudachiGame = game
|
||||
@ -38,22 +45,26 @@ class SudachiEmulationViewModel: ObservableObject {
|
||||
|
||||
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
|
||||
if let sudachiGame = self.sudachiGame {
|
||||
|
||||
if sudachiGame.fileURL == URL(string: "BootMii") {
|
||||
self.sudachi.bootMii()
|
||||
} else {
|
||||
self.sudachi.insert(game: sudachiGame.fileURL)
|
||||
}
|
||||
|
||||
} else {
|
||||
self.sudachi.bootOS()
|
||||
}
|
||||
}
|
||||
|
||||
if UserDefaults.standard.bool(forKey: "pomeloRPC") {
|
||||
startRPCTimer()
|
||||
}
|
||||
|
||||
thread = .init(block: self.step)
|
||||
thread.name = "Pomelo"
|
||||
thread.qualityOfService = .userInteractive
|
||||
@ -74,6 +85,9 @@ class SudachiEmulationViewModel: ObservableObject {
|
||||
private func stopEmulation() {
|
||||
if isRunning {
|
||||
isRunning = false
|
||||
stopGameTimer() // Stop the timer and save elapsed time
|
||||
rpctimer?.invalidate()
|
||||
rpctimer = nil
|
||||
sudachi.exit()
|
||||
thread.cancel()
|
||||
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() {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
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!
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ struct SudachiEmulationView: View {
|
||||
@State private var thread: Thread!
|
||||
@State var uiTabBarController: UITabBarController?
|
||||
@State private var isFirstFrameShown = false
|
||||
@State private var switched = false
|
||||
@State private var wasairplay = false
|
||||
@State private var timer: Timer?
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
@AppStorage("isairplay") private var isairplay: Bool = true
|
||||
@ -31,6 +33,10 @@ struct SudachiEmulationView: View {
|
||||
@AppStorage("showfunnibackground") private var showbackground: Bool = false
|
||||
let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@State private var isInBackground = false
|
||||
@AppStorage("disableMainScreen") var disableMainScreen: Bool = false
|
||||
|
||||
@AppStorage("disableElapsedTime") var disableElapsedTime: Bool = false
|
||||
|
||||
init(game: PomeloGame?) {
|
||||
_game = State(wrappedValue: game)
|
||||
@ -38,83 +44,103 @@ struct SudachiEmulationView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
||||
ZStack {
|
||||
if #available(iOS 18.0, *), showbackground {
|
||||
BackgroundView()
|
||||
.ignoresSafeArea(.all)
|
||||
if (!disableMainScreen && !isairplay) {
|
||||
if #available(iOS 18.0, *), showbackground {
|
||||
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 {
|
||||
VStack {
|
||||
HStack {
|
||||
Spacer()
|
||||
Menu {
|
||||
Button {
|
||||
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 (!disableMainScreen && !isairplay) {
|
||||
if ShowMenuButton {
|
||||
VStack {
|
||||
HStack {
|
||||
Spacer()
|
||||
Menu {
|
||||
Button {
|
||||
if let metalView = mtkview {
|
||||
print("button pressed")
|
||||
|
||||
if let pngData = uiImage.pngData() {
|
||||
// Define the path to save the PNG file
|
||||
if let game = self.game {
|
||||
let fileURL = documentsDir.appendingPathComponent("screenshots/\(game.title)-(\(Date())).png")
|
||||
print(Date())
|
||||
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)")
|
||||
let lastDrawableDisplayed = metalView.currentDrawable?.texture
|
||||
|
||||
if let imageRef = lastDrawableDisplayed?.toImage() {
|
||||
let uiImage: UIImage = UIImage.init(cgImage: imageRef)
|
||||
|
||||
if let pngData = uiImage.pngData() {
|
||||
// Define the path to save the PNG file
|
||||
if let game = self.game {
|
||||
let fileURL = documentsDir.appendingPathComponent("screenshots/\(game.title)-(\(Date())).png")
|
||||
print(Date())
|
||||
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 {
|
||||
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)")
|
||||
}
|
||||
print("Failed to convert UIImage to PNG data")
|
||||
}
|
||||
} 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: {
|
||||
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(
|
||||
// Loading screen overlay on top of MetalView
|
||||
@ -129,37 +155,54 @@ struct SudachiEmulationView: View {
|
||||
UIApplication.shared.isIdleTimerDisabled = true
|
||||
print("AirPlay + \(Air.shared.connected)")
|
||||
|
||||
print(disableMainScreen)
|
||||
print(isairplay)
|
||||
|
||||
// Your existing variables
|
||||
isairplay = Air.shared.connected
|
||||
DispatchQueue.main.async {
|
||||
Air.connection({ fun in
|
||||
print("AirPlay + \(fun)")
|
||||
isairplay = fun
|
||||
})
|
||||
|
||||
if isairplay {
|
||||
Air.play(AnyView(SudachiMetalView(viewModel: viewModel, mtkview: $mtkview, device: $device)))
|
||||
}
|
||||
}
|
||||
|
||||
// Set up initial AirPlay view
|
||||
airplayafter(airplay: false)
|
||||
|
||||
|
||||
if !isairplay {
|
||||
startPollingFirstFrameShowed()
|
||||
}
|
||||
startPollingFirstFrameShowed()
|
||||
}
|
||||
.onDisappear {
|
||||
if !isairplay {
|
||||
stopPollingFirstFrameShowed()
|
||||
}
|
||||
stopPollingFirstFrameShowed()
|
||||
UIApplication.shared.isIdleTimerDisabled = false
|
||||
uiTabBarController?.tabBar.isHidden = false
|
||||
}
|
||||
.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() {
|
||||
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
|
||||
if sudachi.FirstFrameShowed() {
|
||||
withAnimation {
|
||||
isFirstFrameShown = true
|
||||
}
|
||||
if !disableElapsedTime {
|
||||
viewModel.startGameTimer()
|
||||
}
|
||||
stopPollingFirstFrameShowed()
|
||||
}
|
||||
}
|
||||
|
@ -17,15 +17,19 @@ struct SudachiMetalView: View {
|
||||
@Binding var mtkview: MTKView?
|
||||
@Binding var device: MTLDevice?
|
||||
@AppStorage("isairplay") private var isairplay: Bool = true
|
||||
@State var airplaylater: Bool
|
||||
var body: some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
MetalView(device: device) { view in
|
||||
DispatchQueue.main.async {
|
||||
if let metalView = view as? MTKView {
|
||||
mtkview = metalView
|
||||
viewModel.configureSudachi(with: metalView)
|
||||
} else {
|
||||
print("Error: view is not of type MTKView")
|
||||
if !airplaylater {
|
||||
mtkview = metalView
|
||||
viewModel.configureSudachi(with: metalView)
|
||||
} 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
|
||||
DispatchQueue.main.async {
|
||||
if let metalView = view as? MTKView {
|
||||
mtkview = metalView
|
||||
viewModel.configureSudachi(with: metalView)
|
||||
} else {
|
||||
print("Error: view is not of type MTKView")
|
||||
if !airplaylater {
|
||||
mtkview = metalView
|
||||
viewModel.configureSudachi(with: metalView)
|
||||
} else {
|
||||
let sudachi = Sudachi.shared
|
||||
|
||||
sudachi.configure(layer: metalView.layer as! CAMetalLayer, with: metalView.frame.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,14 +53,18 @@ class SudachiScreenView: UIView {
|
||||
return
|
||||
}
|
||||
|
||||
print("Location: \(touch.location(in: primaryScreen))")
|
||||
sudachi.touchBegan(at: touch.location(in: primaryScreen), for: 0)
|
||||
if !userDefaults.bool(forKey: "disabletouch") {
|
||||
print("Location: \(touch.location(in: primaryScreen))")
|
||||
sudachi.touchBegan(at: touch.location(in: primaryScreen), for: 0)
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
super.touchesEnded(touches, with: event)
|
||||
print("Touch Ended")
|
||||
sudachi.touchEnded(for: 0)
|
||||
if !userDefaults.bool(forKey: "disabletouch") {
|
||||
print("Touch Ended")
|
||||
sudachi.touchEnded(for: 0)
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
@ -69,15 +73,22 @@ class SudachiScreenView: UIView {
|
||||
return
|
||||
}
|
||||
|
||||
let location = touch.location(in: primaryScreen)
|
||||
print("Location Moved: \(location)")
|
||||
sudachi.touchMoved(at: location, for: 0)
|
||||
if !userDefaults.bool(forKey: "disabletouch") {
|
||||
let location = touch.location(in: primaryScreen)
|
||||
print("Location Moved: \(location)")
|
||||
sudachi.touchMoved(at: location, for: 0)
|
||||
}
|
||||
}
|
||||
|
||||
func setupSudachiScreen2() {
|
||||
primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
|
||||
primaryScreen.translatesAutoresizingMaskIntoConstraints = false
|
||||
primaryScreen.clipsToBounds = true
|
||||
if userDefaults.bool(forKey: "isairplay") {
|
||||
primaryScreen.layer.backgroundColor = UIColor.black.cgColor
|
||||
} else {
|
||||
primaryScreen.layer.backgroundColor = UIColor.secondarySystemBackground.cgColor
|
||||
}
|
||||
addSubview(primaryScreen)
|
||||
|
||||
fullscreenconstraints = [
|
||||
|
@ -7,7 +7,6 @@ __attribute__((constructor)) static void entry(int argc, char **argv)
|
||||
double systemVersion = [[[UIDevice currentDevice] systemVersion] doubleValue];
|
||||
|
||||
|
||||
|
||||
if (isJITEnabled()) {
|
||||
NSLog(@"yippee");
|
||||
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
|
||||
}
|
||||
|
||||
if (!getEntitlementValue(@"com.apple.developer.kernel.increased-memory-limit")) {
|
||||
NSLog(@"Entitlement Does Not Exist");
|
||||
if (getEntitlementValue(@"com.apple.developer.kernel.increased-memory-limit")) {
|
||||
NSLog(@"Entitlement Does Exist");
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
[defaults setBool:YES forKey:@"entitlementNotExists"];
|
||||
[defaults setBool:YES forKey:@"entitlementExists"];
|
||||
[defaults synchronize]; // Ensure the value is saved immediately
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,8 @@ struct GameGridView: View {
|
||||
@State var selectedGame: PomeloGame?
|
||||
@State var alertMessage: Alert? = nil
|
||||
|
||||
@State var alapsedtime: [String: TimeInterval] = loadElapsedTimeFromJSON()
|
||||
|
||||
var body: some View {
|
||||
let filteredGames = core.games.filter { game in
|
||||
return searchText.isEmpty || game.title.localizedCaseInsensitiveContains(searchText)
|
||||
@ -28,14 +30,47 @@ struct GameGridView: View {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 200))], spacing: 2) {
|
||||
ForEach(0..<filteredGames.count, id: \.self) { index in
|
||||
let game = filteredGames[index] // Use filteredGames here
|
||||
|
||||
var gametime: TimeInterval?
|
||||
|
||||
NavigationLink(destination: SudachiEmulationView(game: game)) {
|
||||
GameIconView(game: game, selectedGame: $selectedGame)
|
||||
.frame(maxWidth: 200, minHeight: 250)
|
||||
}
|
||||
.onAppear {
|
||||
selectedGame = filteredGames.first
|
||||
|
||||
if let game = alapsedtime.first(where: { $0.key == game.title }) {
|
||||
gametime = game.value
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
.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: {
|
||||
do {
|
||||
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() {
|
||||
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 [:]
|
||||
}
|
||||
|
@ -13,22 +13,57 @@ import UniformTypeIdentifiers
|
||||
import Sudachi
|
||||
|
||||
struct GameListView: View {
|
||||
// let games: [PomeloGame]
|
||||
@Binding var core: Core
|
||||
@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 {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 0) {
|
||||
ForEach(core.games.prefix(12)) { game in
|
||||
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 {
|
||||
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 {
|
||||
UIPasteboard.general.string = String(describing: game.programid)
|
||||
} label: {
|
||||
Text("Game ID: \(String(describing: game.programid))")
|
||||
}
|
||||
|
||||
Button {
|
||||
saveImageToIconsFolder(gameImageData: game.imageData, imageName: game.title)
|
||||
} label: {
|
||||
@ -36,7 +71,7 @@ struct GameListView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if core.games.count > 12 {
|
||||
NavigationLink {
|
||||
GameGridView(core: core)
|
||||
@ -49,16 +84,20 @@ struct GameListView: View {
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 70, height: 70)
|
||||
}
|
||||
.foregroundColor(Color.init(uiColor: .darkGray))
|
||||
.foregroundColor(Color(uiColor: .darkGray))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
loadElapsedTime()
|
||||
selectedGame = core.games.first
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Function to load elapsed time
|
||||
private func loadElapsedTime() {
|
||||
alapsedtime = loadElapsedTimeFromJSON()
|
||||
}
|
||||
|
||||
func saveImageToIconsFolder(gameImageData: Data, imageName: String) {
|
||||
|
@ -30,7 +30,7 @@ struct TopBarView: View {
|
||||
|
||||
HStack {
|
||||
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: {
|
||||
Image(systemName: "person.crop.circle.fill")
|
||||
.resizable()
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
import SwiftUI
|
||||
import AppIntents
|
||||
|
||||
infix operator --: LogicalDisjunctionPrecedence
|
||||
|
||||
func --(lhs: Bool, rhs: Bool) -> Bool {
|
||||
@ -17,22 +18,7 @@ func --(lhs: Bool, rhs: Bool) -> Bool {
|
||||
struct PomeloApp: App {
|
||||
|
||||
init() {
|
||||
|
||||
// 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)
|
||||
setMoltenVKSettings()
|
||||
}
|
||||
|
||||
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([])
|
||||
|
77
Pomelo/PomeloRPC/PomeloRPC.swift
Normal file
77
Pomelo/PomeloRPC/PomeloRPC.swift
Normal 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()
|
||||
}
|
@ -23,6 +23,16 @@ struct AdvancedSettingsView: View {
|
||||
@AppStorage("canShowMetalHUD") var canShowMetalHUD: Bool = false
|
||||
@AppStorage("ShowMenuButton") private var ShowMenuButton: Bool = true
|
||||
@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
|
||||
|
||||
var body: some View {
|
||||
@ -57,6 +67,23 @@ struct AdvancedSettingsView: View {
|
||||
.font(.footnote)
|
||||
.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 {
|
||||
Rectangle()
|
||||
.fill(Color(uiColor: UIColor.secondarySystemBackground))
|
||||
@ -157,6 +184,38 @@ struct AdvancedSettingsView: View {
|
||||
.font(.footnote)
|
||||
.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()
|
||||
.fill(Color(uiColor: UIColor.secondarySystemBackground))
|
||||
@ -164,11 +223,32 @@ struct AdvancedSettingsView: View {
|
||||
.frame(width: .infinity, height: 50)
|
||||
.overlay() {
|
||||
HStack {
|
||||
Toggle("Memory Usage Increase", isOn: $kpagetable)
|
||||
Toggle("Disable Touch", isOn: $disableTouch)
|
||||
.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)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
@ -296,3 +376,12 @@ func restoreFoldersFromiCloud() {
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
import SwiftUI
|
||||
|
||||
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("extended-virtual-addressing") private var extended: Bool = false
|
||||
@State var gd = false
|
||||
@ -33,7 +33,7 @@ struct InfoView: View {
|
||||
.font(Font.headline.weight(.bold))
|
||||
Spacer()
|
||||
.frame(height: 10)
|
||||
Text("Increased Memory Limit: \(String(describing: !entitlementNotExists))")
|
||||
Text("Increased Memory Limit: \(String(describing: entitlementExists))")
|
||||
Spacer()
|
||||
.frame(height: 10)
|
||||
}
|
||||
@ -50,6 +50,24 @@ struct InfoView: View {
|
||||
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()
|
||||
|
||||
|
@ -41,6 +41,15 @@ public struct Sudachi {
|
||||
public func bootMii() {
|
||||
sudachiObjC.bootMii()
|
||||
}
|
||||
|
||||
public func halt() {
|
||||
sudachiObjC.halt()
|
||||
}
|
||||
|
||||
public func start() {
|
||||
sudachiObjC.start()
|
||||
}
|
||||
|
||||
public func 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) {
|
||||
// Calling the Objective-C function with both gyroscope and accelerometer data
|
||||
sudachiObjC.virtualControllerGyro(controllerId,
|
||||
deltaTimestamp: deltaTimestamp,
|
||||
gyroX: x,
|
||||
|
@ -53,6 +53,8 @@ typedef NS_ENUM(NSUInteger, VirtualControllerButtonType) {
|
||||
-(void) pause;
|
||||
-(void) play;
|
||||
-(BOOL) ispaused;
|
||||
-(BOOL) halt;
|
||||
-(BOOL) start;
|
||||
-(BOOL) canGetFullPath;
|
||||
-(void) bootMii;
|
||||
-(void) quit;
|
||||
|
@ -112,6 +112,14 @@
|
||||
EmulationSession::GetInstance().PauseEmulation();
|
||||
}
|
||||
|
||||
-(void) halt {
|
||||
EmulationSession::GetInstance().HaltEmulation();
|
||||
}
|
||||
|
||||
-(void) start {
|
||||
EmulationSession::GetInstance().RunEmulation();
|
||||
}
|
||||
|
||||
-(void) play {
|
||||
EmulationSession::GetInstance().UnPauseEmulation();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user