Browse Source
sudachi ios stuff
sudachi ios stuff
14 changed files with 2644 additions and 0 deletions
-
11src/ios/Eden/AppUI-Bridging-Header.h
-
108src/ios/Eden/AppUI.swift
-
26src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.h
-
434src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm
-
92src/ios/Eden/Wrapper/AppUIObjC.h
-
258src/ios/Eden/Wrapper/AppUIObjC.mm
-
568src/ios/Eden/Wrapper/Config/Config.h
-
330src/ios/Eden/Wrapper/Config/Config.mm
-
10src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h
-
16src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm
-
98src/ios/Eden/Wrapper/EmulationSession/EmulationSession.h
-
528src/ios/Eden/Wrapper/EmulationSession/EmulationSession.mm
-
80src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.h
-
85src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.mm
@ -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 */ |
|||
@ -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() |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// |
|||
// AppUIGameInformation.h - Sudachi |
|||
// Created by Jarrod Norwell on 1/20/24. |
|||
// |
|||
|
|||
#import <Foundation/Foundation.h> |
|||
|
|||
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 |
|||
@ -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<u8> icon; |
|||
bool isHomebrew; |
|||
}; |
|||
|
|||
|
|||
class SdlConfig final : public YuzuConfig { |
|||
public: |
|||
explicit SdlConfig(std::optional<std::string> 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<YuzuSettings::BasicSetting*>& FindRelevantList(YuzuSettings::Category category) override; |
|||
|
|||
public: |
|||
static const std::array<int, YuzuSettings::NativeButton::NumButtons> default_buttons; |
|||
static const std::array<int, YuzuSettings::NativeMotion::NumMotions> default_motions; |
|||
static const std::array<std::array<int, 4>, YuzuSettings::NativeAnalog::NumAnalogs> default_analogs; |
|||
static const std::array<int, 2> default_stick_mod; |
|||
static const std::array<int, 2> default_ringcon_analogs; |
|||
}; |
|||
|
|||
|
|||
#define SDL_MAIN_HANDLED |
|||
#include <SDL.h> |
|||
|
|||
#include "common/logging/log.h" |
|||
#include "input_common/main.h" |
|||
|
|||
const std::array<int, YuzuSettings::NativeButton::NumButtons> 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<int, YuzuSettings::NativeMotion::NumMotions> SdlConfig::default_motions = { |
|||
SDL_SCANCODE_7, |
|||
SDL_SCANCODE_8, |
|||
}; |
|||
|
|||
const std::array<std::array<int, 4>, 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<int, 2> SdlConfig::default_stick_mod = { |
|||
SDL_SCANCODE_D, |
|||
0, |
|||
}; |
|||
|
|||
const std::array<int, 2> SdlConfig::default_ringcon_analogs{{ |
|||
0, |
|||
0, |
|||
}}; |
|||
|
|||
SdlConfig::SdlConfig(const std::optional<std::string> 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<YuzuSettings::BasicSetting*>& SdlConfig::FindRelevantList(YuzuSettings::Category category) { |
|||
return YuzuSettings::values.linkage.by_category[category]; |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
std::unordered_map<std::string, GameMetadata> 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::AppLoader_NRO*>(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 |
|||
@ -0,0 +1,92 @@ |
|||
// |
|||
// AppUIObjC.h - Sudachi |
|||
// Created by Jarrod Norwell on 1/8/24. |
|||
// |
|||
|
|||
#import <Foundation/Foundation.h> |
|||
#import <MetalKit/MetalKit.h> |
|||
|
|||
#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<NSURL *> *)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 |
|||
@ -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 <mach/mach.h> |
|||
|
|||
@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<u64>(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<NSURL *> *)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 |
|||
@ -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_<title_id>` 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 <filesystem> |
|||
#include <memory> |
|||
#include <optional> |
|||
#include <string> |
|||
|
|||
#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 <typename Type, bool ranged> |
|||
void ReadSetting(const std::string& group, YuzuSettings::Setting<Type, ranged>& setting); |
|||
|
|||
void ReadValues(); |
|||
|
|||
const ConfigType type; |
|||
std::unique_ptr<INIReader> config; |
|||
std::string config_loc; |
|||
const bool global; |
|||
}; |
|||
@ -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 <memory> |
|||
#include <optional> |
|||
#include <sstream> |
|||
|
|||
#include <INIReader.h> |
|||
#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<INIReader>(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<INIReader>(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<std::string>& 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<bool>& setting) { |
|||
setting = config->GetBoolean(group, setting.GetLabel(), setting.GetDefault()); |
|||
} |
|||
|
|||
template <typename Type, bool ranged> |
|||
void Config::ReadSetting(const std::string& group, YuzuSettings::Setting<Type, ranged>& setting) { |
|||
setting = static_cast<Type>( |
|||
config->GetInteger(group, setting.GetLabel(), static_cast<long>(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<int>(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<YuzuSettings::GpuAccuracy>(config->GetInteger( |
|||
"Renderer", "gpu_accuracy", static_cast<u32>(YuzuSettings::GpuAccuracy::Normal))); |
|||
|
|||
// Use GPU default anisotropic filtering on Android |
|||
YuzuSettings::values.max_anisotropy = |
|||
static_cast<YuzuSettings::AnisotropyMode>(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<std::string> 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(); |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
// |
|||
// DirectoryManager.h - Sudachi |
|||
// Created by Jarrod Norwell on 1/18/24. |
|||
// |
|||
|
|||
#pragma once |
|||
|
|||
namespace DirectoryManager { |
|||
const char* AppUIDirectory(void); |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// |
|||
// DirectoryManager.mm - Sudachi |
|||
// Created by Jarrod Norwell on 1/18/24. |
|||
// |
|||
|
|||
#import <Foundation/Foundation.h> |
|||
|
|||
#import "DirectoryManager.h" |
|||
|
|||
NSURL *DocumentsDirectory() { |
|||
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject]; |
|||
} |
|||
|
|||
const char* DirectoryManager::AppUIDirectory(void) { |
|||
return [[DocumentsDirectory() path] UTF8String]; |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
// |
|||
// EmulationSession.h - Sudachi |
|||
// Created by Jarrod Norwell on 1/20/24. |
|||
// |
|||
|
|||
#pragma once |
|||
|
|||
#import <QuartzCore/CAMetalLayer.h> |
|||
|
|||
#import <Metal/Metal.hpp> |
|||
#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<EmulationWindow> 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<FileSys::VfsFilesystem> m_vfs; |
|||
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; |
|||
std::atomic<bool> m_is_running = false; |
|||
std::atomic<bool> m_is_paused = false; |
|||
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider; |
|||
int m_applet_id{1}; |
|||
|
|||
// GPU driver parameters |
|||
std::shared_ptr<Common::DynamicLibrary> 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<s32> m_next_program_index = -1; |
|||
}; |
|||
@ -0,0 +1,528 @@ |
|||
// |
|||
// EmulationSession.m - Sudachi |
|||
// Created by Jarrod Norwell on 1/20/24. |
|||
// |
|||
|
|||
#import "EmulationSession.h" |
|||
|
|||
#include <SDL.h> |
|||
|
|||
#include <codecvt> |
|||
#include <locale> |
|||
#include <string> |
|||
#include <string_view> |
|||
#include <dlfcn.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/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<FileSys::RealVfsFilesystem>(); |
|||
} |
|||
|
|||
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<Common::DynamicLibrary>(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<FileSys::NSP>(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<FileSys::ManualContentProvider>(); |
|||
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); |
|||
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<Service::AM::AppletId>(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<EmulationWindow>(&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<Service::AM::AppletId>(m_applet_id), |
|||
.launch_type = frontend_initiated ? Service::AM::LaunchType::FrontendInitiated |
|||
: Service::AM::LaunchType::ApplicationInitiated, |
|||
.program_index = static_cast<s32>(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<EmulationWindow>(&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<u64>(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<int>(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<Core::HID::NpadStyleIndex>(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(); |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
// |
|||
// EmulationWindow.h - Sudachi |
|||
// Created by Jarrod Norwell on 1/18/24. |
|||
// |
|||
|
|||
#pragma once |
|||
|
|||
#import <Metal/Metal.hpp> |
|||
#import <Foundation/Foundation.h> |
|||
#import <UIKit/UIKit.h> |
|||
|
|||
#include <memory> |
|||
#include <span> |
|||
|
|||
#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<Common::DynamicLibrary> driver_library) |
|||
: m_driver_library{driver_library} {} |
|||
|
|||
~GraphicsContext_Apple() = default; |
|||
|
|||
std::shared_ptr<Common::DynamicLibrary> GetDriverLibrary() override { |
|||
return m_driver_library; |
|||
} |
|||
|
|||
private: |
|||
std::shared_ptr<Common::DynamicLibrary> 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<Common::DynamicLibrary> 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<Core::Frontend::GraphicsContext> CreateSharedContext() const override { |
|||
return {std::make_unique<GraphicsContext_Apple>(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<Common::DynamicLibrary> m_driver_library; |
|||
|
|||
bool m_first_frame = false; |
|||
}; |
|||
|
|||
NS_ASSUME_NONNULL_END |
|||
@ -0,0 +1,85 @@ |
|||
// |
|||
// EmulationWindow.mm - Sudachi |
|||
// Created by Jarrod Norwell on 1/18/24. |
|||
// |
|||
|
|||
#import "EmulationWindow.h" |
|||
#import "../EmulationSession/EmulationSession.h" |
|||
|
|||
#include <SDL.h> |
|||
|
|||
#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<void*>(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<Common::DynamicLibrary> 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(); |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue