From feaedd02f7ba1fff6597f8fefdca862367e5fb97 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 19 Feb 2026 03:42:19 +0000 Subject: [PATCH] sudachi ios stuff --- src/ios/Eden/AppUI-Bridging-Header.h | 11 + src/ios/Eden/AppUI.swift | 108 ++++ .../AppUIGameInformation.h | 26 + .../AppUIGameInformation.mm | 434 +++++++++++++ src/ios/Eden/Wrapper/AppUIObjC.h | 92 +++ src/ios/Eden/Wrapper/AppUIObjC.mm | 258 ++++++++ src/ios/Eden/Wrapper/Config/Config.h | 568 ++++++++++++++++++ src/ios/Eden/Wrapper/Config/Config.mm | 330 ++++++++++ .../DirectoryManager/DirectoryManager.h | 10 + .../DirectoryManager/DirectoryManager.mm | 16 + .../EmulationSession/EmulationSession.h | 98 +++ .../EmulationSession/EmulationSession.mm | 528 ++++++++++++++++ .../Wrapper/EmulationWindow/EmulationWindow.h | 80 +++ .../EmulationWindow/EmulationWindow.mm | 85 +++ 14 files changed, 2644 insertions(+) create mode 100644 src/ios/Eden/AppUI-Bridging-Header.h create mode 100644 src/ios/Eden/AppUI.swift create mode 100644 src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.h create mode 100644 src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm create mode 100644 src/ios/Eden/Wrapper/AppUIObjC.h create mode 100644 src/ios/Eden/Wrapper/AppUIObjC.mm create mode 100644 src/ios/Eden/Wrapper/Config/Config.h create mode 100644 src/ios/Eden/Wrapper/Config/Config.mm create mode 100644 src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h create mode 100644 src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm create mode 100644 src/ios/Eden/Wrapper/EmulationSession/EmulationSession.h create mode 100644 src/ios/Eden/Wrapper/EmulationSession/EmulationSession.mm create mode 100644 src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.h create mode 100644 src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.mm diff --git a/src/ios/Eden/AppUI-Bridging-Header.h b/src/ios/Eden/AppUI-Bridging-Header.h new file mode 100644 index 0000000000..a75f43550d --- /dev/null +++ b/src/ios/Eden/AppUI-Bridging-Header.h @@ -0,0 +1,11 @@ +// +// AppUI-Bridging-Header.h - Sudachi +// Created by Jarrod Norwell on 4/3/2024. +// + +#ifndef AppUI_Bridging_Header_h +#define AppUI_Bridging_Header_h + +#import "Wrapper/AppUIObjC.h" + +#endif /* AppUI_Bridging_Header_h */ diff --git a/src/ios/Eden/AppUI.swift b/src/ios/Eden/AppUI.swift new file mode 100644 index 0000000000..b8f79528b7 --- /dev/null +++ b/src/ios/Eden/AppUI.swift @@ -0,0 +1,108 @@ +// +// AppUI.swift - Sudachi +// Created by Jarrod Norwell on 4/3/2024. +// + +import Foundation +import QuartzCore.CAMetalLayer + +public struct AppUI { + + public static let shared = AppUI() + + fileprivate let appUIObjC = AppUIObjC.shared() + + public func configure(layer: CAMetalLayer, with size: CGSize) { + appUIObjC.configure(layer: layer, with: size) + } + + public func information(for url: URL) -> AppUIInformation { + appUIObjC.gameInformation.information(for: url) + } + + public func insert(game url: URL) { + appUIObjC.insert(game: url) + } + + public func insert(games urls: [URL]) { + appUIObjC.insert(games: urls) + } + + public func bootOS() { + appUIObjC.bootOS() + } + + public func pause() { + appUIObjC.pause() + } + + public func play() { + appUIObjC.play() + } + + public func ispaused() -> Bool { + return appUIObjC.ispaused() + } + + public func FirstFrameShowed() -> Bool { + return appUIObjC.hasfirstfame() + } + + public func canGetFullPath() -> Bool { + return appUIObjC.canGetFullPath() + } + + + public func exit() { + appUIObjC.quit() + } + + public func step() { + appUIObjC.step() + } + + public func orientationChanged(orientation: UIInterfaceOrientation, with layer: CAMetalLayer, size: CGSize) { + appUIObjC.orientationChanged(orientation: orientation, with: layer, size: size) + } + + public func touchBegan(at point: CGPoint, for index: UInt) { + appUIObjC.touchBegan(at: point, for: index) + } + + public func touchEnded(for index: UInt) { + appUIObjC.touchEnded(for: index) + } + + public func touchMoved(at point: CGPoint, for index: UInt) { + appUIObjC.touchMoved(at: point, for: index) + } + + 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 + appUIObjC.virtualControllerGyro(controllerId, + deltaTimestamp: deltaTimestamp, + gyroX: x, + gyroY: y, + gyroZ: z, + accelX: accelX, + accelY: accelY, + accelZ: accelZ) + } + + + public func thumbstickMoved(analog: VirtualControllerAnalogType, x: Float, y: Float, controllerid: Int) { + appUIObjC.thumbstickMoved(analog, x: CGFloat(x), y: CGFloat(y), controllerId: Int32(controllerid)) + } + + public func virtualControllerButtonDown(button: VirtualControllerButtonType, controllerid: Int) { + appUIObjC.virtualControllerButtonDown(button, controllerId: Int32(controllerid)) + } + + public func virtualControllerButtonUp(button: VirtualControllerButtonType, controllerid: Int) { + appUIObjC.virtualControllerButtonUp(button, controllerId: Int32(controllerid)) + } + + public func settingsSaved() { + appUIObjC.settingsChanged() + } +} diff --git a/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.h b/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.h new file mode 100644 index 0000000000..5040775ad0 --- /dev/null +++ b/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.h @@ -0,0 +1,26 @@ +// +// AppUIGameInformation.h - Sudachi +// Created by Jarrod Norwell on 1/20/24. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AppUIInformation : NSObject +@property (nonatomic, strong) NSString *developer; +@property (nonatomic, strong) NSData *iconData; +@property (nonatomic) BOOL isHomebrew; +@property (nonatomic) uint64_t programID; +@property (nonatomic, strong) NSString *title, *version; + +-(AppUIInformation *) initWithDeveloper:(NSString *)developer iconData:(NSData *)iconData isHomebrew:(BOOL)isHomebrew programID:(uint64_t)programID title:(NSString *)title version:(NSString *)version; +@end + +@interface AppUIGameInformation : NSObject ++(AppUIGameInformation *) sharedInstance NS_SWIFT_NAME(shared()); + +-(AppUIInformation *) informationForGame:(NSURL *)url NS_SWIFT_NAME(information(for:)); +@end + +NS_ASSUME_NONNULL_END diff --git a/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm b/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm new file mode 100644 index 0000000000..00c894d078 --- /dev/null +++ b/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm @@ -0,0 +1,434 @@ +// +// AppUIGameInformation.mm - Sudachi +// Created by Jarrod Norwell on 1/20/24. +// + +#import "AppUIGameInformation.h" +#import "../DirectoryManager/DirectoryManager.h" +#import "../EmulationSession/EmulationSession.h" + +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "core/core.h" +#include "core/file_sys/fs_filesystem.h" +#include "core/file_sys/patch_manager.h" +#include "core/loader/loader.h" +#include "core/loader/nro.h" +#include "frontend_common/yuzu_config.h" + +struct GameMetadata { + std::string title; + u64 programId; + std::string developer; + std::string version; + std::vector icon; + bool isHomebrew; +}; + + +class SdlConfig final : public YuzuConfig { +public: + explicit SdlConfig(std::optional config_path); + ~SdlConfig() override; + + void ReloadAllValues() override; + void SaveAllValues() override; + +protected: + void ReadSdlValues(); + void ReadSdlPlayerValues(std::size_t player_index); + void ReadSdlControlValues(); + void ReadHidbusValues() override; + void ReadDebugControlValues() override; + void ReadPathValues() override {} + void ReadShortcutValues() override {} + void ReadUIValues() override {} + void ReadUIGamelistValues() override {} + void ReadUILayoutValues() override {} + void ReadMultiplayerValues() override {} + + void SaveSdlValues(); + void SaveSdlPlayerValues(std::size_t player_index); + void SaveSdlControlValues(); + void SaveHidbusValues() override; + void SaveDebugControlValues() override; + void SavePathValues() override {} + void SaveShortcutValues() override {} + void SaveUIValues() override {} + void SaveUIGamelistValues() override {} + void SaveUILayoutValues() override {} + void SaveMultiplayerValues() override {} + + std::vector& FindRelevantList(YuzuSettings::Category category) override; + +public: + static const std::array default_buttons; + static const std::array default_motions; + static const std::array, YuzuSettings::NativeAnalog::NumAnalogs> default_analogs; + static const std::array default_stick_mod; + static const std::array default_ringcon_analogs; +}; + + +#define SDL_MAIN_HANDLED +#include + +#include "common/logging/log.h" +#include "input_common/main.h" + +const std::array SdlConfig::default_buttons = { + SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_T, + SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_Q, SDL_SCANCODE_W, + SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_B, +}; + +const std::array SdlConfig::default_motions = { + SDL_SCANCODE_7, + SDL_SCANCODE_8, +}; + +const std::array, YuzuSettings::NativeAnalog::NumAnalogs> SdlConfig::default_analogs{ + { + { + SDL_SCANCODE_UP, + SDL_SCANCODE_DOWN, + SDL_SCANCODE_LEFT, + SDL_SCANCODE_RIGHT, + }, + { + SDL_SCANCODE_I, + SDL_SCANCODE_K, + SDL_SCANCODE_J, + SDL_SCANCODE_L, + }, + }}; + +const std::array SdlConfig::default_stick_mod = { + SDL_SCANCODE_D, + 0, +}; + +const std::array SdlConfig::default_ringcon_analogs{{ + 0, + 0, +}}; + +SdlConfig::SdlConfig(const std::optional config_path) { + Initialize(config_path); + ReadSdlValues(); + SaveSdlValues(); +} + +SdlConfig::~SdlConfig() { + if (global) { + SdlConfig::SaveAllValues(); + } +} + +void SdlConfig::ReloadAllValues() { + Reload(); + ReadSdlValues(); + SaveSdlValues(); +} + +void SdlConfig::SaveAllValues() { + SaveValues(); + SaveSdlValues(); +} + +void SdlConfig::ReadSdlValues() { + ReadSdlControlValues(); +} + +void SdlConfig::ReadSdlControlValues() { + BeginGroup(YuzuSettings::TranslateCategory(YuzuSettings::Category::Controls)); + + YuzuSettings::values.players.SetGlobal(!IsCustomConfig()); + for (std::size_t p = 0; p < YuzuSettings::values.players.GetValue().size(); ++p) { + ReadSdlPlayerValues(p); + } + if (IsCustomConfig()) { + EndGroup(); + return; + } + ReadDebugControlValues(); + ReadHidbusValues(); + + EndGroup(); +} + +void SdlConfig::ReadSdlPlayerValues(const std::size_t player_index) { + std::string player_prefix; + if (type != ConfigType::InputProfile) { + player_prefix.append("player_").append(ToString(player_index)).append("_"); + } + + auto& player = YuzuSettings::values.players.GetValue()[player_index]; + if (IsCustomConfig()) { + const auto profile_name = + ReadStringSetting(std::string(player_prefix).append("profile_name")); + if (profile_name.empty()) { + // Use the global input config + player = YuzuSettings::values.players.GetValue(true)[player_index]; + player.profile_name = ""; + return; + } + } + + for (int i = 0; i < YuzuSettings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + auto& player_buttons = player.buttons[i]; + + player_buttons = ReadStringSetting( + std::string(player_prefix).append(YuzuSettings::NativeButton::mapping[i]), default_param); + if (player_buttons.empty()) { + player_buttons = default_param; + } + } + + for (int i = 0; i < YuzuSettings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + auto& player_analogs = player.analogs[i]; + + player_analogs = ReadStringSetting( + std::string(player_prefix).append(YuzuSettings::NativeAnalog::mapping[i]), default_param); + if (player_analogs.empty()) { + player_analogs = default_param; + } + } + + for (int i = 0; i < YuzuSettings::NativeMotion::NumMotions; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); + auto& player_motions = player.motions[i]; + + player_motions = ReadStringSetting( + std::string(player_prefix).append(YuzuSettings::NativeMotion::mapping[i]), default_param); + if (player_motions.empty()) { + player_motions = default_param; + } + } +} + +void SdlConfig::ReadDebugControlValues() { + for (int i = 0; i < YuzuSettings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + auto& debug_pad_buttons = YuzuSettings::values.debug_pad_buttons[i]; + debug_pad_buttons = ReadStringSetting( + std::string("debug_pad_").append(YuzuSettings::NativeButton::mapping[i]), default_param); + if (debug_pad_buttons.empty()) { + debug_pad_buttons = default_param; + } + } + for (int i = 0; i < YuzuSettings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + auto& debug_pad_analogs = YuzuSettings::values.debug_pad_analogs[i]; + debug_pad_analogs = ReadStringSetting( + std::string("debug_pad_").append(YuzuSettings::NativeAnalog::mapping[i]), default_param); + if (debug_pad_analogs.empty()) { + debug_pad_analogs = default_param; + } + } +} + +void SdlConfig::ReadHidbusValues() { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); + auto& ringcon_analogs = YuzuSettings::values.ringcon_analogs; + + ringcon_analogs = ReadStringSetting(std::string("ring_controller"), default_param); + if (ringcon_analogs.empty()) { + ringcon_analogs = default_param; + } +} + +void SdlConfig::SaveSdlValues() { + LOG_DEBUG(Config, "Saving SDL configuration values"); + SaveSdlControlValues(); + + WriteToIni(); +} + +void SdlConfig::SaveSdlControlValues() { + BeginGroup(YuzuSettings::TranslateCategory(YuzuSettings::Category::Controls)); + + YuzuSettings::values.players.SetGlobal(!IsCustomConfig()); + for (std::size_t p = 0; p < YuzuSettings::values.players.GetValue().size(); ++p) { + SaveSdlPlayerValues(p); + } + if (IsCustomConfig()) { + EndGroup(); + return; + } + SaveDebugControlValues(); + SaveHidbusValues(); + + EndGroup(); +} + +void SdlConfig::SaveSdlPlayerValues(const std::size_t player_index) { + std::string player_prefix; + if (type != ConfigType::InputProfile) { + player_prefix = std::string("player_").append(ToString(player_index)).append("_"); + } + + const auto& player = YuzuSettings::values.players.GetValue()[player_index]; + if (IsCustomConfig() && player.profile_name.empty()) { + // No custom profile selected + return; + } + + for (int i = 0; i < YuzuSettings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + WriteStringSetting(std::string(player_prefix).append(YuzuSettings::NativeButton::mapping[i]), + player.buttons[i], std::make_optional(default_param)); + } + for (int i = 0; i < YuzuSettings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + WriteStringSetting(std::string(player_prefix).append(YuzuSettings::NativeAnalog::mapping[i]), + player.analogs[i], std::make_optional(default_param)); + } + for (int i = 0; i < YuzuSettings::NativeMotion::NumMotions; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); + WriteStringSetting(std::string(player_prefix).append(YuzuSettings::NativeMotion::mapping[i]), + player.motions[i], std::make_optional(default_param)); + } +} + +void SdlConfig::SaveDebugControlValues() { + for (int i = 0; i < YuzuSettings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + WriteStringSetting(std::string("debug_pad_").append(YuzuSettings::NativeButton::mapping[i]), + YuzuSettings::values.debug_pad_buttons[i], + std::make_optional(default_param)); + } + for (int i = 0; i < YuzuSettings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + WriteStringSetting(std::string("debug_pad_").append(YuzuSettings::NativeAnalog::mapping[i]), + YuzuSettings::values.debug_pad_analogs[i], + std::make_optional(default_param)); + } +} + +void SdlConfig::SaveHidbusValues() { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); + WriteStringSetting(std::string("ring_controller"), YuzuSettings::values.ringcon_analogs, + std::make_optional(default_param)); +} + +std::vector& SdlConfig::FindRelevantList(YuzuSettings::Category category) { + return YuzuSettings::values.linkage.by_category[category]; +} + + + + + + + +std::unordered_map m_game_metadata_cache; + +GameMetadata CacheGameMetadata(const std::string& path) { + const auto file = + Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path); + auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0); + + GameMetadata entry; + loader->ReadTitle(entry.title); + loader->ReadProgramId(entry.programId); + loader->ReadIcon(entry.icon); + + const FileSys::PatchManager pm{ + entry.programId, EmulationSession::GetInstance().System().GetFileSystemController(), + EmulationSession::GetInstance().System().GetContentProvider()}; + const auto control = pm.GetControlMetadata(); + + if (control.first != nullptr) { + entry.developer = control.first->GetDeveloperName(); + entry.version = control.first->GetVersionString(); + } else { + FileSys::NACP nacp; + if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) { + entry.developer = nacp.GetDeveloperName(); + } else { + entry.developer = ""; + } + + entry.version = "1.0.0"; + } + + if (loader->GetFileType() == Loader::FileType::NRO) { + auto loader_nro = reinterpret_cast(loader.get()); + entry.isHomebrew = loader_nro->IsHomebrew(); + } else { + entry.isHomebrew = false; + } + + m_game_metadata_cache[path] = entry; + + return entry; +} + +GameMetadata GameMetadata(const std::string& path, bool reload = false) { + if (!EmulationSession::GetInstance().IsInitialized()) { + Common::FS::SetAppDirectory(DirectoryManager::AppUIDirectory()); + + EmulationSession::GetInstance().System().Initialize(); + EmulationSession::GetInstance().InitializeSystem(false); + } + + if (reload) { + return CacheGameMetadata(path); + } + + if (auto search = m_game_metadata_cache.find(path); search != m_game_metadata_cache.end()) { + return search->second; + } + + return CacheGameMetadata(path); +} + + +@implementation AppUIInformation +-(AppUIInformation *) initWithDeveloper:(NSString *)developer iconData:(NSData *)iconData isHomebrew:(BOOL)isHomebrew programID:(uint64_t)programID + title:(NSString *)title version:(NSString *)version { + if (self = [super init]) { + self.developer = developer; + self.iconData = iconData; + self.isHomebrew = isHomebrew; + self.programID = programID; + self.title = title; + self.version = version; + } return self; +} +@end + +@implementation AppUIGameInformation ++(AppUIGameInformation *) sharedInstance { + static AppUIGameInformation *sharedInstance = NULL; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + return sharedInstance; +} + + +-(AppUIInformation *) informationForGame:(NSURL *)url { + auto gameMetadata = GameMetadata([url.path UTF8String]); + + return [[AppUIInformation alloc] initWithDeveloper:[NSString stringWithCString:gameMetadata.developer.c_str() encoding:NSUTF8StringEncoding] + iconData:[NSData dataWithBytes:gameMetadata.icon.data() length:gameMetadata.icon.size()] + isHomebrew:gameMetadata.isHomebrew programID:gameMetadata.programId + title:[NSString stringWithCString:gameMetadata.title.c_str() encoding:NSUTF8StringEncoding] + version:[NSString stringWithCString:gameMetadata.version.c_str() encoding:NSUTF8StringEncoding]]; +} +@end diff --git a/src/ios/Eden/Wrapper/AppUIObjC.h b/src/ios/Eden/Wrapper/AppUIObjC.h new file mode 100644 index 0000000000..d3594d74d1 --- /dev/null +++ b/src/ios/Eden/Wrapper/AppUIObjC.h @@ -0,0 +1,92 @@ +// +// AppUIObjC.h - Sudachi +// Created by Jarrod Norwell on 1/8/24. +// + +#import +#import + +#import "AppUIGameInformation/AppUIGameInformation.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, VirtualControllerAnalogType) { + VirtualControllerAnalogTypeLeft = 0, + VirtualControllerAnalogTypeRight = 1 +}; + +typedef NS_ENUM(NSUInteger, VirtualControllerButtonType) { + VirtualControllerButtonTypeA = 0, + VirtualControllerButtonTypeB = 1, + VirtualControllerButtonTypeX = 2, + VirtualControllerButtonTypeY = 3, + VirtualControllerButtonTypeL = 4, + VirtualControllerButtonTypeR = 5, + VirtualControllerButtonTypeTriggerL = 6, + VirtualControllerButtonTypeTriggerR = 7, + VirtualControllerButtonTypeTriggerZL = 8, + VirtualControllerButtonTypeTriggerZR = 9, + VirtualControllerButtonTypePlus = 10, + VirtualControllerButtonTypeMinus = 11, + VirtualControllerButtonTypeDirectionalPadLeft = 12, + VirtualControllerButtonTypeDirectionalPadUp = 13, + VirtualControllerButtonTypeDirectionalPadRight = 14, + VirtualControllerButtonTypeDirectionalPadDown = 15, + VirtualControllerButtonTypeSL = 16, + VirtualControllerButtonTypeSR = 17, + VirtualControllerButtonTypeHome = 18, + VirtualControllerButtonTypeCapture = 19 +}; + +@interface AppUIObjC : NSObject { + CAMetalLayer *_layer; + CGSize _size; +} + +@property (nonatomic, strong) AppUIGameInformation *gameInformation; + ++(AppUIObjC *) sharedInstance NS_SWIFT_NAME(shared()); +-(void) configureLayer:(CAMetalLayer *)layer withSize:(CGSize)size NS_SWIFT_NAME(configure(layer:with:)); +-(void) bootOS; +-(void) pause; +-(void) play; +-(BOOL) ispaused; +-(BOOL) canGetFullPath; +-(void) quit; +-(void) insertGame:(NSURL *)url NS_SWIFT_NAME(insert(game:)); +-(void) insertGames:(NSArray *)games NS_SWIFT_NAME(insert(games:)); +-(void) step; +-(BOOL) hasfirstfame; + +-(void) touchBeganAtPoint:(CGPoint)point index:(NSUInteger)index NS_SWIFT_NAME(touchBegan(at:for:)); +-(void) touchEndedForIndex:(NSUInteger)index; +-(void) touchMovedAtPoint:(CGPoint)point index:(NSUInteger)index NS_SWIFT_NAME(touchMoved(at:for:)); + +-(void) thumbstickMoved:(VirtualControllerAnalogType)analog + x:(CGFloat)x + y:(CGFloat)y + controllerId:(int)controllerId; + +-(void) virtualControllerGyro:(int)controllerId + deltaTimestamp:(int)delta_timestamp + gyroX:(float)gyro_x + gyroY:(float)gyro_y + gyroZ:(float)gyro_z + accelX:(float)accel_x + accelY:(float)accel_y + accelZ:(float)accel_z; + +-(void) virtualControllerButtonDown:(VirtualControllerButtonType)button + controllerId:(int)controllerId; + +-(void) virtualControllerButtonUp:(VirtualControllerButtonType)button + controllerId:(int)controllerId; + + +-(void) orientationChanged:(UIInterfaceOrientation)orientation with:(CAMetalLayer *)layer size:(CGSize)size NS_SWIFT_NAME(orientationChanged(orientation:with:size:)); + +-(void) settingsChanged; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/ios/Eden/Wrapper/AppUIObjC.mm b/src/ios/Eden/Wrapper/AppUIObjC.mm new file mode 100644 index 0000000000..065ce6d046 --- /dev/null +++ b/src/ios/Eden/Wrapper/AppUIObjC.mm @@ -0,0 +1,258 @@ +// +// AppUIObjC.mm - Sudachi +// Created by Jarrod Norwell on 1/8/24. +// + +#import "AppUIObjC.h" + +#import "Config/Config.h" +#import "EmulationSession/EmulationSession.h" +#import "DirectoryManager/DirectoryManager.h" + +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/settings.h" +#include "common/fs/fs.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/savedata_factory.h" +#include "core/loader/nro.h" +#include "frontend_common/content_manager.h" +#include "common/settings_enums.h" +#include "network/announce_multiplayer_session.h" +#include "common/announce_multiplayer_room.h" +#include "network/network.h" + +#include "common/detached_tasks.h" +#include "common/dynamic_library.h" +#include "common/fs/path_util.h" +#include "common/logging/backend.h" +#include "common/logging/log.h" +#include "common/microprofile.h" +#include "common/scm_rev.h" +#include "common/scope_exit.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/cpu_manager.h" +#include "core/crypto/key_manager.h" +#include "core/file_sys/card_image.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/fs_filesystem.h" +#include "core/file_sys/submission_package.h" +#include "core/file_sys/vfs/vfs.h" +#include "core/file_sys/vfs/vfs_real.h" +#include "core/frontend/applets/cabinet.h" +#include "core/frontend/applets/controller.h" +#include "core/frontend/applets/error.h" +#include "core/frontend/applets/general.h" +#include "core/frontend/applets/mii_edit.h" +#include "core/frontend/applets/profile_select.h" +#include "core/frontend/applets/software_keyboard.h" +#include "core/frontend/applets/web_browser.h" +#include "core/hle/service/am/applet_manager.h" +#include "core/hle/service/am/frontend/applets.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/loader.h" +#include "frontend_common/yuzu_config.h" +#include "hid_core/frontend/emulated_controller.h" +#include "hid_core/hid_core.h" +#include "hid_core/hid_types.h" +#include "video_core/renderer_base.h" +#include "video_core/renderer_vulkan/renderer_vulkan.h" +#include "video_core/vulkan_common/vulkan_instance.h" +#include "video_core/vulkan_common/vulkan_surface.h" + + +#import + +@implementation AppUIObjC +-(AppUIObjC *) init { + if (self = [super init]) { + _gameInformation = [AppUIGameInformation sharedInstance]; + + + Common::FS::SetAppDirectory(DirectoryManager::AppUIDirectory()); + Config{"config", Config::ConfigType::GlobalConfig}; + + EmulationSession::GetInstance().System().Initialize(); + EmulationSession::GetInstance().InitializeSystem(false); + EmulationSession::GetInstance().InitializeGpuDriver(); + + + YuzuSettings::values.dump_shaders.SetValue(true); + YuzuSettings::values.use_asynchronous_shaders.SetValue(true); + // YuzuSettings::values.astc_recompression.SetValue(YuzuSettings::AstcRecompression::Bc3); + YuzuSettings::values.shader_backend.SetValue(YuzuSettings::ShaderBackend::SpirV); + // YuzuSettings::values.resolution_setup.SetValue(YuzuSettings::ResolutionSetup::Res1X); + // YuzuSettings::values.scaling_filter.SetValue(YuzuSettings::ScalingFilter::Bilinear); + } return self; +} + + ++(AppUIObjC *) sharedInstance { + static AppUIObjC *sharedInstance = NULL; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + return sharedInstance; +} + +- (BOOL)ispaused { + return EmulationSession::GetInstance().IsPaused(); +} + +-(void) pause { + EmulationSession::GetInstance().System().Pause(); + EmulationSession::GetInstance().HaltEmulation(); + EmulationSession::GetInstance().PauseEmulation(); +} + +-(void) play { + + EmulationSession::GetInstance().System().Run(); + EmulationSession::GetInstance().RunEmulation(); + EmulationSession::GetInstance().UnPauseEmulation(); +} + +-(BOOL)hasfirstfame { + @try { + auto* window = &EmulationSession::GetInstance().Window(); + if (window && window->HasFirstFrame()) { + return YES; + } + } + @catch (NSException *exception) { + NSLog(@"Exception occurred: %@", exception); + // Handle the exception, maybe return a default value + return NO; + } + return NO; +} + +- (BOOL)canGetFullPath { + @try { + Core::System& system = EmulationSession::GetInstance().System(); + auto bis_system = system.GetFileSystemController().GetSystemNANDContents(); + + if (bis_system == nullptr) { + return NO; + } + + constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch); + auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); + + if (qlaunch_applet_nca == nullptr) { + return NO; + } + + const auto filename = qlaunch_applet_nca->GetFullPath(); + + // If GetFullPath() is successful + return YES; + } @catch (NSException *exception) { + // Handle the exception if needed + return NO; + } +} + +-(void) quit { + EmulationSession::GetInstance().ShutdownEmulation(); +} + +-(void) configureLayer:(CAMetalLayer *)layer withSize:(CGSize)size { + _layer = layer; + _size = size; + EmulationSession::GetInstance().SetNativeWindow((__bridge CA::MetalLayer*)layer, size); +} + +-(void) bootOS { + EmulationSession::GetInstance().BootOS(); +} + +-(void) insertGame:(NSURL *)url { + EmulationSession::GetInstance().InitializeEmulation([url.path UTF8String], [_gameInformation informationForGame:url].programID, true); +} + +-(void) insertGames:(NSArray *)games { + for (NSURL *url in games) { + EmulationSession::GetInstance().ConfigureFilesystemProvider([url.path UTF8String]); + } +} + +-(void) step { + void(EmulationSession::GetInstance().System().Run()); +} + +-(void) touchBeganAtPoint:(CGPoint)point index:(NSUInteger)index { + float h_ratio, w_ratio; + h_ratio = EmulationSession::GetInstance().Window().GetFramebufferLayout().height / (_size.height * [[UIScreen mainScreen] nativeScale]); + w_ratio = EmulationSession::GetInstance().Window().GetFramebufferLayout().width / (_size.width * [[UIScreen mainScreen] nativeScale]); + + EmulationSession::GetInstance().Window().OnTouchPressed([[NSNumber numberWithUnsignedInteger:index] intValue], + (point.x) * [[UIScreen mainScreen] nativeScale] * w_ratio, + ((point.y) * [[UIScreen mainScreen] nativeScale] * h_ratio)); +} + +-(void) touchEndedForIndex:(NSUInteger)index { + EmulationSession::GetInstance().Window().OnTouchReleased([[NSNumber numberWithUnsignedInteger:index] intValue]); +} + +-(void) touchMovedAtPoint:(CGPoint)point index:(NSUInteger)index { + float h_ratio, w_ratio; + h_ratio = EmulationSession::GetInstance().Window().GetFramebufferLayout().height / (_size.height * [[UIScreen mainScreen] nativeScale]); + w_ratio = EmulationSession::GetInstance().Window().GetFramebufferLayout().width / (_size.width * [[UIScreen mainScreen] nativeScale]); + + EmulationSession::GetInstance().Window().OnTouchMoved([[NSNumber numberWithUnsignedInteger:index] intValue], + (point.x) * [[UIScreen mainScreen] nativeScale] * w_ratio, + ((point.y) * [[UIScreen mainScreen] nativeScale] * h_ratio)); +} + +-(void) thumbstickMoved:(VirtualControllerAnalogType)analog + x:(CGFloat)x + y:(CGFloat)y + controllerId:(int)controllerId { + EmulationSession::GetInstance().OnGamepadConnectEvent(controllerId); + EmulationSession::GetInstance().Window().OnGamepadJoystickEvent(controllerId, [[NSNumber numberWithUnsignedInteger:analog] intValue], CGFloat(x), CGFloat(y)); +} + +-(void) virtualControllerButtonDown:(VirtualControllerButtonType)button + controllerId:(int)controllerId { + EmulationSession::GetInstance().OnGamepadConnectEvent(controllerId); + EmulationSession::GetInstance().Window().OnGamepadButtonEvent(controllerId, [[NSNumber numberWithUnsignedInteger:button] intValue], true); +} + +-(void) virtualControllerGyro:(int)controllerId + deltaTimestamp:(int)delta_timestamp + gyroX:(float)gyro_x + gyroY:(float)gyro_y + gyroZ:(float)gyro_z + accelX:(float)accel_x + accelY:(float)accel_y + accelZ:(float)accel_z +{ + EmulationSession::GetInstance().OnGamepadConnectEvent(controllerId); + EmulationSession::GetInstance().Window().OnGamepadMotionEvent(controllerId, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z); +} + + +-(void) virtualControllerButtonUp:(VirtualControllerButtonType)button + controllerId:(int)controllerId { + EmulationSession::GetInstance().OnGamepadConnectEvent(controllerId); + EmulationSession::GetInstance().Window().OnGamepadButtonEvent(controllerId, [[NSNumber numberWithUnsignedInteger:button] intValue], false); +} + + +-(void) orientationChanged:(UIInterfaceOrientation)orientation with:(CAMetalLayer *)layer size:(CGSize)size { + _layer = layer; + _size = size; + EmulationSession::GetInstance().Window().OnSurfaceChanged((__bridge CA::MetalLayer*)layer, size); +} + +-(void) settingsChanged { + Config{"config", Config::ConfigType::GlobalConfig}; +} + + + +@end diff --git a/src/ios/Eden/Wrapper/Config/Config.h b/src/ios/Eden/Wrapper/Config/Config.h new file mode 100644 index 0000000000..5e29db42cf --- /dev/null +++ b/src/ios/Eden/Wrapper/Config/Config.h @@ -0,0 +1,568 @@ +// +// Config.h +// AppUI +// +// Created by Jarrod Norwell on 13/3/2024. +// + +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace DefaultINI { + +const char* android_config_file = R"( + +[ControlsP0] +# The input devices and parameters for each Switch native input +# The config section determines the player number where the config will be applied on. For example "ControlsP0", "ControlsP1", ... +# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..." +# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values + +# Indicates if this player should be connected at boot +connected= + +# for button input, the following devices are available: +# - "keyboard" (default) for keyboard input. Required parameters: +# - "code": the code of the key to bind +# - "sdl" for joystick input using SDL. Required parameters: +# - "guid": SDL identification GUID of the joystick +# - "port": the index of the joystick to bind +# - "button"(optional): the index of the button to bind +# - "hat"(optional): the index of the hat to bind as direction buttons +# - "axis"(optional): the index of the axis to bind +# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right" +# - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is +# triggered if the axis value crosses +# - "direction"(only used for axis): "+" means the button is triggered when the axis value +# is greater than the threshold; "-" means the button is triggered when the axis value +# is smaller than the threshold +button_a= +button_b= +button_x= +button_y= +button_lstick= +button_rstick= +button_l= +button_r= +button_zl= +button_zr= +button_plus= +button_minus= +button_dleft= +button_dup= +button_dright= +button_ddown= +button_lstick_left= +button_lstick_up= +button_lstick_right= +button_lstick_down= +button_sl= +button_sr= +button_home= +button_screenshot= + +# for analog input, the following devices are available: +# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters: +# - "up", "down", "left", "right": sub-devices for each direction. +# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00" +# - "modifier": sub-devices as a modifier. +# - "modifier_scale": a float number representing the applied modifier scale to the analog input. +# Must be in range of 0.0-1.0. Defaults to 0.5 +# - "sdl" for joystick input using SDL. Required parameters: +# - "guid": SDL identification GUID of the joystick +# - "port": the index of the joystick to bind +# - "axis_x": the index of the axis to bind as x-axis (default to 0) +# - "axis_y": the index of the axis to bind as y-axis (default to 1) +lstick= +rstick= + +# for motion input, the following devices are available: +# - "keyboard" (default) for emulating random motion input from buttons. Required parameters: +# - "code": the code of the key to bind +# - "sdl" for motion input using SDL. Required parameters: +# - "guid": SDL identification GUID of the joystick +# - "port": the index of the joystick to bind +# - "motion": the index of the motion sensor to bind +# - "cemuhookudp" for motion input using Cemu Hook protocol. Required parameters: +# - "guid": the IP address of the cemu hook server encoded to a hex string. for example 192.168.0.1 = "c0a80001" +# - "port": the port of the cemu hook server +# - "pad": the index of the joystick +# - "motion": the index of the motion sensor of the joystick to bind +motionleft= +motionright= + +[ControlsGeneral] +# To use the debug_pad, prepend debug_pad_ before each button setting above. +# i.e. debug_pad_button_a= + +# Enable debug pad inputs to the guest +# 0 (default): Disabled, 1: Enabled +debug_pad_enabled = + +# Whether to enable or disable vibration +# 0: Disabled, 1 (default): Enabled +vibration_enabled = + +# Whether to enable or disable accurate vibrations +# 0 (default): Disabled, 1: Enabled +enable_accurate_vibrations = + +# Enables controller motion inputs +# 0: Disabled, 1 (default): Enabled +motion_enabled = + +# Defines the udp device's touch screen coordinate system for cemuhookudp devices +# - "min_x", "min_y", "max_x", "max_y" +touch_device= + +# for mapping buttons to touch inputs. +#touch_from_button_map=1 +#touch_from_button_maps_0_name=default +#touch_from_button_maps_0_count=2 +#touch_from_button_maps_0_bind_0=foo +#touch_from_button_maps_0_bind_1=bar +# etc. + +# List of Cemuhook UDP servers, delimited by ','. +# Default: 127.0.0.1:26760 +# Example: 127.0.0.1:26760,123.4.5.67:26761 +udp_input_servers = + +# Enable controlling an axis via a mouse input. +# 0 (default): Off, 1: On +mouse_panning = + +# Set mouse sensitivity. +# Default: 1.0 +mouse_panning_sensitivity = + +# Emulate an analog control stick from keyboard inputs. +# 0 (default): Disabled, 1: Enabled +emulate_analog_keyboard = + +# Enable mouse inputs to the guest +# 0 (default): Disabled, 1: Enabled +mouse_enabled = + +# Enable keyboard inputs to the guest +# 0 (default): Disabled, 1: Enabled +keyboard_enabled = + +[Core] +# Whether to use multi-core for CPU emulation +# 0: Disabled, 1 (default): Enabled +use_multi_core = + +# Enable unsafe extended guest system memory layout (8GB DRAM) +# 0 (default): Disabled, 1: Enabled +use_unsafe_extended_memory_layout = + +[Cpu] +# Adjusts various optimizations. +# Auto-select mode enables choice unsafe optimizations. +# Accurate enables only safe optimizations. +# Unsafe allows any unsafe optimizations. +# 0 (default): Auto-select, 1: Accurate, 2: Enable unsafe optimizations +cpu_accuracy = + +# Allow disabling safe optimizations. +# 0 (default): Disabled, 1: Enabled +cpu_debug_mode = + +# Enable inline page tables optimization (faster guest memory access) +# 0: Disabled, 1 (default): Enabled +cpuopt_page_tables = + +# Enable block linking CPU optimization (reduce block dispatcher use during predictable jumps) +# 0: Disabled, 1 (default): Enabled +cpuopt_block_linking = + +# Enable return stack buffer CPU optimization (reduce block dispatcher use during predictable returns) +# 0: Disabled, 1 (default): Enabled +cpuopt_return_stack_buffer = + +# Enable fast dispatcher CPU optimization (use a two-tiered dispatcher architecture) +# 0: Disabled, 1 (default): Enabled +cpuopt_fast_dispatcher = + +# Enable context elimination CPU Optimization (reduce host memory use for guest context) +# 0: Disabled, 1 (default): Enabled +cpuopt_context_elimination = + +# Enable constant propagation CPU optimization (basic IR optimization) +# 0: Disabled, 1 (default): Enabled +cpuopt_const_prop = + +# Enable miscellaneous CPU optimizations (basic IR optimization) +# 0: Disabled, 1 (default): Enabled +cpuopt_misc_ir = + +# Enable reduction of memory misalignment checks (reduce memory fallbacks for misaligned access) +# 0: Disabled, 1 (default): Enabled +cpuopt_reduce_misalign_checks = + +# Enable Host MMU Emulation (faster guest memory access) +# 0: Disabled, 1 (default): Enabled +cpuopt_fastmem = + +# Enable Host MMU Emulation for exclusive memory instructions (faster guest memory access) +# 0: Disabled, 1 (default): Enabled +cpuopt_fastmem_exclusives = + +# Enable fallback on failure of fastmem of exclusive memory instructions (faster guest memory access) +# 0: Disabled, 1 (default): Enabled +cpuopt_recompile_exclusives = + +# Enable optimization to ignore invalid memory accesses (faster guest memory access) +# 0: Disabled, 1 (default): Enabled +cpuopt_ignore_memory_aborts = + +# Enable unfuse FMA (improve performance on CPUs without FMA) +# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. +# 0: Disabled, 1 (default): Enabled +cpuopt_unsafe_unfuse_fma = + +# Enable faster FRSQRTE and FRECPE +# Only enabled if cpu_accuracy is set to Unsafe. +# 0: Disabled, 1 (default): Enabled +cpuopt_unsafe_reduce_fp_error = + +# Enable faster ASIMD instructions (32 bits only) +# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. +# 0: Disabled, 1 (default): Enabled +cpuopt_unsafe_ignore_standard_fpcr = + +# Enable inaccurate NaN handling +# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. +# 0: Disabled, 1 (default): Enabled +cpuopt_unsafe_inaccurate_nan = + +# Disable address space checks (64 bits only) +# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. +# 0: Disabled, 1 (default): Enabled +cpuopt_unsafe_fastmem_check = + +# Enable faster exclusive instructions +# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. +# 0: Disabled, 1 (default): Enabled +cpuopt_unsafe_ignore_global_monitor = + +[Renderer] +# Which backend API to use. +# 0: OpenGL (unsupported), 1 (default): Vulkan, 2: Null +backend = + +# Whether to enable asynchronous presentation (Vulkan only) +# 0: Off, 1 (default): On +async_presentation = + +# Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied). +# 0 (default): Disabled, 1: Enabled +force_max_clock = + +# Enable graphics API debugging mode. +# 0 (default): Disabled, 1: Enabled +debug = + +# Enable shader feedback. +# 0 (default): Disabled, 1: Enabled +renderer_shader_feedback = + +# Enable Nsight Aftermath crash dumps +# 0 (default): Disabled, 1: Enabled +nsight_aftermath = + +# Disable shader loop safety checks, executing the shader without loop logic changes +# 0 (default): Disabled, 1: Enabled +disable_shader_loop_safety_checks = + +# Which Vulkan physical device to use (defaults to 0) +vulkan_device = + +# 0: 0.5x (360p/540p) [EXPERIMENTAL] +# 1: 0.75x (540p/810p) [EXPERIMENTAL] +# 2 (default): 1x (720p/1080p) +# 3: 2x (1440p/2160p) +# 4: 3x (2160p/3240p) +# 5: 4x (2880p/4320p) +# 6: 5x (3600p/5400p) +# 7: 6x (4320p/6480p) +resolution_setup = + +# Pixel filter to use when up- or down-sampling rendered frames. +# 0: Nearest Neighbor +# 1 (default): Bilinear +# 2: Bicubic +# 3: Gaussian +# 4: ScaleForce +# 5: AMD FidelityFX™️ Super Resolution [Vulkan Only] +scaling_filter = + +# Anti-Aliasing (AA) +# 0 (default): None, 1: FXAA +anti_aliasing = + +# Whether to use fullscreen or borderless window mode +# 0 (Windows default): Borderless window, 1 (All other default): Exclusive fullscreen +fullscreen_mode = + +# Aspect ratio +# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Force 16:10, 4: Stretch to Window +aspect_ratio = + +# Anisotropic filtering +# 0: Default, 1: 2x, 2: 4x, 3: 8x, 4: 16x +max_anisotropy = + +# Whether to enable VSync or not. +# OpenGL: Values other than 0 enable VSync +# Vulkan: FIFO is selected if the requested mode is not supported by the driver. +# FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. +# FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down. +# Mailbox can have lower latency than FIFO and does not tear but may drop frames. +# Immediate (no synchronization) just presents whatever is available and can exhibit tearing. +# 0: Immediate (Off), 1 (Default): Mailbox (On), 2: FIFO, 3: FIFO Relaxed +use_vsync = + +# Selects the OpenGL shader backend. NV_gpu_program5 is required for GLASM. If NV_gpu_program5 is +# not available and GLASM is selected, GLSL will be used. +# 0: GLSL, 1 (default): GLASM, 2: SPIR-V +shader_backend = + +# Whether to allow asynchronous shader building. +# 0 (default): Off, 1: On +use_asynchronous_shaders = + +# Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory. +# 0 (default): Off, 1: On +use_reactive_flushing = + +# NVDEC emulation. +# 0: Disabled, 1: CPU Decoding, 2 (default): GPU Decoding +nvdec_emulation = + +# Accelerate ASTC texture decoding. +# 0 (default): Off, 1: On +accelerate_astc = + +# Turns on the speed limiter, which will limit the emulation speed to the desired speed limit value +# 0: Off, 1: On (default) +use_speed_limit = + +# Limits the speed of the game to run no faster than this value as a percentage of target speed +# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default) +speed_limit = + +# Whether to use disk based shader cache +# 0: Off, 1 (default): On +use_disk_shader_cache = + +# Which gpu accuracy level to use +# 0 (default): Normal, 1: High, 2: Extreme (Very slow) +gpu_accuracy = + +# Whether to use asynchronous GPU emulation +# 0 : Off (slow), 1 (default): On (fast) +use_asynchronous_gpu_emulation = + +# Inform the guest that GPU operations completed more quickly than they did. +# 0: Off, 1 (default): On +use_fast_gpu_time = + +# Force unmodified buffers to be flushed, which can cost performance. +# 0: Off (default), 1: On +use_pessimistic_flushes = + +# Whether to use garbage collection or not for GPU caches. +# 0 (default): Off, 1: On +use_caches_gc = + +# The clear color for the renderer. What shows up on the sides of the bottom screen. +# Must be in range of 0-255. Defaults to 0 for all. +bg_red = +bg_blue = +bg_green = + +[Audio] +# Which audio output engine to use. +# auto (default): Auto-select +# cubeb: Cubeb audio engine (if available) +# sdl2: SDL2 audio engine (if available) +# null: No audio output +output_engine = + +# Which audio device to use. +# auto (default): Auto-select +output_device = + +# Output volume. +# 100 (default): 100%, 0; mute +volume = + +[Data Storage] +# Whether to create a virtual SD card. +# 1: Yes, 0 (default): No +use_virtual_sd = + +# Whether or not to enable gamecard emulation +# 1: Yes, 0 (default): No +gamecard_inserted = + +# Whether or not the gamecard should be emulated as the current game +# If 'gamecard_inserted' is 0 this setting is irrelevant +# 1: Yes, 0 (default): No +gamecard_current_game = + +# Path to an XCI file to use as the gamecard +# If 'gamecard_inserted' is 0 this setting is irrelevant +# If 'gamecard_current_game' is 1 this setting is irrelevant +gamecard_path = + +[System] +# Whether the system is docked +# 1: Yes, 0 (default): No +use_docked_mode = + +# Sets the seed for the RNG generator built into the switch +# rng_seed will be ignored and randomly generated if rng_seed_enabled is false +rng_seed_enabled = +rng_seed = + +# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service +# This will auto-increment, with the time set being the time the game is started +# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used +custom_rtc_enabled = +custom_rtc = + +# Sets the systems language index +# 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese, +# 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Taiwanese, 12: British English, 13: Canadian French, +# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese, 17: Brazilian Portuguese +language_index = + +# The system region that yuzu will use during emulation +# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan +region_index = + +# The system time zone that yuzu will use during emulation +# 0: Auto-select (default), 1: Default (system archive value), Others: Index for specified time zone +time_zone_index = + +# Sets the sound output mode. +# 0: Mono, 1 (default): Stereo, 2: Surround +sound_index = + +[Miscellaneous] +# A filter which removes logs below a certain logging level. +# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical +log_filter = *:Trace + +# Use developer keys +# 0 (default): Disabled, 1: Enabled +use_dev_keys = + +[Debugging] +# Record frame time data, can be found in the log directory. Boolean value +record_frame_times = +# Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them +dump_exefs=false +# Determines whether or not yuzu will dump all NSOs it attempts to load while loading them +dump_nso=false +# Determines whether or not yuzu will save the filesystem access log. +enable_fs_access_log=false +# Enables verbose reporting services +reporting_services = +# Determines whether or not yuzu will report to the game that the emulated console is in Kiosk Mode +# false: Retail/Normal Mode (default), true: Kiosk Mode +quest_flag = +# Determines whether debug asserts should be enabled, which will throw an exception on asserts. +# false: Disabled (default), true: Enabled +use_debug_asserts = +# Determines whether unimplemented HLE service calls should be automatically stubbed. +# false: Disabled (default), true: Enabled +use_auto_stub = +# Enables/Disables the macro JIT compiler +disable_macro_jit=false +# Determines whether to enable the GDB stub and wait for the debugger to attach before running. +# false: Disabled (default), true: Enabled +use_gdbstub=false +# The port to use for the GDB server, if it is enabled. +gdbstub_port=6543 + +[WebService] +# Whether or not to enable telemetry +# 0: No, 1 (default): Yes +enable_telemetry = +# URL for Web API +web_api_url = +# Username and token for yuzu Web Service +# See https://profile.yuzu-emu.org/ for more info +yuzu_username = +yuzu_token = + +[Network] +# Name of the network interface device to use with yuzu LAN play. +# e.g. On *nix: 'enp7s0', 'wlp6s0u1u3u3', 'lo' +# e.g. On Windows: 'Ethernet', 'Wi-Fi' +network_interface = + +[AddOns] +# Used to disable add-ons +# List of title IDs of games that will have add-ons disabled (separated by '|'): +title_ids = +# For each title ID, have a key/value pair called `disabled_` equal to the names of the add-ons to disable (sep. by '|') +# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and + on Super Mario Odyssey +)"; +} // namespace DefaultINI + + +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "common/settings.h" + +class INIReader; + +class Config { + bool LoadINI(const std::string& default_contents = "", bool retry = true); + +public: + enum class ConfigType { + GlobalConfig, + PerGameConfig, + InputProfile, + }; + + explicit Config(const std::string& config_name = "config", + ConfigType config_type = ConfigType::GlobalConfig); + ~Config(); + + void Initialize(const std::string& config_name); + +private: + /** + * Applies a value read from the config to a Setting. + * + * @param group The name of the INI group + * @param setting The yuzu setting to modify + */ + template + void ReadSetting(const std::string& group, YuzuSettings::Setting& setting); + + void ReadValues(); + + const ConfigType type; + std::unique_ptr config; + std::string config_loc; + const bool global; +}; diff --git a/src/ios/Eden/Wrapper/Config/Config.mm b/src/ios/Eden/Wrapper/Config/Config.mm new file mode 100644 index 0000000000..3460ee08de --- /dev/null +++ b/src/ios/Eden/Wrapper/Config/Config.mm @@ -0,0 +1,330 @@ +// +// Config.m - Sudachi +// Created by Jarrod Norwell on 13/3/2024. +// + +#import "Config.h" + +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "common/settings_enums.h" +#include "core/hle/service/acc/profile_manager.h" +#include "input_common/main.h" + +namespace FS = Common::FS; + +Config::Config(const std::string& config_name, ConfigType config_type) + : type(config_type), global{config_type == ConfigType::GlobalConfig} { + Initialize(config_name); +} + +Config::~Config() = default; + +bool Config::LoadINI(const std::string& default_contents, bool retry) { + void(FS::CreateParentDir(config_loc)); + config = std::make_unique(FS::PathToUTF8String(config_loc)); + const auto config_loc_str = FS::PathToUTF8String(config_loc); + if (config->ParseError() < 0) { + if (retry) { + LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", + config_loc_str); + + void(FS::CreateParentDir(config_loc)); + void(FS::WriteStringToFile(config_loc, FS::FileType::TextFile, default_contents)); + + config = std::make_unique(config_loc_str); + + return LoadINI(default_contents, false); + } + LOG_ERROR(Config, "Failed."); + return false; + } + LOG_INFO(Config, "Successfully loaded {}", config_loc_str); + return true; +} + +template <> +void Config::ReadSetting(const std::string& group, YuzuSettings::Setting& setting) { + std::string setting_value = config->Get(group, setting.GetLabel(), setting.GetDefault()); + if (setting_value.empty()) { + setting_value = setting.GetDefault(); + } + setting = std::move(setting_value); +} + +template <> +void Config::ReadSetting(const std::string& group, YuzuSettings::Setting& setting) { + setting = config->GetBoolean(group, setting.GetLabel(), setting.GetDefault()); +} + +template +void Config::ReadSetting(const std::string& group, YuzuSettings::Setting& setting) { + setting = static_cast( + config->GetInteger(group, setting.GetLabel(), static_cast(setting.GetDefault()))); +} + +void Config::ReadValues() { + ReadSetting("ControlsGeneral", YuzuSettings::values.mouse_enabled); + ReadSetting("ControlsGeneral", YuzuSettings::values.touch_device); + ReadSetting("ControlsGeneral", YuzuSettings::values.keyboard_enabled); + ReadSetting("ControlsGeneral", YuzuSettings::values.debug_pad_enabled); + ReadSetting("ControlsGeneral", YuzuSettings::values.vibration_enabled); + ReadSetting("ControlsGeneral", YuzuSettings::values.enable_accurate_vibrations); + ReadSetting("ControlsGeneral", YuzuSettings::values.motion_enabled); + YuzuSettings::values.touchscreen.enabled = + config->GetBoolean("ControlsGeneral", "touch_enabled", true); + YuzuSettings::values.touchscreen.rotation_angle = + config->GetInteger("ControlsGeneral", "touch_angle", 0); + YuzuSettings::values.touchscreen.diameter_x = + config->GetInteger("ControlsGeneral", "touch_diameter_x", 15); + YuzuSettings::values.touchscreen.diameter_y = + config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); + + int num_touch_from_button_maps = + config->GetInteger("ControlsGeneral", "touch_from_button_map", 0); + if (num_touch_from_button_maps > 0) { + for (int i = 0; i < num_touch_from_button_maps; ++i) { + YuzuSettings::TouchFromButtonMap map; + map.name = config->Get("ControlsGeneral", + std::string("touch_from_button_maps_") + std::to_string(i) + + std::string("_name"), + "default"); + const int num_touch_maps = config->GetInteger( + "ControlsGeneral", + std::string("touch_from_button_maps_") + std::to_string(i) + std::string("_count"), + 0); + map.buttons.reserve(num_touch_maps); + + for (int j = 0; j < num_touch_maps; ++j) { + std::string touch_mapping = + config->Get("ControlsGeneral", + std::string("touch_from_button_maps_") + std::to_string(i) + + std::string("_bind_") + std::to_string(j), + ""); + map.buttons.emplace_back(std::move(touch_mapping)); + } + + YuzuSettings::values.touch_from_button_maps.emplace_back(std::move(map)); + } + } else { + YuzuSettings::values.touch_from_button_maps.emplace_back( + YuzuSettings::TouchFromButtonMap{"default", {}}); + num_touch_from_button_maps = 1; + } + YuzuSettings::values.touch_from_button_map_index = std::clamp( + YuzuSettings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1); + + ReadSetting("ControlsGeneral", YuzuSettings::values.udp_input_servers); + + // Data Storage + ReadSetting("Data Storage", YuzuSettings::values.use_virtual_sd); + FS::SetYuzuPath(FS::YuzuPath::NANDDir, + config->Get("Data Storage", "nand_directory", + FS::GetYuzuPathString(FS::YuzuPath::NANDDir))); + FS::SetYuzuPath(FS::YuzuPath::SDMCDir, + config->Get("Data Storage", "sdmc_directory", + FS::GetYuzuPathString(FS::YuzuPath::SDMCDir))); + FS::SetYuzuPath(FS::YuzuPath::LoadDir, + config->Get("Data Storage", "load_directory", + FS::GetYuzuPathString(FS::YuzuPath::LoadDir))); + FS::SetYuzuPath(FS::YuzuPath::DumpDir, + config->Get("Data Storage", "dump_directory", + FS::GetYuzuPathString(FS::YuzuPath::DumpDir))); + ReadSetting("Data Storage", YuzuSettings::values.gamecard_inserted); + ReadSetting("Data Storage", YuzuSettings::values.gamecard_current_game); + ReadSetting("Data Storage", YuzuSettings::values.gamecard_path); + + // System + ReadSetting("System", YuzuSettings::values.current_user); + YuzuSettings::values.current_user = std::clamp(YuzuSettings::values.current_user.GetValue(), 0, + Service::Account::MAX_USERS - 1); + + // Enable docked mode by default on iOS + YuzuSettings::values.use_docked_mode.SetValue(config->GetBoolean("System", "use_docked_mode", false) + ? YuzuSettings::ConsoleMode::Docked + : YuzuSettings::ConsoleMode::Handheld); + + const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false); + if (rng_seed_enabled) { + YuzuSettings::values.rng_seed.SetValue(config->GetInteger("System", "rng_seed", 0)); + } else { + YuzuSettings::values.rng_seed.SetValue(0); + } + YuzuSettings::values.rng_seed_enabled.SetValue(rng_seed_enabled); + + const auto custom_rtc_enabled = config->GetBoolean("System", "custom_rtc_enabled", false); + if (custom_rtc_enabled) { + YuzuSettings::values.custom_rtc = config->GetInteger("System", "custom_rtc", 0); + } else { + YuzuSettings::values.custom_rtc = 0; + } + YuzuSettings::values.custom_rtc_enabled = custom_rtc_enabled; + + ReadSetting("System", YuzuSettings::values.language_index); + ReadSetting("System", YuzuSettings::values.region_index); + ReadSetting("System", YuzuSettings::values.time_zone_index); + ReadSetting("System", YuzuSettings::values.sound_index); + + // Core + ReadSetting("Core", YuzuSettings::values.use_multi_core); + ReadSetting("Core", YuzuSettings::values.memory_layout_mode); + + // Cpu + ReadSetting("Cpu", YuzuSettings::values.cpu_accuracy); + ReadSetting("Cpu", YuzuSettings::values.cpu_debug_mode); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_page_tables); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_block_linking); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_return_stack_buffer); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_fast_dispatcher); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_context_elimination); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_const_prop); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_misc_ir); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_reduce_misalign_checks); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_fastmem); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_fastmem_exclusives); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_recompile_exclusives); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_ignore_memory_aborts); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_unfuse_fma); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_reduce_fp_error); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_ignore_standard_fpcr); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_inaccurate_nan); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_fastmem_check); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_ignore_global_monitor); + + // Renderer + ReadSetting("Renderer", YuzuSettings::values.renderer_backend); + ReadSetting("Renderer", YuzuSettings::values.renderer_debug); + ReadSetting("Renderer", YuzuSettings::values.renderer_shader_feedback); + ReadSetting("Renderer", YuzuSettings::values.enable_nsight_aftermath); + ReadSetting("Renderer", YuzuSettings::values.disable_shader_loop_safety_checks); + ReadSetting("Renderer", YuzuSettings::values.vulkan_device); + + ReadSetting("Renderer", YuzuSettings::values.resolution_setup); + ReadSetting("Renderer", YuzuSettings::values.scaling_filter); + ReadSetting("Renderer", YuzuSettings::values.fsr_sharpening_slider); + ReadSetting("Renderer", YuzuSettings::values.anti_aliasing); + ReadSetting("Renderer", YuzuSettings::values.fullscreen_mode); + ReadSetting("Renderer", YuzuSettings::values.aspect_ratio); + ReadSetting("Renderer", YuzuSettings::values.max_anisotropy); + ReadSetting("Renderer", YuzuSettings::values.use_speed_limit); + ReadSetting("Renderer", YuzuSettings::values.speed_limit); + ReadSetting("Renderer", YuzuSettings::values.use_disk_shader_cache); + ReadSetting("Renderer", YuzuSettings::values.use_asynchronous_gpu_emulation); + ReadSetting("Renderer", YuzuSettings::values.vsync_mode); + ReadSetting("Renderer", YuzuSettings::values.shader_backend); + ReadSetting("Renderer", YuzuSettings::values.use_asynchronous_shaders); + ReadSetting("Renderer", YuzuSettings::values.nvdec_emulation); + ReadSetting("Renderer", YuzuSettings::values.use_fast_gpu_time); + ReadSetting("Renderer", YuzuSettings::values.use_vulkan_driver_pipeline_cache); + + ReadSetting("Renderer", YuzuSettings::values.bg_red); + ReadSetting("Renderer", YuzuSettings::values.bg_green); + ReadSetting("Renderer", YuzuSettings::values.bg_blue); + + // Use GPU accuracy normal by default on Android + YuzuSettings::values.gpu_accuracy = static_cast(config->GetInteger( + "Renderer", "gpu_accuracy", static_cast(YuzuSettings::GpuAccuracy::Normal))); + + // Use GPU default anisotropic filtering on Android + YuzuSettings::values.max_anisotropy = + static_cast(config->GetInteger("Renderer", "max_anisotropy", 1)); + + // Disable ASTC compute by default on iOS + YuzuSettings::values.accelerate_astc.SetValue( + config->GetBoolean("Renderer", "accelerate_astc", false) ? YuzuSettings::AstcDecodeMode::Gpu + : YuzuSettings::AstcDecodeMode::Cpu); + + // Enable asynchronous presentation by default on Android + YuzuSettings::values.async_presentation = + config->GetBoolean("Renderer", "async_presentation", true); + + // Disable force_max_clock by default on Android + YuzuSettings::values.renderer_force_max_clock = + config->GetBoolean("Renderer", "force_max_clock", false); + + // Disable use_reactive_flushing by default on Android + YuzuSettings::values.use_reactive_flushing = + config->GetBoolean("Renderer", "use_reactive_flushing", false); + + // Audio + ReadSetting("Audio", YuzuSettings::values.sink_id); + ReadSetting("Audio", YuzuSettings::values.audio_output_device_id); + ReadSetting("Audio", YuzuSettings::values.volume); + + // Miscellaneous + // log_filter has a different default here than from common + YuzuSettings::values.log_filter = "*:Info"; + ReadSetting("Miscellaneous", YuzuSettings::values.use_dev_keys); + + // Debugging + YuzuSettings::values.record_frame_times = + config->GetBoolean("Debugging", "record_frame_times", false); + ReadSetting("Debugging", YuzuSettings::values.dump_exefs); + ReadSetting("Debugging", YuzuSettings::values.dump_nso); + ReadSetting("Debugging", YuzuSettings::values.enable_fs_access_log); + ReadSetting("Debugging", YuzuSettings::values.reporting_services); + ReadSetting("Debugging", YuzuSettings::values.quest_flag); + ReadSetting("Debugging", YuzuSettings::values.use_debug_asserts); + ReadSetting("Debugging", YuzuSettings::values.use_auto_stub); + ReadSetting("Debugging", YuzuSettings::values.disable_macro_jit); + ReadSetting("Debugging", YuzuSettings::values.disable_macro_hle); + ReadSetting("Debugging", YuzuSettings::values.use_gdbstub); + ReadSetting("Debugging", YuzuSettings::values.gdbstub_port); + + const auto title_list = config->Get("AddOns", "title_ids", ""); + std::stringstream ss(title_list); + std::string line; + while (std::getline(ss, line, '|')) { + const auto title_id = std::strtoul(line.c_str(), nullptr, 16); + const auto disabled_list = config->Get("AddOns", "disabled_" + line, ""); + + std::stringstream inner_ss(disabled_list); + std::string inner_line; + std::vector out; + while (std::getline(inner_ss, inner_line, '|')) { + out.push_back(inner_line); + } + + YuzuSettings::values.disabled_addons.insert_or_assign(title_id, out); + } + + // Web Service + ReadSetting("WebService", YuzuSettings::values.enable_telemetry); + ReadSetting("WebService", YuzuSettings::values.web_api_url); + ReadSetting("WebService", YuzuSettings::values.yuzu_username); + ReadSetting("WebService", YuzuSettings::values.yuzu_token); + + // Network + ReadSetting("Network", YuzuSettings::values.network_interface); +} + +void Config::Initialize(const std::string& config_name) { + const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir); + const auto config_file = fmt::format("{}.ini", config_name); + + switch (type) { + case ConfigType::GlobalConfig: + config_loc = FS::PathToUTF8String(fs_config_loc / config_file); + break; + case ConfigType::PerGameConfig: + config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file)); + break; + case ConfigType::InputProfile: + config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file); + LoadINI(DefaultINI::android_config_file); + return; + } + LoadINI(DefaultINI::android_config_file); + ReadValues(); +} diff --git a/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h b/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h new file mode 100644 index 0000000000..3965b6c2bb --- /dev/null +++ b/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h @@ -0,0 +1,10 @@ +// +// DirectoryManager.h - Sudachi +// Created by Jarrod Norwell on 1/18/24. +// + +#pragma once + +namespace DirectoryManager { +const char* AppUIDirectory(void); +} diff --git a/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm b/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm new file mode 100644 index 0000000000..428244fea1 --- /dev/null +++ b/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm @@ -0,0 +1,16 @@ +// +// DirectoryManager.mm - Sudachi +// Created by Jarrod Norwell on 1/18/24. +// + +#import + +#import "DirectoryManager.h" + +NSURL *DocumentsDirectory() { + return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject]; +} + +const char* DirectoryManager::AppUIDirectory(void) { + return [[DocumentsDirectory() path] UTF8String]; +} diff --git a/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.h b/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.h new file mode 100644 index 0000000000..291d414b37 --- /dev/null +++ b/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.h @@ -0,0 +1,98 @@ +// +// EmulationSession.h - Sudachi +// Created by Jarrod Norwell on 1/20/24. +// + +#pragma once + +#import + +#import +#import "../EmulationWindow/EmulationWindow.h" + +#include "common/detached_tasks.h" +#include "core/core.h" +#include "core/file_sys/registered_cache.h" +#include "core/hle/service/acc/profile_manager.h" +#include "core/perf_stats.h" +#include "frontend_common/content_manager.h" +#include "video_core/rasterizer_interface.h" + +class EmulationSession final { +public: + explicit EmulationSession(); + ~EmulationSession() = default; + + static EmulationSession& GetInstance(); + const Core::System& System() const; + Core::System& System(); + FileSys::ManualContentProvider* GetContentProvider(); + InputCommon::InputSubsystem& GetInputSubsystem(); + + const EmulationWindow& Window() const; + EmulationWindow& Window(); + CA::MetalLayer* NativeWindow() const; + void SetNativeWindow(CA::MetalLayer* native_window, CGSize size); + void SurfaceChanged(); + + void InitializeGpuDriver(); + + bool IsRunning() const; + bool IsPaused() const; + void PauseEmulation(); + void UnPauseEmulation(); + void HaltEmulation(); + void RunEmulation(); + void ShutdownEmulation(); + + const Core::PerfStatsResults& PerfStats(); + void ConfigureFilesystemProvider(const std::string& filepath); + void InitializeSystem(bool reload); + void SetAppletId(int applet_id); + Core::SystemResultStatus InitializeEmulation(const std::string& filepath, + const std::size_t program_index, + const bool frontend_initiated); + Core::SystemResultStatus BootOS(); + + static void OnEmulationStarted(); + static u64 GetProgramId(std::string programId); + bool IsInitialized() { return is_initialized; }; + + bool IsHandheldOnly(); + void SetDeviceType([[maybe_unused]] int index, int type); + void OnGamepadConnectEvent([[maybe_unused]] int index); + void OnGamepadDisconnectEvent([[maybe_unused]] int index); +private: + static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max); + static void OnEmulationStopped(Core::SystemResultStatus result); + static void ChangeProgram(std::size_t program_index); + +private: + // Window management + std::unique_ptr m_window; + CA::MetalLayer* m_native_window{}; + + // Core emulation + Core::System m_system; + InputCommon::InputSubsystem m_input_subsystem; + Common::DetachedTasks m_detached_tasks; + Core::PerfStatsResults m_perf_stats{}; + std::shared_ptr m_vfs; + Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; + std::atomic m_is_running = false; + std::atomic m_is_paused = false; + std::unique_ptr m_manual_provider; + int m_applet_id{1}; + + // GPU driver parameters + std::shared_ptr m_vulkan_library; + + // Synchronization + std::condition_variable_any m_cv; + mutable std::mutex m_mutex; + bool is_initialized = false; + CGSize m_size; + + // Program index for next boot + std::atomic m_next_program_index = -1; +}; diff --git a/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.mm b/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.mm new file mode 100644 index 0000000000..18793ca9fe --- /dev/null +++ b/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.mm @@ -0,0 +1,528 @@ +// +// EmulationSession.m - Sudachi +// Created by Jarrod Norwell on 1/20/24. +// + +#import "EmulationSession.h" + +#include + +#include +#include +#include +#include +#include + +#include "common/fs/fs.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/savedata_factory.h" +#include "core/loader/nro.h" +#include "frontend_common/content_manager.h" + +#include "common/detached_tasks.h" +#include "common/dynamic_library.h" +#include "common/fs/path_util.h" +#include "common/logging/backend.h" +#include "common/logging/log.h" +#include "common/microprofile.h" +#include "common/scm_rev.h" +#include "common/scope_exit.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/cpu_manager.h" +#include "core/crypto/key_manager.h" +#include "core/file_sys/card_image.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/fs_filesystem.h" +#include "core/file_sys/submission_package.h" +#include "core/file_sys/vfs/vfs.h" +#include "core/file_sys/vfs/vfs_real.h" +#include "core/frontend/applets/cabinet.h" +#include "core/frontend/applets/controller.h" +#include "core/frontend/applets/error.h" +#include "core/frontend/applets/general.h" +#include "core/frontend/applets/mii_edit.h" +#include "core/frontend/applets/profile_select.h" +#include "core/frontend/applets/software_keyboard.h" +#include "core/frontend/applets/web_browser.h" +#include "core/hle/service/am/applet_manager.h" +#include "core/hle/service/am/frontend/applets.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/loader.h" +#include "frontend_common/yuzu_config.h" +#include "hid_core/frontend/emulated_controller.h" +#include "hid_core/hid_core.h" +#include "hid_core/hid_types.h" +#include "video_core/renderer_base.h" +#include "video_core/renderer_vulkan/renderer_vulkan.h" +#include "video_core/vulkan_common/vulkan_instance.h" +#include "video_core/vulkan_common/vulkan_surface.h" + +#define jconst [[maybe_unused]] const auto +#define jauto [[maybe_unused]] auto + +static EmulationSession s_instance; + +EmulationSession::EmulationSession() { + m_vfs = std::make_shared(); +} + +EmulationSession& EmulationSession::GetInstance() { + return s_instance; +} + +const Core::System& EmulationSession::System() const { + return m_system; +} + +Core::System& EmulationSession::System() { + return m_system; +} + +FileSys::ManualContentProvider* EmulationSession::GetContentProvider() { + return m_manual_provider.get(); +} + +InputCommon::InputSubsystem& EmulationSession::GetInputSubsystem() { + return m_input_subsystem; +} + +const EmulationWindow& EmulationSession::Window() const { + return *m_window; +} + +EmulationWindow& EmulationSession::Window() { + return *m_window; +} + +CA::MetalLayer* EmulationSession::NativeWindow() const { + return m_native_window; +} + +void EmulationSession::SetNativeWindow(CA::MetalLayer* native_window, CGSize size) { + m_native_window = native_window; + m_size = size; +} + +void EmulationSession::InitializeGpuDriver() { + m_vulkan_library = std::make_shared(dlopen("@executable_path/Frameworks/MoltenVK", RTLD_NOW)); +} + +bool EmulationSession::IsRunning() const { + return m_is_running; +} + +bool EmulationSession::IsPaused() const { + return m_is_running && m_is_paused; +} + +const Core::PerfStatsResults& EmulationSession::PerfStats() { + m_perf_stats = m_system.GetAndResetPerfStats(); + return m_perf_stats; +} + +void EmulationSession::SurfaceChanged() { + if (!IsRunning()) { + return; + } + m_window->OnSurfaceChanged(m_native_window, m_size); +} + +void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) { + const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::OpenMode::Read); + if (!file) { + return; + } + + auto loader = Loader::GetLoader(m_system, file); + if (!loader) { + return; + } + + const auto file_type = loader->GetFileType(); + if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { + return; + } + + u64 program_id = 0; + const auto res2 = loader->ReadProgramId(program_id); + if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { + m_manual_provider->AddEntry(FileSys::TitleType::Application, + FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), + program_id, file); + } else if (res2 == Loader::ResultStatus::Success && + (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { + const auto nsp = file_type == Loader::FileType::NSP + ? std::make_shared(file) + : FileSys::XCI{file}.GetSecurePartitionNSP(); + for (const auto& title : nsp->GetNCAs()) { + for (const auto& entry : title.second) { + m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first, + entry.second->GetBaseFile()); + } + } + } +} + +void EmulationSession::InitializeSystem(bool reload) { + if (!reload) { + SDL_SetMainReady(); + + // Initialize logging system + Common::Log::Initialize(); + Common::Log::SetColorConsoleBackendEnabled(true); + Common::Log::Start(); + } + + // Initialize filesystem. + m_system.SetFilesystem(m_vfs); + m_system.GetUserChannel().clear(); + m_manual_provider = std::make_unique(); + m_system.SetContentProvider(std::make_unique()); + m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual, + m_manual_provider.get()); + m_system.GetFileSystemController().CreateFactories(*m_vfs); + + is_initialized = true; +} + +void EmulationSession::SetAppletId(int applet_id) { + m_applet_id = applet_id; + m_system.GetFrontendAppletHolder().SetCurrentAppletId( + static_cast(m_applet_id)); +} + +Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath, + const std::size_t program_index, + const bool frontend_initiated) { + std::scoped_lock lock(m_mutex); + + // Create the render window. + m_window = std::make_unique(&m_input_subsystem, m_native_window, m_size, m_vulkan_library); + + // Initialize system. + m_system.SetShuttingDown(false); + m_system.ApplySettings(); + YuzuSettings::LogSettings(); + m_system.HIDCore().ReloadInputDevices(); + m_system.SetFrontendAppletSet({ + nullptr, // Amiibo Settings + nullptr, // Controller Selector + nullptr, // Error Display + nullptr, // Mii Editor + nullptr, // Parental Controls + nullptr, // Photo Viewer + nullptr, // Profile Selector + nullptr, // std::move(android_keyboard), // Software Keyboard + nullptr, // Web Browser + }); + + // Initialize filesystem. + ConfigureFilesystemProvider(filepath); + + // Load the ROM. + Service::AM::FrontendAppletParameters params{ + .applet_id = static_cast(m_applet_id), + .launch_type = frontend_initiated ? Service::AM::LaunchType::FrontendInitiated + : Service::AM::LaunchType::ApplicationInitiated, + .program_index = static_cast(program_index), + }; + m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath, params); + if (m_load_result != Core::SystemResultStatus::Success) { + return m_load_result; + } + + // Complete initialization. + m_system.GPU().Start(); + m_system.GetCpuManager().OnGpuReady(); + m_system.RegisterExitCallback([&] { HaltEmulation(); }); + + if (YuzuSettings::values.use_disk_shader_cache.GetValue()) { + m_system.Renderer().ReadRasterizer()->LoadDiskResources( + m_system.GetApplicationProcessProgramID(), std::stop_token{}, + [](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); + } + + // Register an ExecuteProgram callback such that Core can execute a sub-program + m_system.RegisterExecuteProgramCallback([&](std::size_t program_index_) { + m_next_program_index = program_index_; + EmulationSession::GetInstance().HaltEmulation(); + ChangeProgram(m_next_program_index); + }); + + OnEmulationStarted(); + return Core::SystemResultStatus::Success; +} + + +Core::SystemResultStatus EmulationSession::BootOS() { + std::scoped_lock lock(m_mutex); + + // Create the render window. + m_window = std::make_unique(&m_input_subsystem, m_native_window, m_size, m_vulkan_library); + + // Initialize system. + m_system.SetShuttingDown(false); + m_system.ApplySettings(); + YuzuSettings::LogSettings(); + m_system.HIDCore().ReloadInputDevices(); + m_system.SetFrontendAppletSet({ + nullptr, // Amiibo Settings + nullptr, // Controller Selector + nullptr, // Error Display + nullptr, // Mii Editor + nullptr, // Parental Controls + nullptr, // Photo Viewer + nullptr, // Profile Selector + nullptr, // std::move(android_keyboard), // Software Keyboard + nullptr, // Web Browser + }); + + constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch); + auto bis_system = m_system.GetFileSystemController().GetSystemNANDContents(); + + auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); + + m_system.GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::QLaunch); + + const auto filename = qlaunch_applet_nca->GetFullPath(); + + auto params = Service::AM::FrontendAppletParameters { + .program_id = QLaunchId, + .applet_id = Service::AM::AppletId::QLaunch, + .applet_type = Service::AM::AppletType::LibraryApplet + }; + + m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filename, params); + + if (m_load_result != Core::SystemResultStatus::Success) { + return m_load_result; + } + + // Complete initialization. + m_system.GPU().Start(); + m_system.GetCpuManager().OnGpuReady(); + m_system.RegisterExitCallback([&] { HaltEmulation(); }); + + if (YuzuSettings::values.use_disk_shader_cache.GetValue()) { + m_system.Renderer().ReadRasterizer()->LoadDiskResources( + m_system.GetApplicationProcessProgramID(), std::stop_token{}, + [](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); + } + + // Register an ExecuteProgram callback such that Core can execute a sub-program + m_system.RegisterExecuteProgramCallback([&](std::size_t program_index_) { + m_next_program_index = program_index_; + EmulationSession::GetInstance().HaltEmulation(); + }); + + OnEmulationStarted(); + return Core::SystemResultStatus::Success; +} + +void EmulationSession::ShutdownEmulation() { + std::scoped_lock lock(m_mutex); + + if (m_next_program_index != -1) { + ChangeProgram(m_next_program_index); + m_next_program_index = -1; + } + + m_is_running = false; + + // Unload user input. + m_system.HIDCore().UnloadInputDevices(); + + // Enable all controllers + m_system.HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All}); + + // Shutdown the main emulated process + if (m_load_result == Core::SystemResultStatus::Success) { + m_system.DetachDebugger(); + m_system.ShutdownMainProcess(); + m_detached_tasks.WaitForAllTasks(); + m_load_result = Core::SystemResultStatus::ErrorNotInitialized; + m_window.reset(); + OnEmulationStopped(Core::SystemResultStatus::Success); + return; + } + + // Tear down the render window. + m_window.reset(); +} + +void EmulationSession::PauseEmulation() { + std::scoped_lock lock(m_mutex); + m_system.Pause(); + m_is_paused = true; +} + +void EmulationSession::UnPauseEmulation() { + std::scoped_lock lock(m_mutex); + m_system.Run(); + m_is_paused = false; +} + +void EmulationSession::HaltEmulation() { + std::scoped_lock lock(m_mutex); + m_is_running = false; + m_cv.notify_one(); +} + +void EmulationSession::RunEmulation() { + { + std::scoped_lock lock(m_mutex); + m_is_running = true; + } + + // Load the disk shader cache. + if (YuzuSettings::values.use_disk_shader_cache.GetValue()) { + LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); + m_system.Renderer().ReadRasterizer()->LoadDiskResources( + m_system.GetApplicationProcessProgramID(), std::stop_token{}, LoadDiskCacheProgress); + LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); + } + + void(m_system.Run()); + + if (m_system.DebuggerEnabled()) { + m_system.InitializeDebugger(); + } + + while (true) { + { + [[maybe_unused]] std::unique_lock lock(m_mutex); + if (m_cv.wait_for(lock, std::chrono::milliseconds(800), + [&]() { return !m_is_running; })) { + // Emulation halted. + break; + } + } + } + + // Reset current applet ID. + m_applet_id = static_cast(Service::AM::AppletId::Application); +} + +void EmulationSession::LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, + int max) { + +} + +void EmulationSession::OnEmulationStarted() { + +} + +void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) { + +} + +void EmulationSession::ChangeProgram(std::size_t program_index) { + LOG_INFO(Frontend, "Trying To Switch Program"); + // Halt current emulation session + EmulationSession::GetInstance().HaltEmulation(); + // Save the current state if necessary + + // Shutdown the current emulation session cleanly + // Update the program index + EmulationSession::GetInstance().m_next_program_index = program_index; + + // Initialize the new program + // Start the new emulation session + EmulationSession::GetInstance().RunEmulation(); +} + +u64 EmulationSession::GetProgramId(std::string programId) { + try { + return std::stoull(programId); + } catch (...) { + return 0; + } +} + +static Core::SystemResultStatus RunEmulation(const std::string& filepath, + const size_t program_index, + const bool frontend_initiated) { + MicroProfileOnThreadCreate("EmuThread"); + SCOPE_EXIT { + MicroProfileShutdown(); + }; + + LOG_INFO(Frontend, "starting"); + + if (filepath.empty()) { + LOG_CRITICAL(Frontend, "failed to load: filepath empty!"); + return Core::SystemResultStatus::ErrorLoader; + } + + SCOPE_EXIT { + EmulationSession::GetInstance().ShutdownEmulation(); + }; + + jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath, program_index, + frontend_initiated); + if (result != Core::SystemResultStatus::Success) { + return result; + } + + EmulationSession::GetInstance().RunEmulation(); + + return Core::SystemResultStatus::Success; +} + + + +bool EmulationSession::IsHandheldOnly() { + jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag(); + + if (npad_style_set.fullkey == 1) { + return false; + } + + if (npad_style_set.handheld == 0) { + return false; + } + + return !YuzuSettings::IsDockedMode(); +} + +void EmulationSession::SetDeviceType([[maybe_unused]] int index, int type) { + jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); + controller->SetNpadStyleIndex(static_cast(type)); +} + +void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) { + jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); + + // Ensure that player1 is configured correctly and handheld disconnected + if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) { + jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); + + if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) { + handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey); + controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey); + handheld->Disconnect(); + } + } + + // Ensure that handheld is configured correctly and player 1 disconnected + if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) { + jauto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); + + if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) { + player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); + controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); + player1->Disconnect(); + } + } + + if (!controller->IsConnected()) { + controller->Connect(); + } +} + +void EmulationSession::OnGamepadDisconnectEvent([[maybe_unused]] int index) { + jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); + controller->Disconnect(); +} diff --git a/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.h b/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.h new file mode 100644 index 0000000000..d099c88c55 --- /dev/null +++ b/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.h @@ -0,0 +1,80 @@ +// +// EmulationWindow.h - Sudachi +// Created by Jarrod Norwell on 1/18/24. +// + +#pragma once + +#import +#import +#import + +#include +#include + +#include "core/frontend/emu_window.h" +#include "core/frontend/graphics_context.h" +#include "input_common/main.h" + +class GraphicsContext_Apple final : public Core::Frontend::GraphicsContext { +public: + explicit GraphicsContext_Apple(std::shared_ptr driver_library) + : m_driver_library{driver_library} {} + + ~GraphicsContext_Apple() = default; + + std::shared_ptr GetDriverLibrary() override { + return m_driver_library; + } + +private: + std::shared_ptr m_driver_library; +}; + +NS_ASSUME_NONNULL_BEGIN + +class EmulationWindow final : public Core::Frontend::EmuWindow { +public: + EmulationWindow(InputCommon::InputSubsystem* input_subsystem, CA::MetalLayer* surface, CGSize size, + std::shared_ptr driver_library); + + ~EmulationWindow() = default; + + void OnSurfaceChanged(CA::MetalLayer* surface, CGSize size); + void OrientationChanged(UIInterfaceOrientation orientation); + void OnFrameDisplayed() override; + + void OnTouchPressed(int id, float x, float y); + void OnTouchMoved(int id, float x, float y); + void OnTouchReleased(int id); + + void OnGamepadButtonEvent(int player_index, int button_id, bool pressed); + void OnGamepadJoystickEvent(int player_index, int stick_id, float x, float y); + void OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x, float gyro_y, float gyro_z, float accel_x, float accel_y, float accel_z); + + std::unique_ptr CreateSharedContext() const override { + return {std::make_unique(m_driver_library)}; + } + + + bool HasFirstFrame() const { + return m_first_frame; + } + + bool IsShown() const override { + return true; + }; + +private: + float m_window_width{}; + float m_window_height{}; + CGSize m_size; + bool is_portrait = true; + + InputCommon::InputSubsystem* m_input_subsystem{}; + std::shared_ptr m_driver_library; + + bool m_first_frame = false; +}; + +NS_ASSUME_NONNULL_END diff --git a/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.mm b/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.mm new file mode 100644 index 0000000000..a0e388e751 --- /dev/null +++ b/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.mm @@ -0,0 +1,85 @@ +// +// EmulationWindow.mm - Sudachi +// Created by Jarrod Norwell on 1/18/24. +// + +#import "EmulationWindow.h" +#import "../EmulationSession/EmulationSession.h" + +#include + +#include "common/logging/log.h" +#include "input_common/drivers/touch_screen.h" +#include "input_common/drivers/virtual_amiibo.h" +#include "input_common/drivers/virtual_gamepad.h" +#include "input_common/main.h" + +void EmulationWindow::OnSurfaceChanged(CA::MetalLayer* surface, CGSize size) { + m_size = size; + + m_window_width = size.width; + m_window_height = size.height; + + // Ensures that we emulate with the correct aspect ratio. + // UpdateCurrentFramebufferLayout(m_window_width, m_window_height); + + window_info.render_surface = reinterpret_cast(surface); + window_info.render_surface_scale = [[UIScreen mainScreen] nativeScale]; +} + +void EmulationWindow::OrientationChanged(UIInterfaceOrientation orientation) { + is_portrait = orientation == UIInterfaceOrientationPortrait; +} + +void EmulationWindow::OnTouchPressed(int id, float x, float y) { + const auto [touch_x, touch_y] = MapToTouchScreen(x, y); + EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchPressed(touch_x, + touch_y, id); +} + +void EmulationWindow::OnTouchMoved(int id, float x, float y) { + const auto [touch_x, touch_y] = MapToTouchScreen(x, y); + EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchMoved(touch_x, + touch_y, id); +} + +void EmulationWindow::OnTouchReleased(int id) { + EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchReleased(id); +} + +void EmulationWindow::OnGamepadButtonEvent(int player_index, int button_id, bool pressed) { + m_input_subsystem->GetVirtualGamepad()->SetButtonState(player_index, button_id, pressed); +} + +void EmulationWindow::OnGamepadJoystickEvent(int player_index, int stick_id, float x, float y) { + m_input_subsystem->GetVirtualGamepad()->SetStickPosition(player_index, stick_id, x, y); +} + +void EmulationWindow::OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x, + float gyro_y, float gyro_z, float accel_x, + float accel_y, float accel_z) { + m_input_subsystem->GetVirtualGamepad()->SetMotionState(player_index, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z); +} + +void EmulationWindow::OnFrameDisplayed() { + if (!m_first_frame) { + m_first_frame = true; + } +} + +EmulationWindow::EmulationWindow(InputCommon::InputSubsystem* input_subsystem, CA::MetalLayer* surface, CGSize size, + std::shared_ptr driver_library) +: m_input_subsystem{input_subsystem}, m_size{size}, m_driver_library{driver_library} { + LOG_INFO(Frontend, "initializing"); + + if (!surface) { + LOG_CRITICAL(Frontend, "surface is nullptr"); + return; + } + + OnSurfaceChanged(surface, m_size); + window_info.render_surface_scale = [[UIScreen mainScreen] nativeScale]; + window_info.type = Core::Frontend::WindowSystemType::Cocoa; + + m_input_subsystem->Initialize(); +}