|
|
|
@ -33,7 +33,6 @@ |
|
|
|
#include "core/crypto/key_manager.h"
|
|
|
|
#include "core/file_sys/card_image.h"
|
|
|
|
#include "core/file_sys/content_archive.h"
|
|
|
|
#include "core/file_sys/registered_cache.h"
|
|
|
|
#include "core/file_sys/submission_package.h"
|
|
|
|
#include "core/file_sys/vfs.h"
|
|
|
|
#include "core/file_sys/vfs_real.h"
|
|
|
|
@ -48,514 +47,416 @@ |
|
|
|
#include "core/hid/emulated_controller.h"
|
|
|
|
#include "core/hid/hid_core.h"
|
|
|
|
#include "core/hid/hid_types.h"
|
|
|
|
#include "core/hle/service/acc/profile_manager.h"
|
|
|
|
#include "core/hle/service/am/applet_ae.h"
|
|
|
|
#include "core/hle/service/am/applet_oe.h"
|
|
|
|
#include "core/hle/service/am/applets/applets.h"
|
|
|
|
#include "core/hle/service/filesystem/filesystem.h"
|
|
|
|
#include "core/loader/loader.h"
|
|
|
|
#include "core/perf_stats.h"
|
|
|
|
#include "jni/android_common/android_common.h"
|
|
|
|
#include "jni/applets/software_keyboard.h"
|
|
|
|
#include "jni/config.h"
|
|
|
|
#include "jni/emu_window/emu_window.h"
|
|
|
|
#include "jni/id_cache.h"
|
|
|
|
#include "video_core/rasterizer_interface.h"
|
|
|
|
#include "jni/native.h"
|
|
|
|
#include "video_core/renderer_base.h"
|
|
|
|
|
|
|
|
#define jconst [[maybe_unused]] const auto
|
|
|
|
#define jauto [[maybe_unused]] auto
|
|
|
|
|
|
|
|
namespace { |
|
|
|
static EmulationSession s_instance; |
|
|
|
|
|
|
|
class EmulationSession final { |
|
|
|
public: |
|
|
|
EmulationSession() { |
|
|
|
m_vfs = std::make_shared<FileSys::RealVfsFilesystem>(); |
|
|
|
} |
|
|
|
|
|
|
|
~EmulationSession() = default; |
|
|
|
|
|
|
|
static EmulationSession& GetInstance() { |
|
|
|
return s_instance; |
|
|
|
} |
|
|
|
|
|
|
|
const Core::System& System() const { |
|
|
|
return m_system; |
|
|
|
} |
|
|
|
EmulationSession::EmulationSession() { |
|
|
|
m_vfs = std::make_shared<FileSys::RealVfsFilesystem>(); |
|
|
|
} |
|
|
|
|
|
|
|
Core::System& System() { |
|
|
|
return m_system; |
|
|
|
} |
|
|
|
EmulationSession& EmulationSession::GetInstance() { |
|
|
|
return s_instance; |
|
|
|
} |
|
|
|
|
|
|
|
const EmuWindow_Android& Window() const { |
|
|
|
return *m_window; |
|
|
|
} |
|
|
|
const Core::System& EmulationSession::System() const { |
|
|
|
return m_system; |
|
|
|
} |
|
|
|
|
|
|
|
EmuWindow_Android& Window() { |
|
|
|
return *m_window; |
|
|
|
} |
|
|
|
Core::System& EmulationSession::System() { |
|
|
|
return m_system; |
|
|
|
} |
|
|
|
|
|
|
|
ANativeWindow* NativeWindow() const { |
|
|
|
return m_native_window; |
|
|
|
} |
|
|
|
const EmuWindow_Android& EmulationSession::Window() const { |
|
|
|
return *m_window; |
|
|
|
} |
|
|
|
|
|
|
|
void SetNativeWindow(ANativeWindow* native_window) { |
|
|
|
m_native_window = native_window; |
|
|
|
} |
|
|
|
EmuWindow_Android& EmulationSession::Window() { |
|
|
|
return *m_window; |
|
|
|
} |
|
|
|
|
|
|
|
int InstallFileToNand(std::string filename, std::string file_extension) { |
|
|
|
jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, |
|
|
|
std::size_t block_size) { |
|
|
|
if (src == nullptr || dest == nullptr) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
if (!dest->Resize(src->GetSize())) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
ANativeWindow* EmulationSession::NativeWindow() const { |
|
|
|
return m_native_window; |
|
|
|
} |
|
|
|
|
|
|
|
using namespace Common::Literals; |
|
|
|
[[maybe_unused]] std::vector<u8> buffer(1_MiB); |
|
|
|
void EmulationSession::SetNativeWindow(ANativeWindow* native_window) { |
|
|
|
m_native_window = native_window; |
|
|
|
} |
|
|
|
|
|
|
|
for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { |
|
|
|
jconst read = src->Read(buffer.data(), buffer.size(), i); |
|
|
|
dest->Write(buffer.data(), read, i); |
|
|
|
} |
|
|
|
return true; |
|
|
|
}; |
|
|
|
|
|
|
|
enum InstallResult { |
|
|
|
Success = 0, |
|
|
|
SuccessFileOverwritten = 1, |
|
|
|
InstallError = 2, |
|
|
|
ErrorBaseGame = 3, |
|
|
|
ErrorFilenameExtension = 4, |
|
|
|
}; |
|
|
|
|
|
|
|
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); |
|
|
|
m_system.GetFileSystemController().CreateFactories(*m_vfs); |
|
|
|
|
|
|
|
[[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp; |
|
|
|
if (file_extension == "nsp") { |
|
|
|
nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read)); |
|
|
|
if (nsp->IsExtractedType()) { |
|
|
|
return InstallError; |
|
|
|
} |
|
|
|
} else { |
|
|
|
return ErrorFilenameExtension; |
|
|
|
int EmulationSession::InstallFileToNand(std::string filename, std::string file_extension) { |
|
|
|
jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, |
|
|
|
std::size_t block_size) { |
|
|
|
if (src == nullptr || dest == nullptr) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
if (!nsp) { |
|
|
|
return InstallError; |
|
|
|
if (!dest->Resize(src->GetSize())) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
if (nsp->GetStatus() != Loader::ResultStatus::Success) { |
|
|
|
return InstallError; |
|
|
|
} |
|
|
|
using namespace Common::Literals; |
|
|
|
[[maybe_unused]] std::vector<u8> buffer(1_MiB); |
|
|
|
|
|
|
|
jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry( |
|
|
|
*nsp, true, copy_func); |
|
|
|
|
|
|
|
switch (res) { |
|
|
|
case FileSys::InstallResult::Success: |
|
|
|
return Success; |
|
|
|
case FileSys::InstallResult::OverwriteExisting: |
|
|
|
return SuccessFileOverwritten; |
|
|
|
case FileSys::InstallResult::ErrorBaseInstall: |
|
|
|
return ErrorBaseGame; |
|
|
|
default: |
|
|
|
return InstallError; |
|
|
|
for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { |
|
|
|
jconst read = src->Read(buffer.data(), buffer.size(), i); |
|
|
|
dest->Write(buffer.data(), read, i); |
|
|
|
} |
|
|
|
} |
|
|
|
return true; |
|
|
|
}; |
|
|
|
|
|
|
|
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir, |
|
|
|
const std::string& custom_driver_name, |
|
|
|
const std::string& file_redirect_dir) { |
|
|
|
#ifdef ARCHITECTURE_arm64
|
|
|
|
void* handle{}; |
|
|
|
const char* file_redirect_dir_{}; |
|
|
|
int featureFlags{}; |
|
|
|
|
|
|
|
// Enable driver file redirection when renderer debugging is enabled.
|
|
|
|
if (Settings::values.renderer_debug && file_redirect_dir.size()) { |
|
|
|
featureFlags |= ADRENOTOOLS_DRIVER_FILE_REDIRECT; |
|
|
|
file_redirect_dir_ = file_redirect_dir.c_str(); |
|
|
|
} |
|
|
|
enum InstallResult { |
|
|
|
Success = 0, |
|
|
|
SuccessFileOverwritten = 1, |
|
|
|
InstallError = 2, |
|
|
|
ErrorBaseGame = 3, |
|
|
|
ErrorFilenameExtension = 4, |
|
|
|
}; |
|
|
|
|
|
|
|
// Try to load a custom driver.
|
|
|
|
if (custom_driver_name.size()) { |
|
|
|
handle = adrenotools_open_libvulkan( |
|
|
|
RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(), |
|
|
|
custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr); |
|
|
|
} |
|
|
|
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); |
|
|
|
m_system.GetFileSystemController().CreateFactories(*m_vfs); |
|
|
|
|
|
|
|
// Try to load the system driver.
|
|
|
|
if (!handle) { |
|
|
|
handle = |
|
|
|
adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(), |
|
|
|
nullptr, nullptr, file_redirect_dir_, nullptr); |
|
|
|
[[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp; |
|
|
|
if (file_extension == "nsp") { |
|
|
|
nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read)); |
|
|
|
if (nsp->IsExtractedType()) { |
|
|
|
return InstallError; |
|
|
|
} |
|
|
|
|
|
|
|
m_vulkan_library = std::make_shared<Common::DynamicLibrary>(handle); |
|
|
|
#endif
|
|
|
|
} else { |
|
|
|
return ErrorFilenameExtension; |
|
|
|
} |
|
|
|
|
|
|
|
bool IsRunning() const { |
|
|
|
return m_is_running; |
|
|
|
if (!nsp) { |
|
|
|
return InstallError; |
|
|
|
} |
|
|
|
|
|
|
|
bool IsPaused() const { |
|
|
|
return m_is_running && m_is_paused; |
|
|
|
if (nsp->GetStatus() != Loader::ResultStatus::Success) { |
|
|
|
return InstallError; |
|
|
|
} |
|
|
|
|
|
|
|
const Core::PerfStatsResults& PerfStats() const { |
|
|
|
std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); |
|
|
|
return m_perf_stats; |
|
|
|
} |
|
|
|
jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true, |
|
|
|
copy_func); |
|
|
|
|
|
|
|
void SurfaceChanged() { |
|
|
|
if (!IsRunning()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
m_window->OnSurfaceChanged(m_native_window); |
|
|
|
switch (res) { |
|
|
|
case FileSys::InstallResult::Success: |
|
|
|
return Success; |
|
|
|
case FileSys::InstallResult::OverwriteExisting: |
|
|
|
return SuccessFileOverwritten; |
|
|
|
case FileSys::InstallResult::ErrorBaseInstall: |
|
|
|
return ErrorBaseGame; |
|
|
|
default: |
|
|
|
return InstallError; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void ConfigureFilesystemProvider(const std::string& filepath) { |
|
|
|
const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::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; |
|
|
|
} |
|
|
|
void EmulationSession::InitializeGpuDriver(const std::string& hook_lib_dir, |
|
|
|
const std::string& custom_driver_dir, |
|
|
|
const std::string& custom_driver_name, |
|
|
|
const std::string& file_redirect_dir) { |
|
|
|
#ifdef ARCHITECTURE_arm64
|
|
|
|
void* handle{}; |
|
|
|
const char* file_redirect_dir_{}; |
|
|
|
int featureFlags{}; |
|
|
|
|
|
|
|
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()); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// Enable driver file redirection when renderer debugging is enabled.
|
|
|
|
if (Settings::values.renderer_debug && file_redirect_dir.size()) { |
|
|
|
featureFlags |= ADRENOTOOLS_DRIVER_FILE_REDIRECT; |
|
|
|
file_redirect_dir_ = file_redirect_dir.c_str(); |
|
|
|
} |
|
|
|
|
|
|
|
Core::SystemResultStatus InitializeEmulation(const std::string& filepath) { |
|
|
|
std::scoped_lock lock(m_mutex); |
|
|
|
|
|
|
|
// Create the render window.
|
|
|
|
m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, |
|
|
|
m_vulkan_library); |
|
|
|
|
|
|
|
m_system.SetFilesystem(m_vfs); |
|
|
|
m_system.GetUserChannel().clear(); |
|
|
|
|
|
|
|
// Initialize system.
|
|
|
|
jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>(); |
|
|
|
m_software_keyboard = android_keyboard.get(); |
|
|
|
m_system.SetShuttingDown(false); |
|
|
|
m_system.ApplySettings(); |
|
|
|
Settings::LogSettings(); |
|
|
|
m_system.HIDCore().ReloadInputDevices(); |
|
|
|
m_system.SetAppletFrontendSet({ |
|
|
|
nullptr, // Amiibo Settings
|
|
|
|
nullptr, // Controller Selector
|
|
|
|
nullptr, // Error Display
|
|
|
|
nullptr, // Mii Editor
|
|
|
|
nullptr, // Parental Controls
|
|
|
|
nullptr, // Photo Viewer
|
|
|
|
nullptr, // Profile Selector
|
|
|
|
std::move(android_keyboard), // Software Keyboard
|
|
|
|
nullptr, // Web Browser
|
|
|
|
}); |
|
|
|
|
|
|
|
// Initialize filesystem.
|
|
|
|
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); |
|
|
|
ConfigureFilesystemProvider(filepath); |
|
|
|
|
|
|
|
// Initialize account manager
|
|
|
|
m_profile_manager = std::make_unique<Service::Account::ProfileManager>(); |
|
|
|
|
|
|
|
// Load the ROM.
|
|
|
|
m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath); |
|
|
|
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(); }); |
|
|
|
// Try to load a custom driver.
|
|
|
|
if (custom_driver_name.size()) { |
|
|
|
handle = adrenotools_open_libvulkan( |
|
|
|
RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(), |
|
|
|
custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr); |
|
|
|
} |
|
|
|
|
|
|
|
return Core::SystemResultStatus::Success; |
|
|
|
// Try to load the system driver.
|
|
|
|
if (!handle) { |
|
|
|
handle = adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(), |
|
|
|
nullptr, nullptr, file_redirect_dir_, nullptr); |
|
|
|
} |
|
|
|
|
|
|
|
void ShutdownEmulation() { |
|
|
|
std::scoped_lock lock(m_mutex); |
|
|
|
m_vulkan_library = std::make_shared<Common::DynamicLibrary>(handle); |
|
|
|
#endif
|
|
|
|
} |
|
|
|
|
|
|
|
m_is_running = false; |
|
|
|
bool EmulationSession::IsRunning() const { |
|
|
|
return m_is_running; |
|
|
|
} |
|
|
|
|
|
|
|
// Unload user input.
|
|
|
|
m_system.HIDCore().UnloadInputDevices(); |
|
|
|
bool EmulationSession::IsPaused() const { |
|
|
|
return m_is_running && m_is_paused; |
|
|
|
} |
|
|
|
|
|
|
|
// 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; |
|
|
|
} |
|
|
|
const Core::PerfStatsResults& EmulationSession::PerfStats() const { |
|
|
|
std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); |
|
|
|
return m_perf_stats; |
|
|
|
} |
|
|
|
|
|
|
|
// Tear down the render window.
|
|
|
|
m_window.reset(); |
|
|
|
void EmulationSession::SurfaceChanged() { |
|
|
|
if (!IsRunning()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
m_window->OnSurfaceChanged(m_native_window); |
|
|
|
} |
|
|
|
|
|
|
|
void PauseEmulation() { |
|
|
|
std::scoped_lock lock(m_mutex); |
|
|
|
m_system.Pause(); |
|
|
|
m_is_paused = true; |
|
|
|
void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) { |
|
|
|
const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read); |
|
|
|
if (!file) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
void UnPauseEmulation() { |
|
|
|
std::scoped_lock lock(m_mutex); |
|
|
|
m_system.Run(); |
|
|
|
m_is_paused = false; |
|
|
|
auto loader = Loader::GetLoader(m_system, file); |
|
|
|
if (!loader) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
void HaltEmulation() { |
|
|
|
std::scoped_lock lock(m_mutex); |
|
|
|
m_is_running = false; |
|
|
|
m_cv.notify_one(); |
|
|
|
const auto file_type = loader->GetFileType(); |
|
|
|
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
void RunEmulation() { |
|
|
|
{ |
|
|
|
std::scoped_lock lock(m_mutex); |
|
|
|
m_is_running = true; |
|
|
|
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()); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Load the disk shader cache.
|
|
|
|
if (Settings::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); |
|
|
|
} |
|
|
|
Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath) { |
|
|
|
std::scoped_lock lock(m_mutex); |
|
|
|
|
|
|
|
// Create the render window.
|
|
|
|
m_window = |
|
|
|
std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, m_vulkan_library); |
|
|
|
|
|
|
|
m_system.SetFilesystem(m_vfs); |
|
|
|
m_system.GetUserChannel().clear(); |
|
|
|
|
|
|
|
// Initialize system.
|
|
|
|
jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>(); |
|
|
|
m_software_keyboard = android_keyboard.get(); |
|
|
|
m_system.SetShuttingDown(false); |
|
|
|
m_system.ApplySettings(); |
|
|
|
Settings::LogSettings(); |
|
|
|
m_system.HIDCore().ReloadInputDevices(); |
|
|
|
m_system.SetAppletFrontendSet({ |
|
|
|
nullptr, // Amiibo Settings
|
|
|
|
nullptr, // Controller Selector
|
|
|
|
nullptr, // Error Display
|
|
|
|
nullptr, // Mii Editor
|
|
|
|
nullptr, // Parental Controls
|
|
|
|
nullptr, // Photo Viewer
|
|
|
|
nullptr, // Profile Selector
|
|
|
|
std::move(android_keyboard), // Software Keyboard
|
|
|
|
nullptr, // Web Browser
|
|
|
|
}); |
|
|
|
|
|
|
|
// Initialize filesystem.
|
|
|
|
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); |
|
|
|
ConfigureFilesystemProvider(filepath); |
|
|
|
|
|
|
|
// Initialize account manager
|
|
|
|
m_profile_manager = std::make_unique<Service::Account::ProfileManager>(); |
|
|
|
|
|
|
|
// Load the ROM.
|
|
|
|
m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath); |
|
|
|
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(); }); |
|
|
|
|
|
|
|
void(m_system.Run()); |
|
|
|
return Core::SystemResultStatus::Success; |
|
|
|
} |
|
|
|
|
|
|
|
if (m_system.DebuggerEnabled()) { |
|
|
|
m_system.InitializeDebugger(); |
|
|
|
} |
|
|
|
void EmulationSession::ShutdownEmulation() { |
|
|
|
std::scoped_lock lock(m_mutex); |
|
|
|
|
|
|
|
OnEmulationStarted(); |
|
|
|
m_is_running = false; |
|
|
|
|
|
|
|
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; |
|
|
|
} |
|
|
|
} |
|
|
|
{ |
|
|
|
// Refresh performance stats.
|
|
|
|
std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); |
|
|
|
m_perf_stats = m_system.GetAndResetPerfStats(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
std::string GetRomTitle(const std::string& path) { |
|
|
|
return GetRomMetadata(path).title; |
|
|
|
} |
|
|
|
|
|
|
|
std::vector<u8> GetRomIcon(const std::string& path) { |
|
|
|
return GetRomMetadata(path).icon; |
|
|
|
} |
|
|
|
// Unload user input.
|
|
|
|
m_system.HIDCore().UnloadInputDevices(); |
|
|
|
|
|
|
|
bool GetIsHomebrew(const std::string& path) { |
|
|
|
return GetRomMetadata(path).isHomebrew; |
|
|
|
// 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; |
|
|
|
} |
|
|
|
|
|
|
|
void ResetRomMetadata() { |
|
|
|
m_rom_metadata_cache.clear(); |
|
|
|
} |
|
|
|
// Tear down the render window.
|
|
|
|
m_window.reset(); |
|
|
|
} |
|
|
|
|
|
|
|
bool IsHandheldOnly() { |
|
|
|
jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag(); |
|
|
|
void EmulationSession::PauseEmulation() { |
|
|
|
std::scoped_lock lock(m_mutex); |
|
|
|
m_system.Pause(); |
|
|
|
m_is_paused = true; |
|
|
|
} |
|
|
|
|
|
|
|
if (npad_style_set.fullkey == 1) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
void EmulationSession::UnPauseEmulation() { |
|
|
|
std::scoped_lock lock(m_mutex); |
|
|
|
m_system.Run(); |
|
|
|
m_is_paused = false; |
|
|
|
} |
|
|
|
|
|
|
|
if (npad_style_set.handheld == 0) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
void EmulationSession::HaltEmulation() { |
|
|
|
std::scoped_lock lock(m_mutex); |
|
|
|
m_is_running = false; |
|
|
|
m_cv.notify_one(); |
|
|
|
} |
|
|
|
|
|
|
|
return !Settings::IsDockedMode(); |
|
|
|
void EmulationSession::RunEmulation() { |
|
|
|
{ |
|
|
|
std::scoped_lock lock(m_mutex); |
|
|
|
m_is_running = true; |
|
|
|
} |
|
|
|
|
|
|
|
void SetDeviceType([[maybe_unused]] int index, int type) { |
|
|
|
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); |
|
|
|
controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type)); |
|
|
|
// Load the disk shader cache.
|
|
|
|
if (Settings::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 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); |
|
|
|
void(m_system.Run()); |
|
|
|
|
|
|
|
if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) { |
|
|
|
handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); |
|
|
|
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); |
|
|
|
handheld->Disconnect(); |
|
|
|
} |
|
|
|
} |
|
|
|
if (m_system.DebuggerEnabled()) { |
|
|
|
m_system.InitializeDebugger(); |
|
|
|
} |
|
|
|
|
|
|
|
// 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); |
|
|
|
OnEmulationStarted(); |
|
|
|
|
|
|
|
if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) { |
|
|
|
player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); |
|
|
|
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); |
|
|
|
player1->Disconnect(); |
|
|
|
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; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (!controller->IsConnected()) { |
|
|
|
controller->Connect(); |
|
|
|
{ |
|
|
|
// Refresh performance stats.
|
|
|
|
std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); |
|
|
|
m_perf_stats = m_system.GetAndResetPerfStats(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
bool EmulationSession::IsHandheldOnly() { |
|
|
|
jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag(); |
|
|
|
|
|
|
|
void OnGamepadDisconnectEvent([[maybe_unused]] int index) { |
|
|
|
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); |
|
|
|
controller->Disconnect(); |
|
|
|
if (npad_style_set.fullkey == 1) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard() { |
|
|
|
return m_software_keyboard; |
|
|
|
if (npad_style_set.handheld == 0) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
private: |
|
|
|
struct RomMetadata { |
|
|
|
std::string title; |
|
|
|
std::vector<u8> icon; |
|
|
|
bool isHomebrew; |
|
|
|
}; |
|
|
|
return !Settings::IsDockedMode(); |
|
|
|
} |
|
|
|
|
|
|
|
RomMetadata GetRomMetadata(const std::string& path) { |
|
|
|
if (jauto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) { |
|
|
|
return search->second; |
|
|
|
} |
|
|
|
void EmulationSession::SetDeviceType([[maybe_unused]] int index, int type) { |
|
|
|
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); |
|
|
|
controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type)); |
|
|
|
} |
|
|
|
|
|
|
|
return CacheRomMetadata(path); |
|
|
|
} |
|
|
|
void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) { |
|
|
|
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); |
|
|
|
|
|
|
|
RomMetadata CacheRomMetadata(const std::string& path) { |
|
|
|
jconst file = Core::GetGameFileFromPath(m_vfs, path); |
|
|
|
jauto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0); |
|
|
|
// 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); |
|
|
|
|
|
|
|
RomMetadata entry; |
|
|
|
loader->ReadTitle(entry.title); |
|
|
|
loader->ReadIcon(entry.icon); |
|
|
|
if (loader->GetFileType() == Loader::FileType::NRO) { |
|
|
|
jauto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get()); |
|
|
|
entry.isHomebrew = loader_nro->IsHomebrew(); |
|
|
|
} else { |
|
|
|
entry.isHomebrew = false; |
|
|
|
if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) { |
|
|
|
handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); |
|
|
|
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); |
|
|
|
handheld->Disconnect(); |
|
|
|
} |
|
|
|
|
|
|
|
m_rom_metadata_cache[path] = entry; |
|
|
|
|
|
|
|
return entry; |
|
|
|
} |
|
|
|
|
|
|
|
private: |
|
|
|
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max) { |
|
|
|
JNIEnv* env = IDCache::GetEnvForThread(); |
|
|
|
env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(), |
|
|
|
IDCache::GetDiskCacheLoadProgress(), static_cast<jint>(stage), |
|
|
|
static_cast<jint>(progress), static_cast<jint>(max)); |
|
|
|
} |
|
|
|
// 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); |
|
|
|
|
|
|
|
static void OnEmulationStarted() { |
|
|
|
JNIEnv* env = IDCache::GetEnvForThread(); |
|
|
|
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), |
|
|
|
IDCache::GetOnEmulationStarted()); |
|
|
|
if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) { |
|
|
|
player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); |
|
|
|
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); |
|
|
|
player1->Disconnect(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static void OnEmulationStopped(Core::SystemResultStatus result) { |
|
|
|
JNIEnv* env = IDCache::GetEnvForThread(); |
|
|
|
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), |
|
|
|
IDCache::GetOnEmulationStopped(), static_cast<jint>(result)); |
|
|
|
if (!controller->IsConnected()) { |
|
|
|
controller->Connect(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private: |
|
|
|
static EmulationSession s_instance; |
|
|
|
|
|
|
|
// Frontend management
|
|
|
|
std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache; |
|
|
|
|
|
|
|
// Window management
|
|
|
|
std::unique_ptr<EmuWindow_Android> m_window; |
|
|
|
ANativeWindow* 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; |
|
|
|
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; |
|
|
|
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager; |
|
|
|
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider; |
|
|
|
void EmulationSession::OnGamepadDisconnectEvent([[maybe_unused]] int index) { |
|
|
|
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); |
|
|
|
controller->Disconnect(); |
|
|
|
} |
|
|
|
|
|
|
|
// GPU driver parameters
|
|
|
|
std::shared_ptr<Common::DynamicLibrary> m_vulkan_library; |
|
|
|
SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() { |
|
|
|
return m_software_keyboard; |
|
|
|
} |
|
|
|
|
|
|
|
// Synchronization
|
|
|
|
std::condition_variable_any m_cv; |
|
|
|
mutable std::mutex m_perf_stats_mutex; |
|
|
|
mutable std::mutex m_mutex; |
|
|
|
}; |
|
|
|
void EmulationSession::LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, |
|
|
|
int max) { |
|
|
|
JNIEnv* env = IDCache::GetEnvForThread(); |
|
|
|
env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(), |
|
|
|
IDCache::GetDiskCacheLoadProgress(), static_cast<jint>(stage), |
|
|
|
static_cast<jint>(progress), static_cast<jint>(max)); |
|
|
|
} |
|
|
|
|
|
|
|
/*static*/ EmulationSession EmulationSession::s_instance; |
|
|
|
void EmulationSession::OnEmulationStarted() { |
|
|
|
JNIEnv* env = IDCache::GetEnvForThread(); |
|
|
|
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnEmulationStarted()); |
|
|
|
} |
|
|
|
|
|
|
|
} // Anonymous namespace
|
|
|
|
void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) { |
|
|
|
JNIEnv* env = IDCache::GetEnvForThread(); |
|
|
|
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnEmulationStopped(), |
|
|
|
static_cast<jint>(result)); |
|
|
|
} |
|
|
|
|
|
|
|
static Core::SystemResultStatus RunEmulation(const std::string& filepath) { |
|
|
|
Common::Log::Initialize(); |
|
|
|
|