Based entirely off existing xbyak x86 implementation and available tests. Still needs a lot of optimization and testing on non-Windows platforms. So far passes all tests and boots at least some games on Windows.
367 lines
11 KiB
C++
367 lines
11 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include <cstdlib>
|
|
|
|
#include "xenia/base/console.h"
|
|
#include "xenia/base/cvar.h"
|
|
#include "xenia/base/main_win.h"
|
|
#include "xenia/base/platform_win.h"
|
|
#include "xenia/kernel/kernel_state.cc"
|
|
#include "xenia/ui/windowed_app.h"
|
|
#include "xenia/ui/windowed_app_context_win.h"
|
|
|
|
#include "version.h"
|
|
|
|
#include <Psapi.h>
|
|
|
|
DEFINE_bool(enable_console, false, "Open a console window with the main window",
|
|
"Logging");
|
|
|
|
static uintptr_t g_xenia_exe_base = 0;
|
|
static size_t g_xenia_exe_size = 0;
|
|
|
|
static HMODULE probe_for_module(void* addr) {
|
|
// get 65k aligned addr downwards to probe for MZ
|
|
uintptr_t base = reinterpret_cast<uintptr_t>(addr) & ~0xFFFFULL;
|
|
|
|
constexpr unsigned max_search_iters =
|
|
(64 * (1024 * 1024)) /
|
|
65536; // search down at most 64 mb (we do it in
|
|
// batches of 64k so its pretty quick). i think its reasonable to
|
|
// expect no module will be > 64mb
|
|
// break if access violation thrown, we're definitely not a PE in that case
|
|
__try {
|
|
for (unsigned i = 0; i < max_search_iters; ++i) {
|
|
if (*reinterpret_cast<unsigned short*>(base) == 'ZM') {
|
|
return reinterpret_cast<HMODULE>(base);
|
|
} else {
|
|
base -= 65536;
|
|
}
|
|
}
|
|
return nullptr;
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
static constexpr auto XENIA_ERROR_LANGUAGE =
|
|
MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT);
|
|
|
|
struct HostExceptionReport {
|
|
_EXCEPTION_POINTERS* const ExceptionInfo;
|
|
size_t Report_Scratchpos;
|
|
|
|
const DWORD last_win32_error;
|
|
const NTSTATUS last_ntstatus;
|
|
|
|
const int errno_value;
|
|
char Report_Scratchbuffer[2048];
|
|
|
|
unsigned int address_format_ring_index;
|
|
|
|
char formatted_addresses[16][128];
|
|
|
|
void AddString(const char* s);
|
|
static char* ChompNewlines(char* s);
|
|
|
|
HostExceptionReport(_EXCEPTION_POINTERS* _ExceptionInfo)
|
|
: ExceptionInfo(_ExceptionInfo),
|
|
Report_Scratchpos(0u),
|
|
last_win32_error(GetLastError()),
|
|
#if XE_ARCH_AMD64
|
|
last_ntstatus(__readgsdword(0x1250)),
|
|
#elif XE_ARCH_ARM64
|
|
// TEB.LastStatusValue at offset 0x1250 (same as x64).
|
|
// ARM64 uses x18 register for TEB base instead of GS segment.
|
|
last_ntstatus(__readx18dword(0x1250)),
|
|
#endif
|
|
errno_value(errno),
|
|
address_format_ring_index(0)
|
|
|
|
{
|
|
memset(Report_Scratchbuffer, 0, sizeof(Report_Scratchbuffer));
|
|
}
|
|
|
|
void DisplayExceptionMessage() {
|
|
MessageBoxA(nullptr, Report_Scratchbuffer, "Unhandled Exception in Xenia",
|
|
MB_ICONERROR);
|
|
}
|
|
|
|
const char* GetFormattedAddress(uintptr_t address);
|
|
|
|
const char* GetFormattedAddress(PVOID address) {
|
|
return GetFormattedAddress(reinterpret_cast<uintptr_t>(address));
|
|
}
|
|
};
|
|
char* HostExceptionReport::ChompNewlines(char* s) {
|
|
if (!s) {
|
|
return nullptr;
|
|
}
|
|
unsigned read_pos = 0;
|
|
unsigned write_pos = 0;
|
|
|
|
while (true) {
|
|
char current = s[read_pos++];
|
|
if (current == '\n') {
|
|
continue;
|
|
}
|
|
s[write_pos++] = current;
|
|
if (!current) {
|
|
break;
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
void HostExceptionReport::AddString(const char* s) {
|
|
size_t ln = strlen(s);
|
|
|
|
for (size_t i = 0; i < ln; ++i) {
|
|
Report_Scratchbuffer[i + Report_Scratchpos] = s[i];
|
|
}
|
|
Report_Scratchpos += ln;
|
|
}
|
|
|
|
const char* HostExceptionReport::GetFormattedAddress(uintptr_t address) {
|
|
char (¤t_buffer)[128] =
|
|
formatted_addresses[address_format_ring_index++ % 16];
|
|
|
|
/* if (address >= g_xenia_exe_base &&
|
|
address - g_xenia_exe_base < g_xenia_exe_size) {
|
|
uintptr_t offset = address - g_xenia_exe_base;
|
|
|
|
sprintf_s(current_buffer, "xenia_canary.exe+%llX", offset);
|
|
} else */
|
|
{
|
|
HMODULE hmod_for = probe_for_module((void*)address);
|
|
|
|
if (hmod_for) {
|
|
// get the module filename, then chomp off all but the actual file name
|
|
// (full path is obtained)
|
|
char tmp_module_name[MAX_PATH + 1];
|
|
GetModuleFileNameA(hmod_for, tmp_module_name, sizeof(tmp_module_name));
|
|
|
|
size_t search_back = strlen(tmp_module_name);
|
|
// hunt backwards for the last sep
|
|
while (tmp_module_name[--search_back] != '\\');
|
|
|
|
// MessageBoxA(nullptr, tmp_module_name, "ffds", MB_OK);
|
|
sprintf_s(current_buffer, "%s+%llX", tmp_module_name + search_back + 1,
|
|
address - reinterpret_cast<uintptr_t>(hmod_for));
|
|
|
|
} else {
|
|
sprintf_s(current_buffer, "0x%llX", address);
|
|
}
|
|
}
|
|
return current_buffer;
|
|
}
|
|
using ExceptionInfoCategoryHandler = bool (*)(HostExceptionReport* report);
|
|
static char* Ntstatus_msg(NTSTATUS status) {
|
|
char* statusmsg = nullptr;
|
|
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
GetModuleHandleA("ntdll.dll"), status, XENIA_ERROR_LANGUAGE,
|
|
(LPSTR)&statusmsg, 0, NULL);
|
|
return statusmsg;
|
|
}
|
|
static bool exception_pointers_handler(HostExceptionReport* report) {
|
|
PVOID exception_addr =
|
|
report->ExceptionInfo->ExceptionRecord->ExceptionAddress;
|
|
|
|
#if XE_ARCH_AMD64
|
|
DWORD64 last_stackpointer = report->ExceptionInfo->ContextRecord->Rsp;
|
|
DWORD64 last_rip = report->ExceptionInfo->ContextRecord->Rip;
|
|
#elif XE_ARCH_ARM64
|
|
DWORD64 last_stackpointer = report->ExceptionInfo->ContextRecord->Sp;
|
|
DWORD64 last_rip = report->ExceptionInfo->ContextRecord->Pc;
|
|
#endif
|
|
DWORD except_code = report->ExceptionInfo->ExceptionRecord->ExceptionCode;
|
|
|
|
std::string build = (
|
|
#ifdef XE_BUILD_IS_PR
|
|
"PR#" XE_BUILD_PR_NUMBER " - "
|
|
#endif
|
|
XE_BUILD_BRANCH "@" XE_BUILD_COMMIT_SHORT " on " XE_BUILD_DATE);
|
|
|
|
std::string title_info = "Title not started yet.";
|
|
|
|
if (xe::kernel::kernel_state()) {
|
|
if (xe::kernel::kernel_state()->emulator()->is_title_open()) {
|
|
const uint32_t title_id = xe::kernel::kernel_state()->title_id();
|
|
const std::string title_name =
|
|
xe::kernel::kernel_state()->emulator()->title_name();
|
|
const std::string title_version =
|
|
xe::kernel::kernel_state()->emulator()->title_version();
|
|
|
|
title_info =
|
|
fmt::format("{} ({:08X}) - {}", title_name, title_id, title_version);
|
|
}
|
|
}
|
|
|
|
const std::string except_message = fmt::format(
|
|
"Exception encountered!\nTitle Info: {}\nBuild: {}\nException address: "
|
|
"{}\nStackpointer: {}\nInstruction pointer: {}\nExceptionCode: 0x{} "
|
|
"({})\n",
|
|
title_info, build.c_str(), report->GetFormattedAddress(exception_addr),
|
|
report->GetFormattedAddress(last_stackpointer),
|
|
report->GetFormattedAddress(last_rip), except_code,
|
|
HostExceptionReport::ChompNewlines(Ntstatus_msg(except_code)));
|
|
|
|
report->AddString(except_message.c_str());
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool exception_win32_error_handle(HostExceptionReport* report) {
|
|
if (!report->last_win32_error) {
|
|
return false; // no error, nothing to do
|
|
}
|
|
// todo: formatmessage
|
|
char win32_error_buf[512];
|
|
// its ok if we dont free statusmsg, we're exiting anyway
|
|
char* statusmsg = nullptr;
|
|
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
nullptr, report->last_win32_error, XENIA_ERROR_LANGUAGE,
|
|
(LPSTR)&statusmsg, 0, nullptr);
|
|
sprintf_s(win32_error_buf, "Last Win32 Error: 0x%X (%s)\n",
|
|
report->last_win32_error,
|
|
HostExceptionReport::ChompNewlines(statusmsg));
|
|
report->AddString(win32_error_buf);
|
|
return true;
|
|
}
|
|
static bool exception_ntstatus_error_handle(HostExceptionReport* report) {
|
|
if (!report->last_ntstatus) {
|
|
return false;
|
|
}
|
|
// todo: formatmessage
|
|
char win32_error_buf[512];
|
|
|
|
sprintf_s(win32_error_buf, "Last NTSTATUS: 0x%X (%s)\n",
|
|
report->last_ntstatus, Ntstatus_msg(report->last_ntstatus));
|
|
report->AddString(win32_error_buf);
|
|
return true;
|
|
}
|
|
|
|
static bool exception_cerror_handle(HostExceptionReport* report) {
|
|
if (!report->errno_value) {
|
|
return false;
|
|
}
|
|
char errno_buffer[512];
|
|
sprintf_s(errno_buffer, "Last errno value: 0x%X (%s)\n", report->errno_value,
|
|
strerror(report->errno_value));
|
|
|
|
report->AddString(errno_buffer);
|
|
return true;
|
|
}
|
|
|
|
static bool thread_name_handle(HostExceptionReport* report) {
|
|
// ll GetThreadDescription(HANDLE hThread, PWSTR *ppszThreadDescription)
|
|
|
|
FARPROC description_getter =
|
|
GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetThreadDescription");
|
|
|
|
if (!description_getter) {
|
|
return false;
|
|
}
|
|
PWSTR descr = nullptr;
|
|
|
|
reinterpret_cast<HRESULT (*)(HANDLE, PWSTR*)>(description_getter)(
|
|
GetCurrentThread(), &descr);
|
|
|
|
if (!descr) {
|
|
return false;
|
|
}
|
|
|
|
char result_buffer[512];
|
|
|
|
sprintf_s(result_buffer, "Faulting thread name: %ws\n", descr);
|
|
|
|
report->AddString(result_buffer);
|
|
return true;
|
|
}
|
|
static ExceptionInfoCategoryHandler host_exception_category_handlers[] = {
|
|
exception_pointers_handler, exception_win32_error_handle,
|
|
exception_ntstatus_error_handle, exception_cerror_handle,
|
|
thread_name_handle};
|
|
|
|
LONG _UnhandledExceptionFilter(_EXCEPTION_POINTERS* ExceptionInfo) {
|
|
HostExceptionReport report{ExceptionInfo};
|
|
for (auto&& handler : host_exception_category_handlers) {
|
|
__try {
|
|
if (!handler(&report)) {
|
|
continue;
|
|
}
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
report.AddString("<Nested Exception Encountered>\n");
|
|
}
|
|
}
|
|
report.DisplayExceptionMessage();
|
|
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
int WINAPI wWinMain(HINSTANCE hinstance, HINSTANCE hinstance_prev,
|
|
LPWSTR command_line, int show_cmd) {
|
|
MODULEINFO modinfo;
|
|
|
|
GetModuleInformation(GetCurrentProcess(), (HMODULE)hinstance, &modinfo,
|
|
sizeof(MODULEINFO));
|
|
|
|
g_xenia_exe_base = reinterpret_cast<uintptr_t>(hinstance);
|
|
g_xenia_exe_size = modinfo.SizeOfImage;
|
|
|
|
int result;
|
|
SetUnhandledExceptionFilter(_UnhandledExceptionFilter);
|
|
{
|
|
xe::ui::Win32WindowedAppContext app_context(hinstance, show_cmd);
|
|
// TODO(Triang3l): Initialize creates a window. Set DPI awareness via the
|
|
// manifest.
|
|
if (!app_context.Initialize()) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
std::unique_ptr<xe::ui::WindowedApp> app =
|
|
xe::ui::GetWindowedAppCreator()(app_context);
|
|
|
|
if (!xe::ParseWin32LaunchArguments(false, app->GetPositionalOptionsUsage(),
|
|
app->GetPositionalOptions(), nullptr)) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
// Initialize COM on the UI thread with the apartment-threaded concurrency
|
|
// model, so dialogs can be used.
|
|
if (FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
xe::InitializeWin32App(app->GetName());
|
|
|
|
if (app->OnInitialize()) {
|
|
// TODO(Triang3l): Rework this, need to initialize the console properly,
|
|
// disable has_console_attached_ by default in windowed apps, and attach
|
|
// only if needed.
|
|
if (cvars::enable_console) {
|
|
xe::AttachConsole();
|
|
}
|
|
result = app_context.RunMainMessageLoop();
|
|
} else {
|
|
result = EXIT_FAILURE;
|
|
}
|
|
|
|
app->InvokeOnDestroy();
|
|
}
|
|
|
|
// Logging may still be needed in the destructors.
|
|
xe::ShutdownWin32App();
|
|
|
|
CoUninitialize();
|
|
|
|
return result;
|
|
}
|