diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index f39c840d4..057741d5f 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -13,10 +13,12 @@ #include "xenia/apu/audio_system.h" #include "xenia/base/assert.h" +#include "xenia/base/byte_stream.h" #include "xenia/base/clock.h" #include "xenia/base/debugging.h" #include "xenia/base/exception_handler.h" #include "xenia/base/logging.h" +#include "xenia/base/mapped_memory.h" #include "xenia/base/profiling.h" #include "xenia/base/string.h" #include "xenia/cpu/backend/code_cache.h" @@ -24,6 +26,7 @@ #include "xenia/hid/input_driver.h" #include "xenia/hid/input_system.h" #include "xenia/kernel/kernel_state.h" +#include "xenia/kernel/user_module.h" #include "xenia/kernel/xam/xam_module.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_module.h" #include "xenia/memory.h" @@ -288,6 +291,112 @@ X_STATUS Emulator::LaunchStfsContainer(std::wstring path) { return CompleteLaunch(path, "game:\\default.xex"); } +void Emulator::Pause() { + auto lock = global_critical_region::AcquireDirect(); + if (paused_) { + return; + } + paused_ = true; + + auto threads = + kernel_state()->object_table()->GetObjectsByType( + kernel::XObject::kTypeThread); + for (auto thread : threads) { + if (!thread->can_debugger_suspend()) { + // Don't pause host threads. + continue; + } + + thread->thread()->Suspend(nullptr); + } + + XELOGD("! EMULATOR PAUSED !"); +} + +void Emulator::Resume() { + if (!paused_) { + return; + } + paused_ = false; + XELOGD("! EMULATOR RESUMED !"); + + auto threads = + kernel_state()->object_table()->GetObjectsByType( + kernel::XObject::kTypeThread); + for (auto thread : threads) { + if (!thread->can_debugger_suspend()) { + // Don't pause host threads. + continue; + } + + thread->thread()->Resume(nullptr); + } +} + +bool Emulator::SaveToFile(const std::wstring& path) { + Pause(); + + filesystem::CreateFile(path); + auto map = MappedMemory::Open(path, MappedMemory::Mode::kReadWrite, 0, + 1024ull * 1024ull * 1024ull * 4ull); + if (!map) { + return false; + } + + // Save the emulator state to a file + ByteStream stream(map->data(), map->size()); + stream.Write('XSAV'); + + kernel_state_->Save(&stream); + memory_->Save(&stream); + map->Close(stream.offset()); + + Resume(); + return true; +} + +bool Emulator::RestoreFromFile(const std::wstring& path) { + // Restore the emulator state from a file + auto map = MappedMemory::Open(path, MappedMemory::Mode::kReadWrite); + if (!map) { + return false; + } + + restoring_ = true; + + // Terminate any loaded titles. + Pause(); + kernel_state_->TerminateTitle(); + + ByteStream stream(map->data(), map->size()); + if (stream.Read() != 'XSAV') { + return false; + } + + if (!kernel_state_->Restore(&stream)) { + return false; + } + if (!memory_->Restore(&stream)) { + return false; + } + + // Update the main thread. + auto threads = kernel_state_->object_table()->GetObjectsByType(); + for (auto thread : threads) { + if (thread->main_thread()) { + main_thread_ = thread->thread(); + break; + } + } + + Resume(); + + restore_fence_.Signal(); + restoring_ = false; + + return true; +} + bool Emulator::ExceptionCallbackThunk(Exception* ex, void* data) { return reinterpret_cast(data)->ExceptionCallback(ex); } @@ -348,19 +457,42 @@ bool Emulator::ExceptionCallback(Exception* ex) { return false; } +void Emulator::WaitUntilExit() { + while (true) { + xe::threading::Wait(main_thread_, false); + + if (restoring_) { + restore_fence_.Wait(); + } else { + // Not restoring and the thread exited. We're finished. + break; + } + } +} + X_STATUS Emulator::CompleteLaunch(const std::wstring& path, const std::string& module_path) { // Allow xam to request module loads. auto xam = kernel_state()->GetKernelModule("xam.xex"); - auto xboxkrnl = - kernel_state()->GetKernelModule( - "xboxkrnl.exe"); int result = 0; auto next_module = module_path; while (next_module != "") { XELOGI("Launching module %s", next_module.c_str()); - result = xboxkrnl->LaunchModule(next_module.c_str()); + auto module = kernel_state_->LoadUserModule(next_module.c_str()); + if (!module) { + XELOGE("Failed to load user module %s", path); + return X_STATUS_NOT_FOUND; + } + + kernel_state_->SetExecutableModule(module); + auto main_xthread = module->Launch(); + if (!main_xthread) { + return X_STATUS_UNSUCCESSFUL; + } + + main_thread_ = main_xthread->thread(); + WaitUntilExit(); // Check xam and see if they want us to load another module. auto& loader_data = xam->loader_data(); diff --git a/src/xenia/emulator.h b/src/xenia/emulator.h index e72395416..709aa088e 100644 --- a/src/xenia/emulator.h +++ b/src/xenia/emulator.h @@ -118,6 +118,14 @@ class Emulator { // Launches a game from an STFS container file. X_STATUS LaunchStfsContainer(std::wstring path); + void Pause(); + void Resume(); + + bool SaveToFile(const std::wstring& path); + bool RestoreFromFile(const std::wstring& path); + + void WaitUntilExit(); + private: static bool ExceptionCallbackThunk(Exception* ex, void* data); bool ExceptionCallback(Exception* ex); @@ -142,6 +150,11 @@ class Emulator { std::unique_ptr file_system_; std::unique_ptr kernel_state_; + threading::Thread* main_thread_ = nullptr; + + bool paused_ = false; + bool restoring_ = false; + threading::Fence restore_fence_; // Fired on restore finish. }; } // namespace xe diff --git a/src/xenia/kernel/user_module.cc b/src/xenia/kernel/user_module.cc index d38dcec0f..74097419f 100644 --- a/src/xenia/kernel/user_module.cc +++ b/src/xenia/kernel/user_module.cc @@ -11,6 +11,7 @@ #include +#include "xenia/base/byte_stream.h" #include "xenia/base/logging.h" #include "xenia/cpu/elf_module.h" #include "xenia/cpu/processor.h" @@ -101,7 +102,8 @@ X_STATUS UserModule::LoadFromMemory(const void* addr, const size_t length) { } // Copy the xex2 header into guest memory. - const xex2_header* header = this->xex_module()->xex_header(); + auto header = this->xex_module()->xex_header(); + auto security_header = this->xex_module()->xex_security_info(); guest_xex_header_ = memory()->SystemHeapAlloc(header->header_size); uint8_t* xex_header_ptr = memory()->TranslateVirtual(guest_xex_header_); @@ -113,6 +115,15 @@ X_STATUS UserModule::LoadFromMemory(const void* addr, const size_t length) { ldr_data->dll_base = 0; // GetProcAddress will read this. ldr_data->xex_header_base = guest_xex_header_; + ldr_data->full_image_size = security_header->image_size; + this->xex_module()->GetOptHeader(XEX_HEADER_ENTRY_POINT, + &ldr_data->entry_point); + + xe::be* image_base_ptr = nullptr; + if (this->xex_module()->GetOptHeader(XEX_HEADER_IMAGE_BASE_ADDRESS, + &image_base_ptr)) { + ldr_data->image_base = *image_base_ptr; + } // Cache some commonly used headers... this->xex_module()->GetOptHeader(XEX_HEADER_ENTRY_POINT, &entry_point_); @@ -171,7 +182,7 @@ X_STATUS UserModule::GetSection(const char* name, uint32_t* out_section_data, if (!cpu::XexModule::GetOptHeader(xex_header(), XEX_HEADER_RESOURCE_INFO, &resource_header)) { // No resources. - return X_STATUS_UNSUCCESSFUL; + return X_STATUS_NOT_FOUND; } uint32_t count = (resource_header->size - 4) / 16; @@ -186,7 +197,7 @@ X_STATUS UserModule::GetSection(const char* name, uint32_t* out_section_data, } } - return X_STATUS_UNSUCCESSFUL; + return X_STATUS_NOT_FOUND; } X_STATUS UserModule::GetOptHeader(xe_xex2_header_keys key, void** out_ptr) { @@ -259,14 +270,14 @@ X_STATUS UserModule::GetOptHeader(uint8_t* membase, const xex2_header* header, return X_STATUS_SUCCESS; } -X_STATUS UserModule::Launch(uint32_t flags) { +object_ref UserModule::Launch(uint32_t flags) { XELOGI("Launching module..."); // Create a thread to run in. // We start suspended so we can run the debugger prep. - auto thread = object_ref(new XThread(kernel_state(), stack_size_, 0, - entry_point_, 0, - X_CREATE_SUSPENDED, true)); + auto thread = object_ref( + new XThread(kernel_state(), stack_size_, 0, entry_point_, 0, + X_CREATE_SUSPENDED, true, true)); // We know this is the 'main thread'. char thread_name[32]; @@ -277,7 +288,7 @@ X_STATUS UserModule::Launch(uint32_t flags) { X_STATUS result = thread->Create(); if (XFAILED(result)) { XELOGE("Could not create launch thread: %.8X", result); - return result; + return nullptr; } // Waits for a debugger client, if desired. @@ -290,8 +301,8 @@ X_STATUS UserModule::Launch(uint32_t flags) { // suspend count without resuming it until the debugger wants. thread->Resume(); - // Wait until thread completes. - thread->Wait(0, 0, 0, nullptr); + return thread; +} return X_STATUS_SUCCESS; } diff --git a/src/xenia/kernel/user_module.h b/src/xenia/kernel/user_module.h index cc4d67f2a..16c5e29c5 100644 --- a/src/xenia/kernel/user_module.h +++ b/src/xenia/kernel/user_module.h @@ -23,6 +23,9 @@ namespace cpu { class XexModule; class ElfModule; } // namespace cpu +namespace kernel { +class XThread; +} // namespace kernel } // namespace xe namespace xe { @@ -80,7 +83,7 @@ class UserModule : public XModule { xe_xex2_header_keys key, uint32_t* out_header_guest_ptr); - X_STATUS Launch(uint32_t flags); + object_ref Launch(uint32_t flags = 0); void Dump();