xenia-canary/src/xenia/emulator.cc

366 lines
12 KiB
C++
Raw Normal View History

/**
******************************************************************************
* 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"
#include <gflags/gflags.h>
#include "third_party/elemental-forms/src/el/elements.h"
#include "xenia/apu/audio_system.h"
#include "xenia/base/assert.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/profiling.h"
#include "xenia/base/string.h"
#include "xenia/cpu/backend/code_cache.h"
#include "xenia/gpu/graphics_system.h"
#include "xenia/hid/input_system.h"
2015-02-01 06:49:47 +00:00
#include "xenia/kernel/kernel_state.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"
#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"
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 {
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) {}
Emulator::~Emulator() {
// Note that we delete things in the reverse order they were initialized.
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
// Give the systems time to shutdown before we delete them.
graphics_system_->Shutdown();
audio_system_->Shutdown();
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();
processor_.reset();
2015-05-25 03:44:27 +00:00
debugger_.reset();
export_resolver_.reset();
ExceptionHandler::Uninstall(Emulator::ExceptionCallbackThunk, this);
}
X_STATUS Emulator::Setup(ui::Window* display_window) {
X_STATUS result = X_STATUS_UNSUCCESSFUL;
display_window_ = display_window;
// Initialize clock.
// 360 uses a 50MHz clock.
Clock::set_guest_tick_frequency(50000000);
// 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.
xe::threading::EnableAffinityConfiguration();
// Create memory system first, as it is required for other systems.
memory_ = std::make_unique<Memory>();
result = memory_->Initialize();
if (result) {
return result;
}
// Shared export resolver used to attach and query for HLE exports.
export_resolver_ = std::make_unique<xe::cpu::ExportResolver>();
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
// Create debugger first. Other types hook up to it.
debugger_->StartSession();
}
2015-05-24 05:27:43 +00:00
// Initialize the CPU.
2015-08-07 03:17:01 +00:00
processor_ = std::make_unique<xe::cpu::Processor>(
2015-05-24 05:27:43 +00:00
memory_.get(), export_resolver_.get(), debugger_.get());
if (!processor_->Setup()) {
return X_STATUS_UNSUCCESSFUL;
}
// Initialize the APU.
2015-08-18 17:54:56 +00:00
audio_system_ = xe::apu::AudioSystem::Create(processor_.get());
if (!audio_system_) {
return X_STATUS_NOT_IMPLEMENTED;
}
// Initialize the GPU.
graphics_system_ = xe::gpu::GraphicsSystem::Create();
if (!graphics_system_) {
return X_STATUS_NOT_IMPLEMENTED;
}
display_window_->loop()->PostSynchronous([this]() {
display_window_->set_context(
graphics_system_->CreateContext(display_window_));
});
2013-10-24 04:47:36 +00:00
// Initialize the HID.
2015-11-07 19:09:47 +00:00
input_system_ = xe::hid::InputSystem::Create(display_window_);
if (!input_system_) {
return X_STATUS_NOT_IMPLEMENTED;
}
2013-10-24 04:47:36 +00:00
result = input_system_->Setup();
if (result) {
return result;
}
// 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>();
// Shared kernel state.
2015-08-07 03:17:01 +00:00
kernel_state_ = std::make_unique<xe::kernel::KernelState>(this);
// Setup the core components.
result = graphics_system_->Setup(processor_.get(), kernel_state_.get(),
display_window_);
if (result) {
return result;
}
2015-08-18 17:54:56 +00:00
result = audio_system_->Setup(kernel_state_.get());
if (result) {
2015-06-21 07:28:42 +00:00
return result;
}
// HLE kernel modules.
2015-09-07 01:07:52 +00:00
kernel_state_->LoadKernelModule<kernel::xboxkrnl::XboxkrnlModule>();
kernel_state_->LoadKernelModule<kernel::xam::XamModule>();
// Initialize emulator fallback exception handling last.
ExceptionHandler::Install(Emulator::ExceptionCallbackThunk, this);
// Finish initializing the display.
display_window_->loop()->PostSynchronous([this]() {
xe::ui::GraphicsContextLock context_lock(display_window_->context());
Profiler::set_window(display_window_);
});
return result;
}
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);
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") {
// Treat as a naked xex file.
return LaunchXexFile(path);
} else {
// Assume a disc image.
return LaunchDiscImage(path);
}
}
X_STATUS Emulator::LaunchXexFile(std::wstring path) {
// 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
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);
if (!device->Initialize()) {
XELOGE("Unable to scan host path");
return X_STATUS_NO_SUCH_FILE;
}
if (!file_system_->RegisterDevice(std::move(device))) {
XELOGE("Unable to register host path");
return X_STATUS_NO_SUCH_FILE;
}
// Create symlinks to the device.
file_system_->RegisterSymbolicLink("game:", mount_path);
file_system_->RegisterSymbolicLink("d:", mount_path);
// Get just the filename (foo.xex).
auto file_name = xe::find_name_from_path(path);
// Launch the game.
std::string fs_path = "game:\\" + xe::to_string(file_name);
return CompleteLaunch(path, fs_path);
}
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()) {
xe::FatalError("Unable to mount disc image; file not found or corrupt.");
return X_STATUS_NO_SUCH_FILE;
}
if (!file_system_->RegisterDevice(std::move(device))) {
xe::FatalError("Unable to register disc image.");
return X_STATUS_NO_SUCH_FILE;
}
// Create symlinks to the device.
file_system_->RegisterSymbolicLink("game:", mount_path);
file_system_->RegisterSymbolicLink("d:", mount_path);
// Launch the game.
return CompleteLaunch(path, "game:\\default.xex");
}
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);
if (!device->Initialize()) {
xe::FatalError(
"Unable to mount STFS container; file not found or corrupt.");
return X_STATUS_NO_SUCH_FILE;
}
if (!file_system_->RegisterDevice(std::move(device))) {
xe::FatalError("Unable to register STFS container.");
return X_STATUS_NO_SUCH_FILE;
}
file_system_->RegisterSymbolicLink("game:", mount_path);
file_system_->RegisterSymbolicLink("d:", mount_path);
// Launch the game.
return CompleteLaunch(path, "game:\\default.xex");
}
bool Emulator::ExceptionCallbackThunk(Exception* ex, void* data) {
return reinterpret_cast<Emulator*>(data)->ExceptionCallback(ex);
}
bool Emulator::ExceptionCallback(Exception* ex) {
// 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();
if (!debugger() ||
(!debugger()->is_attached() && debugging::IsDebuggerAttached())) {
// If Xenia's debugger isn't attached but another one is, pass it to that
// debugger.
return false;
} 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);
}
if (!(ex->pc() >= code_base && ex->pc() < code_end)) {
// Didn't occur in guest code. Let it pass.
return false;
}
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) {
if (!thread->can_debugger_suspend()) {
// Don't pause host threads.
continue;
}
if (current_thread == thread.get()) {
continue;
}
thread->Suspend(nullptr);
}
// Display a dialog telling the user the guest has crashed.
display_window()->loop()->PostSynchronous([&]() {
auto message_form = new el::MessageForm(display_window()->root_element(),
TBIDC("crash_form"));
message_form->Show("Uh-oh!",
"The guest has crashed.\n\n"
"Xenia has now paused itself.");
});
// Now suspend ourself (we should be a guest thread).
assert_true(current_thread->can_debugger_suspend());
current_thread->Suspend(nullptr);
// We should not arrive here!
assert_always();
return false;
}
X_STATUS Emulator::CompleteLaunch(const std::wstring& path,
const std::string& module_path) {
// 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 22:27:25 +00:00
auto xboxkrnl =
2015-09-07 01:07:52 +00:00
kernel_state()->GetKernelModule<kernel::xboxkrnl::XboxkrnlModule>(
"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());
// 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 = "";
}
if (result == 0) {
return X_STATUS_SUCCESS;
} else {
return X_STATUS_UNSUCCESSFUL;
}
}
2014-08-17 20:13:03 +00:00
} // namespace xe