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