2013-10-24 03:42:24 +00:00
|
|
|
/**
|
|
|
|
******************************************************************************
|
|
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
|
|
******************************************************************************
|
|
|
|
* Copyright 2013 Ben Vanik. All rights reserved. *
|
|
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
|
|
******************************************************************************
|
|
|
|
*/
|
|
|
|
|
2015-02-01 06:49:47 +00:00
|
|
|
#include "xenia/emulator.h"
|
2013-10-24 03:42:24 +00:00
|
|
|
|
2015-05-27 05:20:46 +00:00
|
|
|
#include <gflags/gflags.h>
|
|
|
|
|
2015-06-27 18:25:45 +00:00
|
|
|
#include "xenia/apu/audio_system.h"
|
2015-05-02 10:42:51 +00:00
|
|
|
#include "xenia/base/assert.h"
|
2015-12-01 23:25:47 +00:00
|
|
|
#include "xenia/base/byte_stream.h"
|
2015-05-27 05:20:46 +00:00
|
|
|
#include "xenia/base/clock.h"
|
2015-09-11 22:09:25 +00:00
|
|
|
#include "xenia/base/debugging.h"
|
|
|
|
#include "xenia/base/exception_handler.h"
|
2015-06-27 23:27:24 +00:00
|
|
|
#include "xenia/base/logging.h"
|
2015-12-01 23:25:47 +00:00
|
|
|
#include "xenia/base/mapped_memory.h"
|
2015-11-07 19:25:53 +00:00
|
|
|
#include "xenia/base/profiling.h"
|
2015-05-02 10:42:51 +00:00
|
|
|
#include "xenia/base/string.h"
|
2015-09-11 22:09:25 +00:00
|
|
|
#include "xenia/cpu/backend/code_cache.h"
|
2015-06-27 16:11:03 +00:00
|
|
|
#include "xenia/gpu/graphics_system.h"
|
2015-11-08 23:02:24 +00:00
|
|
|
#include "xenia/hid/input_driver.h"
|
2015-06-27 16:42:30 +00:00
|
|
|
#include "xenia/hid/input_system.h"
|
2015-02-01 06:49:47 +00:00
|
|
|
#include "xenia/kernel/kernel_state.h"
|
2015-12-01 23:25:47 +00:00
|
|
|
#include "xenia/kernel/user_module.h"
|
2015-09-07 01:07:52 +00:00
|
|
|
#include "xenia/kernel/xam/xam_module.h"
|
|
|
|
#include "xenia/kernel/xboxkrnl/xboxkrnl_module.h"
|
2015-02-01 06:49:47 +00:00
|
|
|
#include "xenia/memory.h"
|
2015-12-27 04:25:24 +00:00
|
|
|
#include "xenia/ui/imgui_dialog.h"
|
2015-06-27 23:27:24 +00:00
|
|
|
#include "xenia/vfs/devices/disc_image_device.h"
|
|
|
|
#include "xenia/vfs/devices/host_path_device.h"
|
|
|
|
#include "xenia/vfs/devices/stfs_container_device.h"
|
2015-09-07 01:07:52 +00:00
|
|
|
#include "xenia/vfs/virtual_file_system.h"
|
2013-10-24 03:42:24 +00:00
|
|
|
|
2015-05-27 05:20:46 +00:00
|
|
|
DEFINE_double(time_scalar, 1.0,
|
|
|
|
"Scalar used to speed or slow time (1x, 2x, 1/2x, etc).");
|
|
|
|
|
2014-08-17 20:13:03 +00:00
|
|
|
namespace xe {
|
2013-10-24 03:42:24 +00:00
|
|
|
|
2014-08-16 23:46:20 +00:00
|
|
|
Emulator::Emulator(const std::wstring& command_line)
|
2014-12-20 21:54:55 +00:00
|
|
|
: command_line_(command_line) {}
|
2013-10-24 03:42:24 +00:00
|
|
|
|
|
|
|
Emulator::~Emulator() {
|
|
|
|
// Note that we delete things in the reverse order they were initialized.
|
|
|
|
|
2015-07-23 00:26:10 +00:00
|
|
|
if (debugger_) {
|
|
|
|
// Kill the debugger first, so that we don't have it messing with things.
|
|
|
|
debugger_->StopSession();
|
|
|
|
}
|
2015-05-24 05:27:43 +00:00
|
|
|
|
2015-05-20 05:20:49 +00:00
|
|
|
// Give the systems time to shutdown before we delete them.
|
2015-12-13 19:22:47 +00:00
|
|
|
if (graphics_system_) {
|
|
|
|
graphics_system_->Shutdown();
|
|
|
|
}
|
|
|
|
if (audio_system_) {
|
|
|
|
audio_system_->Shutdown();
|
|
|
|
}
|
2013-10-24 03:42:24 +00:00
|
|
|
|
2014-08-21 06:26:46 +00:00
|
|
|
input_system_.reset();
|
|
|
|
graphics_system_.reset();
|
|
|
|
audio_system_.reset();
|
2014-01-03 02:58:44 +00:00
|
|
|
|
2015-05-25 03:44:27 +00:00
|
|
|
kernel_state_.reset();
|
|
|
|
file_system_.reset();
|
|
|
|
|
2014-08-21 06:26:46 +00:00
|
|
|
processor_.reset();
|
2013-10-24 03:42:24 +00:00
|
|
|
|
2015-05-25 03:44:27 +00:00
|
|
|
debugger_.reset();
|
|
|
|
|
2014-08-21 06:26:46 +00:00
|
|
|
export_resolver_.reset();
|
2015-09-21 04:31:05 +00:00
|
|
|
|
|
|
|
ExceptionHandler::Uninstall(Emulator::ExceptionCallbackThunk, this);
|
2013-10-24 03:42:24 +00:00
|
|
|
}
|
|
|
|
|
2015-11-08 23:02:24 +00:00
|
|
|
X_STATUS Emulator::Setup(
|
|
|
|
ui::Window* display_window,
|
|
|
|
std::function<std::unique_ptr<apu::AudioSystem>(cpu::Processor*)>
|
|
|
|
audio_system_factory,
|
|
|
|
std::function<std::unique_ptr<gpu::GraphicsSystem>()>
|
|
|
|
graphics_system_factory,
|
|
|
|
std::function<std::vector<std::unique_ptr<hid::InputDriver>>(ui::Window*)>
|
|
|
|
input_driver_factory) {
|
2013-10-24 03:42:24 +00:00
|
|
|
X_STATUS result = X_STATUS_UNSUCCESSFUL;
|
|
|
|
|
2015-07-13 05:03:47 +00:00
|
|
|
display_window_ = display_window;
|
|
|
|
|
2015-05-27 05:20:46 +00:00
|
|
|
// Initialize clock.
|
|
|
|
// 360 uses a 50MHz clock.
|
2015-05-28 02:04:47 +00:00
|
|
|
Clock::set_guest_tick_frequency(50000000);
|
2015-05-27 05:20:46 +00:00
|
|
|
// We could reset this with save state data/constant value to help replays.
|
|
|
|
Clock::set_guest_system_time_base(Clock::QueryHostSystemTime());
|
|
|
|
// This can be adjusted dynamically, as well.
|
|
|
|
Clock::set_guest_time_scalar(FLAGS_time_scalar);
|
|
|
|
|
|
|
|
// Before we can set thread affinity we must enable the process to use all
|
|
|
|
// logical processors.
|
2015-07-16 05:09:19 +00:00
|
|
|
xe::threading::EnableAffinityConfiguration();
|
2015-05-16 06:52:48 +00:00
|
|
|
|
2013-10-24 03:42:24 +00:00
|
|
|
// Create memory system first, as it is required for other systems.
|
2014-08-21 06:26:46 +00:00
|
|
|
memory_ = std::make_unique<Memory>();
|
2015-12-03 01:37:48 +00:00
|
|
|
if (!memory_->Initialize()) {
|
|
|
|
return false;
|
2014-08-21 06:26:46 +00:00
|
|
|
}
|
2013-10-24 03:42:24 +00:00
|
|
|
|
|
|
|
// Shared export resolver used to attach and query for HLE exports.
|
2015-05-02 09:11:11 +00:00
|
|
|
export_resolver_ = std::make_unique<xe::cpu::ExportResolver>();
|
2013-10-24 03:42:24 +00:00
|
|
|
|
2015-07-23 00:26:10 +00:00
|
|
|
if (FLAGS_debug) {
|
|
|
|
// Debugger first, as other parts hook into it.
|
|
|
|
debugger_.reset(new debug::Debugger(this));
|
2015-05-24 05:27:43 +00:00
|
|
|
|
2015-07-23 00:26:10 +00:00
|
|
|
// Create debugger first. Other types hook up to it.
|
|
|
|
debugger_->StartSession();
|
|
|
|
}
|
2015-05-24 05:27:43 +00:00
|
|
|
|
2013-10-24 03:42:24 +00:00
|
|
|
// Initialize the CPU.
|
2015-08-07 03:17:01 +00:00
|
|
|
processor_ = std::make_unique<xe::cpu::Processor>(
|
2015-11-28 04:33:48 +00:00
|
|
|
this, memory_.get(), export_resolver_.get(), debugger_.get());
|
2015-11-04 12:47:38 +00:00
|
|
|
if (!processor_->Setup()) {
|
|
|
|
return X_STATUS_UNSUCCESSFUL;
|
|
|
|
}
|
2013-10-24 03:42:24 +00:00
|
|
|
|
|
|
|
// Initialize the APU.
|
2015-11-08 23:02:24 +00:00
|
|
|
if (audio_system_factory) {
|
|
|
|
audio_system_ = audio_system_factory(processor_.get());
|
|
|
|
if (!audio_system_) {
|
|
|
|
return X_STATUS_NOT_IMPLEMENTED;
|
|
|
|
}
|
2014-08-21 06:26:46 +00:00
|
|
|
}
|
2013-10-24 03:42:24 +00:00
|
|
|
|
|
|
|
// Initialize the GPU.
|
2015-11-08 23:02:24 +00:00
|
|
|
graphics_system_ = graphics_system_factory();
|
2014-08-21 06:26:46 +00:00
|
|
|
if (!graphics_system_) {
|
|
|
|
return X_STATUS_NOT_IMPLEMENTED;
|
|
|
|
}
|
2013-10-24 04:47:36 +00:00
|
|
|
|
|
|
|
// Initialize the HID.
|
2015-11-08 23:02:24 +00:00
|
|
|
input_system_ = std::make_unique<xe::hid::InputSystem>(display_window_);
|
2014-08-21 06:26:46 +00:00
|
|
|
if (!input_system_) {
|
|
|
|
return X_STATUS_NOT_IMPLEMENTED;
|
|
|
|
}
|
2015-11-08 23:02:24 +00:00
|
|
|
if (input_driver_factory) {
|
|
|
|
auto input_drivers = input_driver_factory(display_window_);
|
|
|
|
for (size_t i = 0; i < input_drivers.size(); ++i) {
|
|
|
|
input_system_->AddDriver(std::move(input_drivers[i]));
|
|
|
|
}
|
|
|
|
}
|
2014-01-05 01:12:46 +00:00
|
|
|
|
2013-10-24 04:47:36 +00:00
|
|
|
result = input_system_->Setup();
|
2014-08-21 06:26:46 +00:00
|
|
|
if (result) {
|
|
|
|
return result;
|
|
|
|
}
|
2013-10-24 03:42:24 +00:00
|
|
|
|
|
|
|
// Bring up the virtual filesystem used by the kernel.
|
2015-08-07 03:17:01 +00:00
|
|
|
file_system_ = std::make_unique<xe::vfs::VirtualFileSystem>();
|
2013-10-24 03:42:24 +00:00
|
|
|
|
2014-01-05 01:12:46 +00:00
|
|
|
// Shared kernel state.
|
2015-08-07 03:17:01 +00:00
|
|
|
kernel_state_ = std::make_unique<xe::kernel::KernelState>(this);
|
2015-05-20 05:20:49 +00:00
|
|
|
|
|
|
|
// Setup the core components.
|
2015-11-08 19:54:36 +00:00
|
|
|
result = graphics_system_->Setup(processor_.get(), kernel_state_.get(),
|
2015-07-13 05:03:47 +00:00
|
|
|
display_window_);
|
2015-05-20 05:20:49 +00:00
|
|
|
if (result) {
|
|
|
|
return result;
|
|
|
|
}
|
2014-01-05 01:12:46 +00:00
|
|
|
|
2015-11-08 23:02:24 +00:00
|
|
|
if (audio_system_) {
|
|
|
|
result = audio_system_->Setup(kernel_state_.get());
|
|
|
|
if (result) {
|
|
|
|
return result;
|
|
|
|
}
|
2015-06-21 07:25:24 +00:00
|
|
|
}
|
|
|
|
|
2013-10-24 03:42:24 +00:00
|
|
|
// HLE kernel modules.
|
2015-09-07 01:07:52 +00:00
|
|
|
kernel_state_->LoadKernelModule<kernel::xboxkrnl::XboxkrnlModule>();
|
|
|
|
kernel_state_->LoadKernelModule<kernel::xam::XamModule>();
|
2013-10-24 03:42:24 +00:00
|
|
|
|
2015-09-21 04:31:05 +00:00
|
|
|
// Initialize emulator fallback exception handling last.
|
|
|
|
ExceptionHandler::Install(Emulator::ExceptionCallbackThunk, this);
|
|
|
|
|
2015-07-13 05:03:47 +00:00
|
|
|
// Finish initializing the display.
|
|
|
|
display_window_->loop()->PostSynchronous([this]() {
|
2015-09-07 16:29:07 +00:00
|
|
|
xe::ui::GraphicsContextLock context_lock(display_window_->context());
|
2015-11-06 05:36:05 +00:00
|
|
|
Profiler::set_window(display_window_);
|
2015-07-13 05:03:47 +00:00
|
|
|
});
|
|
|
|
|
2013-10-24 03:42:24 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-06-27 23:27:24 +00:00
|
|
|
X_STATUS Emulator::LaunchPath(std::wstring path) {
|
|
|
|
// Launch based on file type.
|
|
|
|
// This is a silly guess based on file extension.
|
2015-08-06 05:06:20 +00:00
|
|
|
auto last_slash = path.find_last_of(xe::kPathSeparator);
|
2015-06-27 23:27:24 +00:00
|
|
|
auto last_dot = path.find_last_of('.');
|
|
|
|
if (last_dot < last_slash) {
|
|
|
|
last_dot = std::wstring::npos;
|
|
|
|
}
|
|
|
|
if (last_dot == std::wstring::npos) {
|
|
|
|
// Likely an STFS container.
|
|
|
|
return LaunchStfsContainer(path);
|
2015-08-16 07:52:47 +00:00
|
|
|
} else if (path.substr(last_dot) == L".xex" ||
|
|
|
|
path.substr(last_dot) == L".elf") {
|
2015-06-27 23:27:24 +00:00
|
|
|
// Treat as a naked xex file.
|
|
|
|
return LaunchXexFile(path);
|
|
|
|
} else {
|
|
|
|
// Assume a disc image.
|
|
|
|
return LaunchDiscImage(path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
X_STATUS Emulator::LaunchXexFile(std::wstring path) {
|
2013-10-24 03:42:24 +00:00
|
|
|
// We create a virtual filesystem pointing to its directory and symlink
|
|
|
|
// that to the game filesystem.
|
|
|
|
// e.g., /my/files/foo.xex will get a local fs at:
|
|
|
|
// \\Device\\Harddisk0\\Partition1
|
|
|
|
// and then get that symlinked to game:\, so
|
|
|
|
// -> game:\foo.xex
|
|
|
|
|
2015-06-27 23:27:24 +00:00
|
|
|
auto mount_path = "\\Device\\Harddisk0\\Partition0";
|
|
|
|
|
|
|
|
// Register the local directory in the virtual filesystem.
|
|
|
|
auto parent_path = xe::find_base_path(path);
|
|
|
|
auto device =
|
|
|
|
std::make_unique<vfs::HostPathDevice>(mount_path, parent_path, true);
|
2015-06-28 05:37:49 +00:00
|
|
|
if (!device->Initialize()) {
|
|
|
|
XELOGE("Unable to scan host path");
|
|
|
|
return X_STATUS_NO_SUCH_FILE;
|
|
|
|
}
|
2015-06-27 23:27:24 +00:00
|
|
|
if (!file_system_->RegisterDevice(std::move(device))) {
|
|
|
|
XELOGE("Unable to register host path");
|
|
|
|
return X_STATUS_NO_SUCH_FILE;
|
2014-08-15 03:28:44 +00:00
|
|
|
}
|
|
|
|
|
2015-06-27 23:27:24 +00:00
|
|
|
// Create symlinks to the device.
|
|
|
|
file_system_->RegisterSymbolicLink("game:", mount_path);
|
|
|
|
file_system_->RegisterSymbolicLink("d:", mount_path);
|
|
|
|
|
2013-10-24 03:42:24 +00:00
|
|
|
// Get just the filename (foo.xex).
|
2015-06-27 23:27:24 +00:00
|
|
|
auto file_name = xe::find_name_from_path(path);
|
2013-10-24 03:42:24 +00:00
|
|
|
|
2014-08-15 17:19:59 +00:00
|
|
|
// Launch the game.
|
2015-05-02 10:42:51 +00:00
|
|
|
std::string fs_path = "game:\\" + xe::to_string(file_name);
|
2014-08-15 17:19:59 +00:00
|
|
|
return CompleteLaunch(path, fs_path);
|
|
|
|
}
|
2013-10-24 03:42:24 +00:00
|
|
|
|
2015-06-27 23:27:24 +00:00
|
|
|
X_STATUS Emulator::LaunchDiscImage(std::wstring path) {
|
|
|
|
auto mount_path = "\\Device\\Cdrom0";
|
|
|
|
|
|
|
|
// Register the disc image in the virtual filesystem.
|
|
|
|
auto device = std::make_unique<vfs::DiscImageDevice>(mount_path, path);
|
|
|
|
if (!device->Initialize()) {
|
2015-08-30 01:06:30 +00:00
|
|
|
xe::FatalError("Unable to mount disc image; file not found or corrupt.");
|
2015-06-27 23:27:24 +00:00
|
|
|
return X_STATUS_NO_SUCH_FILE;
|
|
|
|
}
|
|
|
|
if (!file_system_->RegisterDevice(std::move(device))) {
|
2015-08-30 01:06:30 +00:00
|
|
|
xe::FatalError("Unable to register disc image.");
|
2015-06-27 23:27:24 +00:00
|
|
|
return X_STATUS_NO_SUCH_FILE;
|
2013-10-24 03:42:24 +00:00
|
|
|
}
|
|
|
|
|
2015-06-27 23:27:24 +00:00
|
|
|
// Create symlinks to the device.
|
|
|
|
file_system_->RegisterSymbolicLink("game:", mount_path);
|
|
|
|
file_system_->RegisterSymbolicLink("d:", mount_path);
|
|
|
|
|
2013-10-24 03:42:24 +00:00
|
|
|
// Launch the game.
|
2014-08-15 17:19:59 +00:00
|
|
|
return CompleteLaunch(path, "game:\\default.xex");
|
2013-10-24 03:42:24 +00:00
|
|
|
}
|
|
|
|
|
2015-06-27 23:27:24 +00:00
|
|
|
X_STATUS Emulator::LaunchStfsContainer(std::wstring path) {
|
|
|
|
auto mount_path = "\\Device\\Cdrom0";
|
|
|
|
|
|
|
|
// Register the container in the virtual filesystem.
|
2015-06-29 17:38:51 +00:00
|
|
|
auto device = std::make_unique<vfs::StfsContainerDevice>(mount_path, path);
|
2015-06-27 23:27:24 +00:00
|
|
|
if (!device->Initialize()) {
|
2015-08-30 01:06:30 +00:00
|
|
|
xe::FatalError(
|
|
|
|
"Unable to mount STFS container; file not found or corrupt.");
|
2015-06-27 23:27:24 +00:00
|
|
|
return X_STATUS_NO_SUCH_FILE;
|
2013-10-24 03:42:24 +00:00
|
|
|
}
|
2015-06-27 23:27:24 +00:00
|
|
|
if (!file_system_->RegisterDevice(std::move(device))) {
|
2015-08-30 01:06:30 +00:00
|
|
|
xe::FatalError("Unable to register STFS container.");
|
2015-06-27 23:27:24 +00:00
|
|
|
return X_STATUS_NO_SUCH_FILE;
|
|
|
|
}
|
|
|
|
|
|
|
|
file_system_->RegisterSymbolicLink("game:", mount_path);
|
|
|
|
file_system_->RegisterSymbolicLink("d:", mount_path);
|
2013-10-24 03:42:24 +00:00
|
|
|
|
|
|
|
// Launch the game.
|
2014-08-15 17:19:59 +00:00
|
|
|
return CompleteLaunch(path, "game:\\default.xex");
|
2013-10-24 03:42:24 +00:00
|
|
|
}
|
2014-01-19 06:23:26 +00:00
|
|
|
|
2015-12-01 23:25:47 +00:00
|
|
|
void Emulator::Pause() {
|
|
|
|
if (paused_) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
paused_ = true;
|
|
|
|
|
2015-12-07 16:55:30 +00:00
|
|
|
// Don't hold the lock on this (so any waits follow through)
|
|
|
|
graphics_system_->Pause();
|
|
|
|
|
|
|
|
auto lock = global_critical_region::AcquireDirect();
|
2015-12-01 23:25:47 +00:00
|
|
|
auto threads =
|
|
|
|
kernel_state()->object_table()->GetObjectsByType<kernel::XThread>(
|
|
|
|
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 !");
|
|
|
|
|
2015-12-07 16:55:30 +00:00
|
|
|
graphics_system_->Resume();
|
|
|
|
|
2015-12-01 23:25:47 +00:00
|
|
|
auto threads =
|
|
|
|
kernel_state()->object_table()->GetObjectsByType<kernel::XThread>(
|
|
|
|
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');
|
|
|
|
|
2015-12-06 23:45:40 +00:00
|
|
|
// It's important we don't hold the global lock here! XThreads need to step
|
|
|
|
// forward (possibly through guarded regions) without worry!
|
2015-12-07 16:55:30 +00:00
|
|
|
graphics_system_->Save(&stream);
|
2015-12-01 23:25:47 +00:00
|
|
|
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();
|
|
|
|
|
2015-12-07 03:02:12 +00:00
|
|
|
auto lock = global_critical_region::AcquireDirect();
|
2015-12-01 23:25:47 +00:00
|
|
|
ByteStream stream(map->data(), map->size());
|
|
|
|
if (stream.Read<uint32_t>() != 'XSAV') {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-12-07 16:55:30 +00:00
|
|
|
if (!graphics_system_->Restore(&stream)) {
|
|
|
|
return false;
|
|
|
|
}
|
2015-12-01 23:25:47 +00:00
|
|
|
if (!kernel_state_->Restore(&stream)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!memory_->Restore(&stream)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the main thread.
|
|
|
|
auto threads = kernel_state_->object_table()->GetObjectsByType<kernel::XThread>();
|
|
|
|
for (auto thread : threads) {
|
|
|
|
if (thread->main_thread()) {
|
|
|
|
main_thread_ = thread->thread();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Resume();
|
|
|
|
|
|
|
|
restore_fence_.Signal();
|
|
|
|
restoring_ = false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-09-21 04:31:05 +00:00
|
|
|
bool Emulator::ExceptionCallbackThunk(Exception* ex, void* data) {
|
|
|
|
return reinterpret_cast<Emulator*>(data)->ExceptionCallback(ex);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Emulator::ExceptionCallback(Exception* ex) {
|
2015-09-11 22:09:25 +00:00
|
|
|
// Check to see if the exception occurred in guest code.
|
|
|
|
auto code_cache = processor()->backend()->code_cache();
|
|
|
|
auto code_base = code_cache->base_address();
|
|
|
|
auto code_end = code_base + code_cache->total_size();
|
|
|
|
|
2015-09-21 04:31:05 +00:00
|
|
|
if (!debugger() ||
|
2015-09-22 07:01:37 +00:00
|
|
|
(!debugger()->is_attached() && debugging::IsDebuggerAttached())) {
|
2015-09-11 22:09:25 +00:00
|
|
|
// If Xenia's debugger isn't attached but another one is, pass it to that
|
|
|
|
// debugger.
|
|
|
|
return false;
|
2015-09-21 04:31:05 +00:00
|
|
|
} else if (debugger() && debugger()->is_attached()) {
|
|
|
|
// Let the debugger handle this exception. It may decide to continue past it
|
|
|
|
// (if it was a stepping breakpoint, etc).
|
|
|
|
return debugger()->OnUnhandledException(ex);
|
2015-09-11 22:09:25 +00:00
|
|
|
}
|
|
|
|
|
2015-09-21 04:31:05 +00:00
|
|
|
if (!(ex->pc() >= code_base && ex->pc() < code_end)) {
|
|
|
|
// Didn't occur in guest code. Let it pass.
|
|
|
|
return false;
|
|
|
|
}
|
2015-09-11 22:09:25 +00:00
|
|
|
|
2015-09-21 04:31:05 +00:00
|
|
|
auto global_lock = global_critical_region::AcquireDirect();
|
|
|
|
|
|
|
|
// Within range. Pause the emulator and eat the exception.
|
|
|
|
auto threads =
|
|
|
|
kernel_state()->object_table()->GetObjectsByType<kernel::XThread>(
|
|
|
|
kernel::XObject::kTypeThread);
|
|
|
|
auto current_thread = kernel::XThread::GetCurrentThread();
|
|
|
|
for (auto thread : threads) {
|
2015-09-22 04:24:26 +00:00
|
|
|
if (!thread->can_debugger_suspend()) {
|
2015-09-21 04:31:05 +00:00
|
|
|
// Don't pause host threads.
|
|
|
|
continue;
|
2015-09-11 22:09:25 +00:00
|
|
|
}
|
2015-09-21 04:31:05 +00:00
|
|
|
if (current_thread == thread.get()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
thread->Suspend(nullptr);
|
|
|
|
}
|
2015-09-11 22:09:25 +00:00
|
|
|
|
2015-09-21 04:31:05 +00:00
|
|
|
// Display a dialog telling the user the guest has crashed.
|
|
|
|
display_window()->loop()->PostSynchronous([&]() {
|
2015-12-27 04:25:24 +00:00
|
|
|
xe::ui::ImGuiDialog::ShowMessageBox(display_window(), "Uh-oh!",
|
|
|
|
"The guest has crashed.\n\n"
|
|
|
|
"Xenia has now paused itself.");
|
2015-09-21 04:31:05 +00:00
|
|
|
});
|
2015-09-11 22:09:25 +00:00
|
|
|
|
2015-09-21 04:31:05 +00:00
|
|
|
// Now suspend ourself (we should be a guest thread).
|
2015-09-22 04:24:26 +00:00
|
|
|
assert_true(current_thread->can_debugger_suspend());
|
2015-09-21 04:31:05 +00:00
|
|
|
current_thread->Suspend(nullptr);
|
2015-09-11 22:09:25 +00:00
|
|
|
|
2015-09-21 04:31:05 +00:00
|
|
|
// We should not arrive here!
|
|
|
|
assert_always();
|
2015-09-11 22:09:25 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-12-01 23:25:47 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-15 17:19:59 +00:00
|
|
|
X_STATUS Emulator::CompleteLaunch(const std::wstring& path,
|
|
|
|
const std::string& module_path) {
|
2015-07-05 20:44:46 +00:00
|
|
|
// Allow xam to request module loads.
|
2015-09-07 01:07:52 +00:00
|
|
|
auto xam = kernel_state()->GetKernelModule<kernel::xam::XamModule>("xam.xex");
|
2015-07-05 20:44:46 +00:00
|
|
|
|
|
|
|
int result = 0;
|
|
|
|
auto next_module = module_path;
|
|
|
|
while (next_module != "") {
|
|
|
|
XELOGI("Launching module %s", next_module.c_str());
|
2015-12-01 23:25:47 +00:00
|
|
|
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();
|
2015-07-05 20:44:46 +00:00
|
|
|
|
|
|
|
// Check xam and see if they want us to load another module.
|
|
|
|
auto& loader_data = xam->loader_data();
|
|
|
|
next_module = loader_data.launch_path;
|
|
|
|
|
|
|
|
// And blank out the launch path to avoid an infinite loop.
|
|
|
|
loader_data.launch_path = "";
|
|
|
|
}
|
|
|
|
|
2015-05-20 05:20:49 +00:00
|
|
|
if (result == 0) {
|
|
|
|
return X_STATUS_SUCCESS;
|
|
|
|
} else {
|
|
|
|
return X_STATUS_UNSUCCESSFUL;
|
|
|
|
}
|
2014-01-19 06:23:26 +00:00
|
|
|
}
|
2014-08-17 20:13:03 +00:00
|
|
|
|
|
|
|
} // namespace xe
|