mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-04-23 12:08:59 +02:00
merge more
This commit is contained in:
parent
c8aa6ab747
commit
052a808e9b
11 changed files with 487 additions and 575 deletions
|
|
@ -20,16 +20,11 @@ add_executable(eden-ios
|
||||||
EmulationGame.swift
|
EmulationGame.swift
|
||||||
JoystickView.swift
|
JoystickView.swift
|
||||||
EmulationHandler.swift
|
EmulationHandler.swift
|
||||||
NavView.swift
|
|
||||||
PomeloApp.swift
|
PomeloApp.swift
|
||||||
SettingsView.swift
|
|
||||||
FileManager.swift
|
FileManager.swift
|
||||||
EmulationView.swift
|
EmulationView.swift
|
||||||
LibraryView.swift
|
LibraryView.swift
|
||||||
KeyboardHostingController.swift
|
KeyboardHostingController.swift
|
||||||
MetalView.swift
|
|
||||||
ControllerView.swift
|
|
||||||
InfoView.swift
|
|
||||||
FolderMonitor.swift
|
FolderMonitor.swift
|
||||||
GameButtonView.swift
|
GameButtonView.swift
|
||||||
EmulationScreenView.swift
|
EmulationScreenView.swift
|
||||||
|
|
|
||||||
|
|
@ -1,432 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import GameController
|
|
||||||
|
|
||||||
import SwiftUIJoystick
|
|
||||||
|
|
||||||
struct ControllerView: View {
|
|
||||||
let appui = AppUI.shared
|
|
||||||
@State var isPressed = false
|
|
||||||
@State var controllerconnected = false
|
|
||||||
@State private var x: CGFloat = 0.0
|
|
||||||
@State private var y: CGFloat = 0.0
|
|
||||||
@Environment(\.presentationMode) var presentationMode
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
GeometryReader { geometry in
|
|
||||||
ZStack {
|
|
||||||
if !controllerconnected {
|
|
||||||
OnScreenController(geometry: geometry) // i did this to clean it up as it was quite long lmfao
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
print("checking for controller:")
|
|
||||||
controllerconnected = false
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
setupControllers() // i dont know what half of this shit does
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a dictionary to track controller IDs
|
|
||||||
@State var controllerIDs: [GCController: Int] = [:]
|
|
||||||
|
|
||||||
private func setupControllers() {
|
|
||||||
NotificationCenter.default.addObserver(forName: .GCControllerDidConnect, object: nil, queue: .main) { notification in
|
|
||||||
if let controller = notification.object as? GCController {
|
|
||||||
print("wow controller onstart") // yippeeee
|
|
||||||
self.setupController(controller)
|
|
||||||
self.controllerconnected = true
|
|
||||||
} else {
|
|
||||||
print("not GCController :((((((") // wahhhhhhh
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(forName: .GCControllerDidDisconnect, object: nil, queue: .main) { notification in
|
|
||||||
if let controller = notification.object as? GCController {
|
|
||||||
print("wow controller gone")
|
|
||||||
if self.controllerIDs.isEmpty {
|
|
||||||
controllerconnected = false
|
|
||||||
}
|
|
||||||
self.controllerIDs.removeValue(forKey: controller) // Remove the controller ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GCController.controllers().forEach { controller in
|
|
||||||
print("wow controller")
|
|
||||||
self.controllerconnected = true
|
|
||||||
self.setupController(controller)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupController(_ controller: GCController) {
|
|
||||||
// Assign a unique ID to the controller, max 5 controllers
|
|
||||||
if controllerIDs.count < 6, controllerIDs[controller] == nil {
|
|
||||||
controllerIDs[controller] = controllerIDs.count
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let controllerId = controllerIDs[controller] else { return }
|
|
||||||
|
|
||||||
if let extendedGamepad = controller.extendedGamepad {
|
|
||||||
|
|
||||||
// Handle extended gamepad
|
|
||||||
extendedGamepad.dpad.up.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.directionalPadUp, controllerId: controllerId) : self.touchUpInside(.directionalPadUp, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
|
|
||||||
extendedGamepad.dpad.down.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.directionalPadDown, controllerId: controllerId) : self.touchUpInside(.directionalPadDown, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
extendedGamepad.dpad.left.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.directionalPadLeft, controllerId: controllerId) : self.touchUpInside(.directionalPadLeft, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
extendedGamepad.dpad.right.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.directionalPadRight, controllerId: controllerId) : self.touchUpInside(.directionalPadRight, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
extendedGamepad.buttonOptions?.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.minus, controllerId: controllerId) : self.touchUpInside(.minus, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
extendedGamepad.buttonMenu.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.plus, controllerId: controllerId) : self.touchUpInside(.plus, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
extendedGamepad.buttonA.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.A, controllerId: controllerId) : self.touchUpInside(.A, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
extendedGamepad.buttonB.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.B, controllerId: controllerId) : self.touchUpInside(.B, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
extendedGamepad.buttonX.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.X, controllerId: controllerId) : self.touchUpInside(.X, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
extendedGamepad.buttonY.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.Y, controllerId: controllerId) : self.touchUpInside(.Y, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
extendedGamepad.leftShoulder.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.triggerL, controllerId: controllerId) : self.touchUpInside(.L, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
extendedGamepad.leftTrigger.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.triggerZL, controllerId: controllerId) : self.touchUpInside(.triggerZL, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
extendedGamepad.rightShoulder.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.triggerR, controllerId: controllerId) : self.touchUpInside(.triggerR, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
extendedGamepad.leftThumbstickButton?.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.L, controllerId: controllerId) : self.touchUpInside(.triggerR, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
extendedGamepad.rightThumbstickButton?.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.R, controllerId: controllerId) : self.touchUpInside(.triggerR, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
extendedGamepad.rightTrigger.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.triggerZR, controllerId: controllerId) : self.touchUpInside(.triggerZR, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
extendedGamepad.buttonHome?.pressedChangedHandler = { button, value, pressed in
|
|
||||||
if pressed {
|
|
||||||
appui.exit()
|
|
||||||
presentationMode.wrappedValue.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
extendedGamepad.leftThumbstick.valueChangedHandler = { dpad, x, y in
|
|
||||||
self.appui.thumbstickMoved(analog: .left, x: x, y: y, controllerid: controllerId)
|
|
||||||
}
|
|
||||||
|
|
||||||
extendedGamepad.rightThumbstick.valueChangedHandler = { dpad, x, y in
|
|
||||||
self.appui.thumbstickMoved(analog: .right, x: x, y: y, controllerid: controllerId)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let motion = controller.motion {
|
|
||||||
var lastTimestamp = Date().timeIntervalSince1970 // Initialize timestamp when motion starts
|
|
||||||
|
|
||||||
motion.valueChangedHandler = { motion in
|
|
||||||
// Get current time
|
|
||||||
let currentTimestamp = Date().timeIntervalSince1970
|
|
||||||
let deltaTimestamp = Int32((currentTimestamp - lastTimestamp) * 1000) // Difference in milliseconds
|
|
||||||
|
|
||||||
// Update last timestamp
|
|
||||||
lastTimestamp = currentTimestamp
|
|
||||||
|
|
||||||
// Get gyroscope data
|
|
||||||
let gyroX = motion.rotationRate.x
|
|
||||||
let gyroY = motion.rotationRate.y
|
|
||||||
let gyroZ = motion.rotationRate.z
|
|
||||||
|
|
||||||
// Get accelerometer data
|
|
||||||
let accelX = motion.gravity.x + motion.userAcceleration.x
|
|
||||||
let accelY = motion.gravity.y + motion.userAcceleration.y
|
|
||||||
let accelZ = motion.gravity.z + motion.userAcceleration.z
|
|
||||||
|
|
||||||
print("\(gyroX), \(gyroY), \(gyroZ), \(accelX), \(accelY), \(accelZ)")
|
|
||||||
|
|
||||||
// Call your gyroMoved function with the motion data
|
|
||||||
appui.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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if let microGamepad = controller.microGamepad {
|
|
||||||
// Handle micro gamepad
|
|
||||||
microGamepad.dpad.up.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.directionalPadUp, controllerId: controllerId) : self.touchUpInside(.directionalPadUp, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
microGamepad.dpad.down.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.directionalPadDown, controllerId: controllerId) : self.touchUpInside(.directionalPadDown, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
microGamepad.dpad.left.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.directionalPadLeft, controllerId: controllerId) : self.touchUpInside(.directionalPadLeft, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
microGamepad.dpad.right.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.directionalPadRight, controllerId: controllerId) : self.touchUpInside(.directionalPadRight, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
microGamepad.buttonA.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.A, controllerId: controllerId) : self.touchUpInside(.A, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
microGamepad.buttonX.pressedChangedHandler = { button, value, pressed in
|
|
||||||
pressed ? self.touchDown(.X, controllerId: controllerId) : self.touchUpInside(.X, controllerId: controllerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func touchDown(_ button: VirtualControllerButtonType, controllerId: Int) {
|
|
||||||
appui.virtualControllerButtonDown(button: button, controllerid: controllerId) }
|
|
||||||
|
|
||||||
private func touchUpInside(_ button: VirtualControllerButtonType, controllerId: Int) {
|
|
||||||
appui.virtualControllerButtonUp(button: button, controllerid: controllerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct OnScreenController: View {
|
|
||||||
@State var geometry: GeometryProxy
|
|
||||||
var body: some View {
|
|
||||||
if geometry.size.height > geometry.size.width && UIDevice.current.userInterfaceIdiom != .pad {
|
|
||||||
// portrait
|
|
||||||
VStack {
|
|
||||||
Spacer()
|
|
||||||
VStack {
|
|
||||||
HStack {
|
|
||||||
VStack {
|
|
||||||
ShoulderButtonsViewLeft()
|
|
||||||
ZStack {
|
|
||||||
Joystick()
|
|
||||||
DPadView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
VStack {
|
|
||||||
ShoulderButtonsViewRight()
|
|
||||||
ZStack {
|
|
||||||
Joystick(iscool: true) // hope this works
|
|
||||||
ABXYView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
HStack {
|
|
||||||
ButtonView(button: .plus).padding(.horizontal, 40)
|
|
||||||
ButtonView(button: .minus).padding(.horizontal, 40)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.bottom, geometry.size.height / 3.2) // very broken
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// could be landscape
|
|
||||||
VStack {
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
ButtonView(button: .home)
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
VStack {
|
|
||||||
HStack {
|
|
||||||
|
|
||||||
// gotta fuckin add + and - now
|
|
||||||
VStack {
|
|
||||||
ShoulderButtonsViewLeft()
|
|
||||||
ZStack {
|
|
||||||
Joystick()
|
|
||||||
DPadView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
VStack {
|
|
||||||
Spacer()
|
|
||||||
ButtonView(button: .plus) // Adding the + button
|
|
||||||
}
|
|
||||||
VStack {
|
|
||||||
Spacer()
|
|
||||||
ButtonView(button: .minus) // Adding the - button
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
VStack {
|
|
||||||
ShoulderButtonsViewRight()
|
|
||||||
ZStack {
|
|
||||||
Joystick(iscool: true) // hope this work s
|
|
||||||
ABXYView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(.bottom, geometry.size.height / 11) // also extremally broken (
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ShoulderButtonsViewLeft: View {
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
ButtonView(button: .triggerZL)
|
|
||||||
.padding(.horizontal)
|
|
||||||
ButtonView(button: .triggerL)
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
.frame(width: 160, height: 20)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ShoulderButtonsViewRight: View {
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
ButtonView(button: .triggerR)
|
|
||||||
.padding(.horizontal)
|
|
||||||
ButtonView(button: .triggerZR)
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
.frame(width: 160, height: 20)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DPadView: View {
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
ButtonView(button: .directionalPadUp)
|
|
||||||
HStack {
|
|
||||||
ButtonView(button: .directionalPadLeft)
|
|
||||||
Spacer(minLength: 20)
|
|
||||||
ButtonView(button: .directionalPadRight)
|
|
||||||
}
|
|
||||||
ButtonView(button: .directionalPadDown)
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
.frame(width: 145, height: 145)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ABXYView: View {
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
ButtonView(button: .X)
|
|
||||||
HStack {
|
|
||||||
ButtonView(button: .Y)
|
|
||||||
Spacer(minLength: 20)
|
|
||||||
ButtonView(button: .A)
|
|
||||||
}
|
|
||||||
ButtonView(button: .B)
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
.frame(width: 145, height: 145)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Haptics {
|
|
||||||
static let shared = Haptics()
|
|
||||||
private init() { }
|
|
||||||
func play(_ feedbackStyle: UIImpactFeedbackGenerator.FeedbackStyle) {
|
|
||||||
print("haptics")
|
|
||||||
UIImpactFeedbackGenerator(style: feedbackStyle).impactOccurred()
|
|
||||||
}
|
|
||||||
func notify(_ feedbackType: UINotificationFeedbackGenerator.FeedbackType) {
|
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(feedbackType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ButtonView: View {
|
|
||||||
var button: VirtualControllerButtonType
|
|
||||||
@StateObject private var viewModel: EmulationViewModel = EmulationViewModel(game: nil)
|
|
||||||
let appui = AppUI.shared
|
|
||||||
@State var mtkView: MTKView?
|
|
||||||
@State var width: CGFloat = 45
|
|
||||||
@State var height: CGFloat = 45
|
|
||||||
@State var isPressed = false
|
|
||||||
var id: Int {
|
|
||||||
if onscreenjoy {
|
|
||||||
return 8
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
@AppStorage("onscreenhandheld") var onscreenjoy: Bool = false
|
|
||||||
@Environment(\.colorScheme) var colorScheme
|
|
||||||
@Environment(\.presentationMode) var presentationMode
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Image(systemName: buttonText)
|
|
||||||
.resizable()
|
|
||||||
.frame(width: width, height: height)
|
|
||||||
.foregroundColor(colorScheme == .dark ? Color.gray : Color.gray)
|
|
||||||
.opacity(isPressed ? 0.5 : 1)
|
|
||||||
.gesture(
|
|
||||||
DragGesture(minimumDistance: 0)
|
|
||||||
.onChanged { _ in
|
|
||||||
if !self.isPressed {
|
|
||||||
self.isPressed = true
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
if button == .home {
|
|
||||||
presentationMode.wrappedValue.dismiss()
|
|
||||||
appui.exit()
|
|
||||||
} else {
|
|
||||||
appui.virtualControllerButtonDown(button: button, controllerid: id)
|
|
||||||
Haptics.shared.play(.heavy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onEnded { _ in
|
|
||||||
self.isPressed = false
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
if button != .home {
|
|
||||||
appui.virtualControllerButtonUp(button: button, controllerid: id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.onAppear() {
|
|
||||||
if button == .triggerL || button == .triggerZL || button == .triggerZR || button == .triggerR {
|
|
||||||
width = 65
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if button == .minus || button == .plus || button == .home {
|
|
||||||
width = 35
|
|
||||||
height = 35
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var buttonText: String {
|
|
||||||
switch button {
|
|
||||||
case .A: return "a.circle.fill"
|
|
||||||
case .B: return "b.circle.fill"
|
|
||||||
case .X: return "x.circle.fill"
|
|
||||||
case .Y: return "y.circle.fill"
|
|
||||||
case .directionalPadUp: return "arrowtriangle.up.circle.fill"
|
|
||||||
case .directionalPadDown: return "arrowtriangle.down.circle.fill"
|
|
||||||
case .directionalPadLeft: return "arrowtriangle.left.circle.fill"
|
|
||||||
case .directionalPadRight: return "arrowtriangle.right.circle.fill"
|
|
||||||
case .triggerZL: return"zl.rectangle.roundedtop.fill"
|
|
||||||
case .triggerZR: return "zr.rectangle.roundedtop.fill"
|
|
||||||
case .triggerL: return "l.rectangle.roundedbottom.fill"
|
|
||||||
case .triggerR: return "r.rectangle.roundedbottom.fill"
|
|
||||||
case .plus: return "plus.circle.fill"
|
|
||||||
case .minus: return "minus.circle.fill"
|
|
||||||
case .home: return "house.circle.fill"
|
|
||||||
default: return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9,6 +9,430 @@ import Foundation
|
||||||
import GameController
|
import GameController
|
||||||
import UIKit
|
import UIKit
|
||||||
import SwiftUIIntrospect
|
import SwiftUIIntrospect
|
||||||
|
import SwiftUIJoystick
|
||||||
|
import Metal
|
||||||
|
|
||||||
|
struct MetalView: UIViewRepresentable {
|
||||||
|
let device: MTLDevice?
|
||||||
|
let configure: (UIView) -> Void
|
||||||
|
func makeUIView(context: Context) -> EmulationScreenView {
|
||||||
|
let view = EmulationScreenView()
|
||||||
|
configure(view.primaryScreen)
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
func updateUIView(_ uiView: EmulationScreenView, context: Context) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ControllerView: View {
|
||||||
|
let appui = AppUI.shared
|
||||||
|
@State var isPressed = false
|
||||||
|
@State var controllerconnected = false
|
||||||
|
@State private var x: CGFloat = 0.0
|
||||||
|
@State private var y: CGFloat = 0.0
|
||||||
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { geometry in
|
||||||
|
ZStack {
|
||||||
|
if !controllerconnected {
|
||||||
|
OnScreenController(geometry: geometry) // i did this to clean it up as it was quite long lmfao
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
print("checking for controller:")
|
||||||
|
controllerconnected = false
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
setupControllers() // i dont know what half of this shit does
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a dictionary to track controller IDs
|
||||||
|
@State var controllerIDs: [GCController: Int] = [:]
|
||||||
|
|
||||||
|
private func setupControllers() {
|
||||||
|
NotificationCenter.default.addObserver(forName: .GCControllerDidConnect, object: nil, queue: .main) { notification in
|
||||||
|
if let controller = notification.object as? GCController {
|
||||||
|
print("wow controller onstart") // yippeeee
|
||||||
|
self.setupController(controller)
|
||||||
|
self.controllerconnected = true
|
||||||
|
} else {
|
||||||
|
print("not GCController :((((((") // wahhhhhhh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(forName: .GCControllerDidDisconnect, object: nil, queue: .main) { notification in
|
||||||
|
if let controller = notification.object as? GCController {
|
||||||
|
print("wow controller gone")
|
||||||
|
if self.controllerIDs.isEmpty {
|
||||||
|
controllerconnected = false
|
||||||
|
}
|
||||||
|
self.controllerIDs.removeValue(forKey: controller) // Remove the controller ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GCController.controllers().forEach { controller in
|
||||||
|
print("wow controller")
|
||||||
|
self.controllerconnected = true
|
||||||
|
self.setupController(controller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupController(_ controller: GCController) {
|
||||||
|
// Assign a unique ID to the controller, max 5 controllers
|
||||||
|
if controllerIDs.count < 6, controllerIDs[controller] == nil {
|
||||||
|
controllerIDs[controller] = controllerIDs.count
|
||||||
|
}
|
||||||
|
guard let controllerId = controllerIDs[controller] else { return }
|
||||||
|
if let extendedGamepad = controller.extendedGamepad {
|
||||||
|
// Handle extended gamepad
|
||||||
|
extendedGamepad.dpad.up.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.directionalPadUp, controllerId: controllerId) : self.touchUpInside(.directionalPadUp, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.dpad.down.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.directionalPadDown, controllerId: controllerId) : self.touchUpInside(.directionalPadDown, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.dpad.left.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.directionalPadLeft, controllerId: controllerId) : self.touchUpInside(.directionalPadLeft, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.dpad.right.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.directionalPadRight, controllerId: controllerId) : self.touchUpInside(.directionalPadRight, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.buttonOptions?.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.minus, controllerId: controllerId) : self.touchUpInside(.minus, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.buttonMenu.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.plus, controllerId: controllerId) : self.touchUpInside(.plus, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.buttonA.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.A, controllerId: controllerId) : self.touchUpInside(.A, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.buttonB.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.B, controllerId: controllerId) : self.touchUpInside(.B, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.buttonX.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.X, controllerId: controllerId) : self.touchUpInside(.X, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.buttonY.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.Y, controllerId: controllerId) : self.touchUpInside(.Y, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.leftShoulder.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.triggerL, controllerId: controllerId) : self.touchUpInside(.L, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.leftTrigger.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.triggerZL, controllerId: controllerId) : self.touchUpInside(.triggerZL, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.rightShoulder.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.triggerR, controllerId: controllerId) : self.touchUpInside(.triggerR, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.leftThumbstickButton?.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.L, controllerId: controllerId) : self.touchUpInside(.triggerR, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.rightThumbstickButton?.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.R, controllerId: controllerId) : self.touchUpInside(.triggerR, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.rightTrigger.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.triggerZR, controllerId: controllerId) : self.touchUpInside(.triggerZR, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.buttonHome?.pressedChangedHandler = { button, value, pressed in
|
||||||
|
if pressed {
|
||||||
|
appui.exit()
|
||||||
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extendedGamepad.leftThumbstick.valueChangedHandler = { dpad, x, y in
|
||||||
|
self.appui.thumbstickMoved(analog: .left, x: x, y: y, controllerid: controllerId)
|
||||||
|
}
|
||||||
|
extendedGamepad.rightThumbstick.valueChangedHandler = { dpad, x, y in
|
||||||
|
self.appui.thumbstickMoved(analog: .right, x: x, y: y, controllerid: controllerId)
|
||||||
|
}
|
||||||
|
if let motion = controller.motion {
|
||||||
|
var lastTimestamp = Date().timeIntervalSince1970 // Initialize timestamp when motion starts
|
||||||
|
motion.valueChangedHandler = { motion in
|
||||||
|
// Get current time
|
||||||
|
let currentTimestamp = Date().timeIntervalSince1970
|
||||||
|
let deltaTimestamp = Int32((currentTimestamp - lastTimestamp) * 1000) // Difference in milliseconds
|
||||||
|
|
||||||
|
// Update last timestamp
|
||||||
|
lastTimestamp = currentTimestamp
|
||||||
|
|
||||||
|
// Get gyroscope data
|
||||||
|
let gyroX = motion.rotationRate.x
|
||||||
|
let gyroY = motion.rotationRate.y
|
||||||
|
let gyroZ = motion.rotationRate.z
|
||||||
|
|
||||||
|
// Get accelerometer data
|
||||||
|
let accelX = motion.gravity.x + motion.userAcceleration.x
|
||||||
|
let accelY = motion.gravity.y + motion.userAcceleration.y
|
||||||
|
let accelZ = motion.gravity.z + motion.userAcceleration.z
|
||||||
|
|
||||||
|
print("\(gyroX), \(gyroY), \(gyroZ), \(accelX), \(accelY), \(accelZ)")
|
||||||
|
|
||||||
|
// Call your gyroMoved function with the motion data
|
||||||
|
appui.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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let microGamepad = controller.microGamepad {
|
||||||
|
// Handle micro gamepad
|
||||||
|
microGamepad.dpad.up.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.directionalPadUp, controllerId: controllerId) : self.touchUpInside(.directionalPadUp, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
microGamepad.dpad.down.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.directionalPadDown, controllerId: controllerId) : self.touchUpInside(.directionalPadDown, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
microGamepad.dpad.left.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.directionalPadLeft, controllerId: controllerId) : self.touchUpInside(.directionalPadLeft, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
microGamepad.dpad.right.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.directionalPadRight, controllerId: controllerId) : self.touchUpInside(.directionalPadRight, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
microGamepad.buttonA.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.A, controllerId: controllerId) : self.touchUpInside(.A, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
microGamepad.buttonX.pressedChangedHandler = { button, value, pressed in
|
||||||
|
pressed ? self.touchDown(.X, controllerId: controllerId) : self.touchUpInside(.X, controllerId: controllerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func touchDown(_ button: VirtualControllerButtonType, controllerId: Int) {
|
||||||
|
appui.virtualControllerButtonDown(button: button, controllerid: controllerId) }
|
||||||
|
|
||||||
|
private func touchUpInside(_ button: VirtualControllerButtonType, controllerId: Int) {
|
||||||
|
appui.virtualControllerButtonUp(button: button, controllerid: controllerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OnScreenController: View {
|
||||||
|
@State var geometry: GeometryProxy
|
||||||
|
var body: some View {
|
||||||
|
if geometry.size.height > geometry.size.width && UIDevice.current.userInterfaceIdiom != .pad {
|
||||||
|
// portrait
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
VStack {
|
||||||
|
ShoulderButtonsViewLeft()
|
||||||
|
ZStack {
|
||||||
|
Joystick()
|
||||||
|
DPadView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
VStack {
|
||||||
|
ShoulderButtonsViewRight()
|
||||||
|
ZStack {
|
||||||
|
Joystick(iscool: true) // hope this works
|
||||||
|
ABXYView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
ButtonView(button: .plus).padding(.horizontal, 40)
|
||||||
|
ButtonView(button: .minus).padding(.horizontal, 40)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.bottom, geometry.size.height / 3.2) // very broken
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// could be landscape
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
ButtonView(button: .home)
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
|
||||||
|
// gotta fuckin add + and - now
|
||||||
|
VStack {
|
||||||
|
ShoulderButtonsViewLeft()
|
||||||
|
ZStack {
|
||||||
|
Joystick()
|
||||||
|
DPadView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
ButtonView(button: .plus) // Adding the + button
|
||||||
|
}
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
ButtonView(button: .minus) // Adding the - button
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
VStack {
|
||||||
|
ShoulderButtonsViewRight()
|
||||||
|
ZStack {
|
||||||
|
Joystick(iscool: true) // hope this work s
|
||||||
|
ABXYView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.bottom, geometry.size.height / 11) // also extremally broken (
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ShoulderButtonsViewLeft: View {
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
ButtonView(button: .triggerZL).padding(.horizontal)
|
||||||
|
ButtonView(button: .triggerL).padding(.horizontal)
|
||||||
|
}
|
||||||
|
.frame(width: 160, height: 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ShoulderButtonsViewRight: View {
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
ButtonView(button: .triggerR).padding(.horizontal)
|
||||||
|
ButtonView(button: .triggerZR).padding(.horizontal)
|
||||||
|
}
|
||||||
|
.frame(width: 160, height: 20)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DPadView: View {
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
ButtonView(button: .directionalPadUp)
|
||||||
|
HStack {
|
||||||
|
ButtonView(button: .directionalPadLeft)
|
||||||
|
Spacer(minLength: 20)
|
||||||
|
ButtonView(button: .directionalPadRight)
|
||||||
|
}
|
||||||
|
ButtonView(button: .directionalPadDown).padding(.horizontal)
|
||||||
|
}
|
||||||
|
.frame(width: 145, height: 145)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ABXYView: View {
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
ButtonView(button: .X)
|
||||||
|
HStack {
|
||||||
|
ButtonView(button: .Y)
|
||||||
|
Spacer(minLength: 20)
|
||||||
|
ButtonView(button: .A)
|
||||||
|
}
|
||||||
|
ButtonView(button: .B).padding(.horizontal)
|
||||||
|
}
|
||||||
|
.frame(width: 145, height: 145)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Haptics {
|
||||||
|
static let shared = Haptics()
|
||||||
|
private init() { }
|
||||||
|
func play(_ feedbackStyle: UIImpactFeedbackGenerator.FeedbackStyle) {
|
||||||
|
print("haptics")
|
||||||
|
UIImpactFeedbackGenerator(style: feedbackStyle).impactOccurred()
|
||||||
|
}
|
||||||
|
func notify(_ feedbackType: UINotificationFeedbackGenerator.FeedbackType) {
|
||||||
|
UINotificationFeedbackGenerator().notificationOccurred(feedbackType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ButtonView: View {
|
||||||
|
var button: VirtualControllerButtonType
|
||||||
|
@StateObject private var viewModel: EmulationViewModel = EmulationViewModel(game: nil)
|
||||||
|
let appui = AppUI.shared
|
||||||
|
@State var mtkView: MTKView?
|
||||||
|
@State var width: CGFloat = 45
|
||||||
|
@State var height: CGFloat = 45
|
||||||
|
@State var isPressed = false
|
||||||
|
var id: Int {
|
||||||
|
if onscreenjoy {
|
||||||
|
return 8
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
@AppStorage("onscreenhandheld") var onscreenjoy: Bool = false
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Image(systemName: buttonText)
|
||||||
|
.resizable()
|
||||||
|
.frame(width: width, height: height)
|
||||||
|
.foregroundColor(colorScheme == .dark ? Color.gray : Color.gray)
|
||||||
|
.opacity(isPressed ? 0.5 : 1)
|
||||||
|
.gesture(
|
||||||
|
DragGesture(minimumDistance: 0)
|
||||||
|
.onChanged { _ in
|
||||||
|
if !self.isPressed {
|
||||||
|
self.isPressed = true
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if button == .home {
|
||||||
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
appui.exit()
|
||||||
|
} else {
|
||||||
|
appui.virtualControllerButtonDown(button: button, controllerid: id)
|
||||||
|
Haptics.shared.play(.heavy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onEnded { _ in
|
||||||
|
self.isPressed = false
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if button != .home {
|
||||||
|
appui.virtualControllerButtonUp(button: button, controllerid: id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.onAppear() {
|
||||||
|
if button == .triggerL || button == .triggerZL || button == .triggerZR || button == .triggerR {
|
||||||
|
width = 65
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if button == .minus || button == .plus || button == .home {
|
||||||
|
width = 35
|
||||||
|
height = 35
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var buttonText: String {
|
||||||
|
switch button {
|
||||||
|
case .A: return "a.circle.fill"
|
||||||
|
case .B: return "b.circle.fill"
|
||||||
|
case .X: return "x.circle.fill"
|
||||||
|
case .Y: return "y.circle.fill"
|
||||||
|
case .directionalPadUp: return "arrowtriangle.up.circle.fill"
|
||||||
|
case .directionalPadDown: return "arrowtriangle.down.circle.fill"
|
||||||
|
case .directionalPadLeft: return "arrowtriangle.left.circle.fill"
|
||||||
|
case .directionalPadRight: return "arrowtriangle.right.circle.fill"
|
||||||
|
case .triggerZL: return"zl.rectangle.roundedtop.fill"
|
||||||
|
case .triggerZR: return "zr.rectangle.roundedtop.fill"
|
||||||
|
case .triggerL: return "l.rectangle.roundedbottom.fill"
|
||||||
|
case .triggerR: return "r.rectangle.roundedbottom.fill"
|
||||||
|
case .plus: return "plus.circle.fill"
|
||||||
|
case .minus: return "minus.circle.fill"
|
||||||
|
case .home: return "house.circle.fill"
|
||||||
|
default: return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct EmulationView: View {
|
struct EmulationView: View {
|
||||||
@StateObject private var viewModel: EmulationViewModel
|
@StateObject private var viewModel: EmulationViewModel
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
import Zip
|
import Zip
|
||||||
|
|
||||||
struct Core : Comparable, Hashable {
|
struct Core : Comparable, Hashable {
|
||||||
|
|
@ -40,55 +39,6 @@ struct Core : Comparable, Hashable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class YuzuFileManager {
|
|
||||||
static var shared = YuzuFileManager()
|
|
||||||
|
|
||||||
func directories() -> [String : [String : String]] {
|
|
||||||
[
|
|
||||||
"themes" : [:],
|
|
||||||
"amiibo" : [:],
|
|
||||||
"cache" : [:],
|
|
||||||
"config" : [:],
|
|
||||||
"crash_dumps" : [:],
|
|
||||||
"dump" : [:],
|
|
||||||
"keys" : [:],
|
|
||||||
"load" : [:],
|
|
||||||
"log" : [:],
|
|
||||||
"nand" : [:],
|
|
||||||
"play_time" : [:],
|
|
||||||
"roms" : [:],
|
|
||||||
"screenshots" : [:],
|
|
||||||
"sdmc" : [:],
|
|
||||||
"shader" : [:],
|
|
||||||
"tas" : [:],
|
|
||||||
"icons" : [:]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
func createdirectories() throws {
|
|
||||||
let documentdir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
||||||
try directories().forEach() { directory, filename in
|
|
||||||
let directoryURL = documentdir.appendingPathComponent(directory)
|
|
||||||
|
|
||||||
if !FileManager.default.fileExists(atPath: directoryURL.path) {
|
|
||||||
print("creating dir at \(directoryURL.path)") // yippee
|
|
||||||
try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: false, attributes: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DetectKeys() -> (Bool, Bool) {
|
|
||||||
var prodkeys = false
|
|
||||||
var titlekeys = false
|
|
||||||
let filemanager = FileManager.default
|
|
||||||
let documentdir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
||||||
let KeysFolderURL = documentdir.appendingPathComponent("keys")
|
|
||||||
prodkeys = filemanager.fileExists(atPath: KeysFolderURL.appendingPathComponent("prod.keys").path)
|
|
||||||
titlekeys = filemanager.fileExists(atPath: KeysFolderURL.appendingPathComponent("title.keys").path)
|
|
||||||
return (prodkeys, titlekeys)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum LibManError : Error {
|
enum LibManError : Error {
|
||||||
case ripenum, urlgobyebye
|
case ripenum, urlgobyebye
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +47,6 @@ class LibraryManager {
|
||||||
static let shared = LibraryManager()
|
static let shared = LibraryManager()
|
||||||
let documentdir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("roms", conformingTo: .folder)
|
let documentdir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("roms", conformingTo: .folder)
|
||||||
|
|
||||||
|
|
||||||
func removerom(_ game: EmulationGame) throws {
|
func removerom(_ game: EmulationGame) throws {
|
||||||
do {
|
do {
|
||||||
try FileManager.default.removeItem(at: game.fileURL)
|
try FileManager.default.removeItem(at: game.fileURL)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,18 @@ import UIKit
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
|
struct SettingsView: View {
|
||||||
|
@State var core: Core
|
||||||
|
@State var showprompt = false
|
||||||
|
|
||||||
|
@AppStorage("icon") var iconused = 1
|
||||||
|
var body: some View {
|
||||||
|
NavigationStack {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct GameIconView: View {
|
struct GameIconView: View {
|
||||||
var game: EmulationGame
|
var game: EmulationGame
|
||||||
@Binding var selectedGame: EmulationGame?
|
@Binding var selectedGame: EmulationGame?
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ struct GameListView: View {
|
||||||
guard let EmulationGame = game as? PoYuzume else { return false }
|
guard let EmulationGame = game as? PoYuzume else { return false }
|
||||||
return searchText.isEmpty || EmulationGame.title.localizedCaseInsensitiveContains(searchText)
|
return searchText.isEmpty || EmulationGame.title.localizedCaseInsensitiveContains(searchText)
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack {
|
VStack {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
|
||||||
|
|
||||||
struct LibraryView: View {
|
struct LibraryView: View {
|
||||||
@Binding var core: Core
|
@Binding var core: Core
|
||||||
@State var isGridView: Bool = true
|
@State var isGridView: Bool = true
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Metal
|
|
||||||
|
|
||||||
|
|
||||||
struct MetalView: UIViewRepresentable {
|
|
||||||
let device: MTLDevice?
|
|
||||||
let configure: (UIView) -> Void
|
|
||||||
|
|
||||||
func makeUIView(context: Context) -> EmulationScreenView {
|
|
||||||
let view = EmulationScreenView()
|
|
||||||
configure(view.primaryScreen)
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIView(_ uiView: EmulationScreenView, context: Context) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct BootOSView: View {
|
|
||||||
@Binding var core: Core
|
|
||||||
@Binding var currentnavigarion: Int
|
|
||||||
@State var appui = AppUI.shared
|
|
||||||
@AppStorage("cangetfullpath") var canGetFullPath: Bool = false
|
|
||||||
var body: some View {
|
|
||||||
if (appui.canGetFullPath() -- canGetFullPath) {
|
|
||||||
EmulationView(game: nil)
|
|
||||||
} else {
|
|
||||||
VStack {
|
|
||||||
Text("Unable Launch Switch OS")
|
|
||||||
.font(.largeTitle)
|
|
||||||
.padding()
|
|
||||||
Text("You do not have the Switch Home Menu Files Needed to launch the Ηome Menu")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NavView: View {
|
|
||||||
@Binding var core: Core
|
|
||||||
@State private var selectedTab = 0
|
|
||||||
var body: some View {
|
|
||||||
TabView(selection: $selectedTab) {
|
|
||||||
LibraryView(core: $core)
|
|
||||||
.tabItem { Label("Library", systemImage: "rectangle.on.rectangle") }
|
|
||||||
.tag(0)
|
|
||||||
BootOSView(core: $core, currentnavigarion: $selectedTab)
|
|
||||||
.toolbar(.hidden, for: .tabBar)
|
|
||||||
.tabItem { Label("Boot OS", systemImage: "house") }
|
|
||||||
.tag(1)
|
|
||||||
SettingsView(core: core)
|
|
||||||
.tabItem { Label("Settings", systemImage: "gear") }
|
|
||||||
.tag(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,6 +4,9 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Zip
|
||||||
|
|
||||||
infix operator --: LogicalDisjunctionPrecedence
|
infix operator --: LogicalDisjunctionPrecedence
|
||||||
|
|
||||||
|
|
@ -11,10 +14,58 @@ func --(lhs: Bool, rhs: Bool) -> Bool {
|
||||||
return lhs || rhs
|
return lhs || rhs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class YuzuFileManager {
|
||||||
|
static var shared = YuzuFileManager()
|
||||||
|
func directories() -> [String : [String : String]] {
|
||||||
|
[
|
||||||
|
"themes" : [:],
|
||||||
|
"amiibo" : [:],
|
||||||
|
"cache" : [:],
|
||||||
|
"config" : [:],
|
||||||
|
"crash_dumps" : [:],
|
||||||
|
"dump" : [:],
|
||||||
|
"keys" : [:],
|
||||||
|
"load" : [:],
|
||||||
|
"log" : [:],
|
||||||
|
"nand" : [:],
|
||||||
|
"play_time" : [:],
|
||||||
|
"roms" : [:],
|
||||||
|
"screenshots" : [:],
|
||||||
|
"sdmc" : [:],
|
||||||
|
"shader" : [:],
|
||||||
|
"tas" : [:],
|
||||||
|
"icons" : [:]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
func createdirectories() throws {
|
||||||
|
let documentdir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||||
|
try directories().forEach() { directory, filename in
|
||||||
|
let directoryURL = documentdir.appendingPathComponent(directory)
|
||||||
|
|
||||||
|
if !FileManager.default.fileExists(atPath: directoryURL.path) {
|
||||||
|
print("creating dir at \(directoryURL.path)") // yippee
|
||||||
|
try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: false, attributes: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@State var core = Core(games: [], root: FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0])
|
@State var core = Core(games: [], root: FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0])
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HomeView(core: core).onAppear() {
|
HomeView(core: core).onAppear() {
|
||||||
|
do {
|
||||||
|
try YuzuFileManager.shared.createdirectories() // this took a while to create the proper directories
|
||||||
|
do {
|
||||||
|
core = try LibraryManager.shared.library() // this shit is like you tried to throw a egg into a blender with no lid on
|
||||||
|
} catch {
|
||||||
|
print("Failed to fetch library: \(error)") // aaaaaaaaa
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Failed to create directories: \(error)") // i wonder why hmmmmmmm
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct SettingsView: View {
|
|
||||||
@State var core: Core
|
|
||||||
@State var showprompt = false
|
|
||||||
|
|
||||||
@AppStorage("icon") var iconused = 1
|
|
||||||
var body: some View {
|
|
||||||
NavigationStack {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue