diff --git a/premake5.lua b/premake5.lua index 3431b60da..c559c7361 100644 --- a/premake5.lua +++ b/premake5.lua @@ -190,6 +190,7 @@ solution("xenia") include("src/xenia/ui/gl") include("src/xenia/ui/spirv") include("src/xenia/vfs") + include("src/xenia/xdbf") if os.is("windows") then include("src/xenia/apu/xaudio2") diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index b5e077aaf..123cbe86e 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -288,11 +288,16 @@ void EmulatorWindow::ShowHelpWebsite() { LaunchBrowser("http://xenia.jp"); } void EmulatorWindow::UpdateTitle() { std::wstring title(base_title_); - if (Clock::guest_time_scalar() != 1.0) { - title += L" (@"; - title += xe::to_wstring(std::to_string(Clock::guest_time_scalar())); - title += L"x)"; + + const std::wstring &game_title(emulator()->game_title()); + if (!game_title.empty()) { + title = game_title + L" - " + title; } + + if (Clock::guest_time_scalar() != 1.0) { + title += xe::format_string(L" (@%.2fx)", Clock::guest_time_scalar()); + } + window_->set_title(title); } diff --git a/src/xenia/app/emulator_window.h b/src/xenia/app/emulator_window.h index 54ddae867..fa8db88b3 100644 --- a/src/xenia/app/emulator_window.h +++ b/src/xenia/app/emulator_window.h @@ -35,11 +35,12 @@ class EmulatorWindow { ui::Loop* loop() const { return loop_.get(); } ui::Window* window() const { return window_.get(); } + void UpdateTitle(); + private: explicit EmulatorWindow(Emulator* emulator); bool Initialize(); - void UpdateTitle(); void CpuTimeScalarReset(); void CpuTimeScalarSetHalf(); diff --git a/src/xenia/app/premake5.lua b/src/xenia/app/premake5.lua index d72059e22..245e90d69 100644 --- a/src/xenia/app/premake5.lua +++ b/src/xenia/app/premake5.lua @@ -25,6 +25,7 @@ project("xenia-app") "xenia-ui", "xenia-ui-gl", "xenia-vfs", + "xenia-xdbf", }) flags({ "WinMain", -- Use WinMain instead of main. diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc index 865d28cf3..ab4934666 100644 --- a/src/xenia/app/xenia_main.cc +++ b/src/xenia/app/xenia_main.cc @@ -195,7 +195,8 @@ int xenia_main(const std::vector& args) { // Normalize the path and make absolute. std::wstring abs_path = xe::to_absolute_path(path); - result = emulator->LaunchPath(abs_path); + result = emulator->LaunchPath(abs_path, + [&]() { emulator_window->UpdateTitle(); }); if (XFAILED(result)) { XELOGE("Failed to launch target: %.8X", result); emulator.reset(); diff --git a/src/xenia/cpu/xex_module.cc b/src/xenia/cpu/xex_module.cc index 2753fc698..ea54bd4fc 100644 --- a/src/xenia/cpu/xex_module.cc +++ b/src/xenia/cpu/xex_module.cc @@ -470,8 +470,8 @@ bool XexModule::SetupLibraryImports(const char* name, GuestFunction::ExternHandler handler = nullptr; if (kernel_export) { if (kernel_export->function_data.trampoline) { - handler = (GuestFunction::ExternHandler)kernel_export - ->function_data.trampoline; + handler = (GuestFunction::ExternHandler) + kernel_export->function_data.trampoline; } else { handler = (GuestFunction::ExternHandler)kernel_export->function_data.shim; diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index ccfec481e..f2182f62f 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -35,6 +35,7 @@ #include "xenia/vfs/devices/host_path_device.h" #include "xenia/vfs/devices/stfs_container_device.h" #include "xenia/vfs/virtual_file_system.h" +#include "xenia/xdbf/xdbf_utils.h" DEFINE_double(time_scalar, 1.0, "Scalar used to speed or slow time (1x, 2x, 1/2x, etc)."); @@ -191,7 +192,8 @@ X_STATUS Emulator::Setup( return result; } -X_STATUS Emulator::LaunchPath(std::wstring path) { +X_STATUS Emulator::LaunchPath(std::wstring path, + std::function on_launch) { // Launch based on file type. // This is a silly guess based on file extension. auto last_slash = path.find_last_of(xe::kPathSeparator); @@ -201,18 +203,19 @@ X_STATUS Emulator::LaunchPath(std::wstring path) { } if (last_dot == std::wstring::npos) { // Likely an STFS container. - return LaunchStfsContainer(path); + return LaunchStfsContainer(path, on_launch); } else if (path.substr(last_dot) == L".xex" || path.substr(last_dot) == L".elf") { // Treat as a naked xex file. - return LaunchXexFile(path); + return LaunchXexFile(path, on_launch); } else { // Assume a disc image. - return LaunchDiscImage(path); + return LaunchDiscImage(path, on_launch); } } -X_STATUS Emulator::LaunchXexFile(std::wstring path) { +X_STATUS Emulator::LaunchXexFile(std::wstring path, + std::function on_launch) { // 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: @@ -244,10 +247,11 @@ X_STATUS Emulator::LaunchXexFile(std::wstring path) { // Launch the game. std::string fs_path = "game:\\" + xe::to_string(file_name); - return CompleteLaunch(path, fs_path); + return CompleteLaunch(path, fs_path, on_launch); } -X_STATUS Emulator::LaunchDiscImage(std::wstring path) { +X_STATUS Emulator::LaunchDiscImage(std::wstring path, + std::function on_launch) { auto mount_path = "\\Device\\Cdrom0"; // Register the disc image in the virtual filesystem. @@ -266,10 +270,11 @@ X_STATUS Emulator::LaunchDiscImage(std::wstring path) { file_system_->RegisterSymbolicLink("d:", mount_path); // Launch the game. - return CompleteLaunch(path, "game:\\default.xex"); + return CompleteLaunch(path, "game:\\default.xex", on_launch); } -X_STATUS Emulator::LaunchStfsContainer(std::wstring path) { +X_STATUS Emulator::LaunchStfsContainer(std::wstring path, + std::function on_launch) { auto mount_path = "\\Device\\Cdrom0"; // Register the container in the virtual filesystem. @@ -288,7 +293,7 @@ X_STATUS Emulator::LaunchStfsContainer(std::wstring path) { file_system_->RegisterSymbolicLink("d:", mount_path); // Launch the game. - return CompleteLaunch(path, "game:\\default.xex"); + return CompleteLaunch(path, "game:\\default.xex", on_launch); } void Emulator::Pause() { @@ -493,12 +498,13 @@ void Emulator::WaitUntilExit() { } } -X_STATUS Emulator::CompleteLaunch(const std::wstring& path, - const std::string& module_path) { +X_STATUS Emulator::CompleteLaunch(const std::wstring &path, + const std::string &module_path, + std::function on_launch) { // Allow xam to request module loads. auto xam = kernel_state()->GetKernelModule("xam.xex"); - int result = 0; + X_STATUS result = X_STATUS_SUCCESS; auto next_module = module_path; while (next_module != "") { XELOGI("Launching module %s", next_module.c_str()); @@ -509,11 +515,34 @@ X_STATUS Emulator::CompleteLaunch(const std::wstring& path, } kernel_state_->SetExecutableModule(module); + + // Try and load the resource database (xex only) + char title[9] = {0}; + sprintf(title, "%08X", module->title_id()); + + uint32_t resource_data = 0; + uint32_t resource_size = 0; + if (XSUCCEEDED(module->GetSection(title, &resource_data, &resource_size))) { + auto xdb_ptr = module->memory()->TranslateVirtual(resource_data); + if (xdb_ptr != nullptr) { + xe::xdbf::XdbfWrapper db; + if (db.initialize(xdb_ptr, static_cast(resource_size))) { + game_title_ = xe::to_wstring(xe::xdbf::get_title(db)); + xe::xdbf::XdbfBlock icon_block = xe::xdbf::get_icon(db); + if (icon_block.buffer != nullptr) { + display_window_->SetIconFromBuffer(icon_block.buffer, + icon_block.size); + } + } + } + } + auto main_xthread = module->Launch(); if (!main_xthread) { return X_STATUS_UNSUCCESSFUL; } + on_launch(); main_thread_ = main_xthread->thread(); WaitUntilExit(); diff --git a/src/xenia/emulator.h b/src/xenia/emulator.h index 00e93cfff..23a71561f 100644 --- a/src/xenia/emulator.h +++ b/src/xenia/emulator.h @@ -53,6 +53,9 @@ class Emulator { // Full command line used when launching the process. const std::wstring& command_line() const { return command_line_; } + // Title of the game in the default language. + const std::wstring& game_title() const { return game_title_; } + // Window used for displaying graphical output. ui::Window* display_window() const { return display_window_; } @@ -106,17 +109,18 @@ class Emulator { // Launches a game from the given file path. // This will attempt to infer the type of the given file (such as an iso, etc) // using heuristics. - X_STATUS LaunchPath(std::wstring path); + X_STATUS LaunchPath(std::wstring path, std::function on_launch); // Launches a game from a .xex file by mounting the containing folder as if it // was an extracted STFS container. - X_STATUS LaunchXexFile(std::wstring path); + X_STATUS LaunchXexFile(std::wstring path, std::function on_launch); // Launches a game from a disc image file (.iso, etc). - X_STATUS LaunchDiscImage(std::wstring path); + X_STATUS LaunchDiscImage(std::wstring path, std::function on_launch); // Launches a game from an STFS container file. - X_STATUS LaunchStfsContainer(std::wstring path); + X_STATUS LaunchStfsContainer(std::wstring path, + std::function on_launch); void Pause(); void Resume(); @@ -131,10 +135,12 @@ class Emulator { static bool ExceptionCallbackThunk(Exception* ex, void* data); bool ExceptionCallback(Exception* ex); - X_STATUS CompleteLaunch(const std::wstring& path, - const std::string& module_path); + X_STATUS CompleteLaunch(const std::wstring &path, + const std::string &module_path, + std::function on_launch); std::wstring command_line_; + std::wstring game_title_; ui::Window* display_window_; diff --git a/src/xenia/kernel/kernel_module.cc b/src/xenia/kernel/kernel_module.cc index befa08d33..fdad0256f 100644 --- a/src/xenia/kernel/kernel_module.cc +++ b/src/xenia/kernel/kernel_module.cc @@ -105,8 +105,8 @@ uint32_t KernelModule::GetProcAddressByOrdinal(uint16_t ordinal) { cpu::GuestFunction::ExternHandler handler = nullptr; if (export_entry->function_data.trampoline) { - handler = (cpu::GuestFunction::ExternHandler)export_entry - ->function_data.trampoline; + handler = (cpu::GuestFunction::ExternHandler) + export_entry->function_data.trampoline; } else { handler = (cpu::GuestFunction::ExternHandler)export_entry->function_data.shim; diff --git a/src/xenia/kernel/user_module.cc b/src/xenia/kernel/user_module.cc index 36e11470f..31947b38e 100644 --- a/src/xenia/kernel/user_module.cc +++ b/src/xenia/kernel/user_module.cc @@ -208,7 +208,7 @@ X_STATUS UserModule::GetSection(const char* name, uint32_t* out_section_data, return X_STATUS_NOT_FOUND; } - uint32_t count = (resource_header->size - 4) / 16; + uint32_t count = (resource_header->size - 4) / sizeof(xex2_resource); for (uint32_t i = 0; i < count; i++) { auto& res = resource_header->resources[i]; if (std::strncmp(name, res.name, 8) == 0) { diff --git a/src/xenia/ui/window.h b/src/xenia/ui/window.h index cd68db447..d894d9276 100644 --- a/src/xenia/ui/window.h +++ b/src/xenia/ui/window.h @@ -53,6 +53,8 @@ class Window { return true; } + virtual bool SetIconFromBuffer(void* buffer, size_t size) = 0; + virtual bool is_fullscreen() const { return false; } virtual void ToggleFullscreen(bool fullscreen) {} diff --git a/src/xenia/ui/window_win.cc b/src/xenia/ui/window_win.cc index cefaf908d..d3c6224b7 100644 --- a/src/xenia/ui/window_win.cc +++ b/src/xenia/ui/window_win.cc @@ -31,6 +31,10 @@ Win32Window::~Win32Window() { CloseWindow(hwnd_); hwnd_ = nullptr; } + if (icon_ != nullptr) { + DestroyIcon(icon_); + icon_ = nullptr; + } } NativePlatformHandle Win32Window::native_platform_handle() const { @@ -163,6 +167,25 @@ bool Win32Window::set_title(const std::wstring& title) { return true; } +bool Win32Window::SetIconFromBuffer(void* buffer, size_t size) { + if (icon_ != nullptr) { + DestroyIcon(icon_); + } + + HICON icon = CreateIconFromResourceEx(reinterpret_cast(buffer), + static_cast(size), TRUE, + 0x00030000, 0, 0, LR_DEFAULTCOLOR); + + if (icon != nullptr) { + icon_ = icon; + + SendMessage(hwnd_, WM_SETICON, ICON_BIG, reinterpret_cast(icon)); + SendMessage(hwnd_, WM_SETICON, ICON_SMALL, reinterpret_cast(icon)); + } + + return false; +} + bool Win32Window::is_fullscreen() const { return fullscreen_; } void Win32Window::ToggleFullscreen(bool fullscreen) { diff --git a/src/xenia/ui/window_win.h b/src/xenia/ui/window_win.h index 0f34f6e28..296baba45 100644 --- a/src/xenia/ui/window_win.h +++ b/src/xenia/ui/window_win.h @@ -31,6 +31,7 @@ class Win32Window : public Window { HWND hwnd() const { return hwnd_; } bool set_title(const std::wstring& title) override; + bool SetIconFromBuffer(void* buffer, size_t size) override; bool is_fullscreen() const override; void ToggleFullscreen(bool fullscreen) override; @@ -68,6 +69,7 @@ class Win32Window : public Window { bool HandleKeyboard(UINT message, WPARAM wParam, LPARAM lParam); HWND hwnd_ = nullptr; + HICON icon_ = nullptr; bool closing_ = false; bool fullscreen_ = false; diff --git a/src/xenia/xdbf/premake5.lua b/src/xenia/xdbf/premake5.lua new file mode 100644 index 000000000..44e9e6a64 --- /dev/null +++ b/src/xenia/xdbf/premake5.lua @@ -0,0 +1,17 @@ +project_root = "../../.." +include(project_root.."/tools/build") + +group("src") +project("xenia-xdbf") + uuid("a95b5fce-1083-4bff-a022-ffdd0bab3db0") + kind("StaticLib") + language("C++") + links({ + "xenia-base", + }) + defines({ + }) + includedirs({ + project_root.."third_party/gflags/src", + }) + recursive_platform_files() diff --git a/src/xenia/xdbf/xdbf_utils.cc b/src/xenia/xdbf/xdbf_utils.cc new file mode 100644 index 000000000..e60a88d80 --- /dev/null +++ b/src/xenia/xdbf/xdbf_utils.cc @@ -0,0 +1,142 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2016 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/xdbf/xdbf_utils.h" + +namespace xe { +namespace xdbf { + +enum XdbfId : uint64_t { + kIdTitle = 0x8000, + kIdXSTC = 0x58535443, +}; + +enum XdbgMagic : uint32_t { + kMagicXSTC = 'XSTC', + kMagicXSTR = 'XSTR', +}; + +XdbfWrapper::XdbfWrapper() = default; + +XBDF_HEADER& XdbfWrapper::get_header() const { return *state_.header; } + +XBDF_ENTRY& XdbfWrapper::get_entry(uint32_t n) const { + return state_.entries[n]; +} + +XBDF_FILE_LOC& XdbfWrapper::get_file(uint32_t n) const { + return state_.files[n]; +} + +bool XdbfWrapper::initialize(uint8_t* buffer, size_t length) { + if (length <= sizeof(XBDF_HEADER)) { + return false; + } + + XdbfState state; + + state.data = buffer; + state.size = length; + + uint8_t* ptr = state.data; + + state.header = reinterpret_cast(ptr); + ptr += sizeof(XBDF_HEADER); + + state.entries = reinterpret_cast(ptr); + ptr += (sizeof(XBDF_ENTRY) * state.header->entry_count); + + state.files = reinterpret_cast(ptr); + ptr += (sizeof(XBDF_FILE_LOC) * state.header->free_count); + + state.offset = ptr; + + if (state.header->magic == 'XDBF') { + state_ = state; + return true; + } + + return false; +} + +XdbfBlock XdbfWrapper::get_entry(XdbfSection section, uint64_t id) const { + XdbfBlock block = {nullptr, 0}; + uint32_t x = 0; + + while (x < get_header().entry_used) { + auto& entry = get_entry(x); + + if (entry.section == section && entry.id == id) { + block.buffer = state_.offset + entry.offset; + block.size = entry.size; + break; + } + + ++x; + } + + return block; +} + +XdbfBlock get_icon(const XdbfWrapper& ref) { + return ref.get_entry(kSectionImage, kIdTitle); +} + +XdbfLocale get_default_language(const XdbfWrapper& ref) { + XdbfBlock block = ref.get_entry(kSectionMetadata, kIdXSTC); + if (block.buffer != nullptr) { + XDBF_XSTC* xstc = reinterpret_cast(block.buffer); + assert_true(xstc->magic == kMagicXSTC); + + uint32_t default_language = xstc->default_language; + return static_cast(default_language); + } + + return kLocaleEnglish; +} + +std::string get_title(const XdbfWrapper& ref) { + std::string title_str; + + uint64_t language_id = static_cast(get_default_language(ref)); + + XdbfBlock lang_block = ref.get_entry(kSectionStringTable, language_id); + + if (lang_block.buffer != nullptr) { + XDBF_XSTR_HEADER* xstr_head = + reinterpret_cast(lang_block.buffer); + + assert_true(xstr_head->magic == kMagicXSTR); + assert_true(xstr_head->version == 1); + + uint16_t str_count = xstr_head->string_count; + uint8_t* currentAddress = lang_block.buffer + sizeof(XDBF_XSTR_HEADER); + + uint16_t s = 0; + while (s < str_count && title_str.empty()) { + XDBF_STRINGTABLE_ENTRY* entry = + reinterpret_cast(currentAddress); + currentAddress += sizeof(XDBF_STRINGTABLE_ENTRY); + uint16_t len = entry->string_length; + + if (entry->id == static_cast(kIdTitle)) { + title_str.resize(static_cast(len)); + std::copy(currentAddress, currentAddress + len, title_str.begin()); + } + + ++s; + currentAddress += len; + } + } + + return title_str; +} + +} // namespace xdbf +} // namespace xe diff --git a/src/xenia/xdbf/xdbf_utils.h b/src/xenia/xdbf/xdbf_utils.h new file mode 100644 index 000000000..4431eeda1 --- /dev/null +++ b/src/xenia/xdbf/xdbf_utils.h @@ -0,0 +1,129 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2016 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_XDBF_XDBF_UTILS_H_ +#define XENIA_XDBF_XDBF_UTILS_H_ + +#include +#include + +#include "xenia/base/memory.h" + +namespace xe { +namespace xdbf { + +// http://freestyledash.googlecode.com/svn/trunk/Freestyle/Tools/XEX/SPA.h +// http://freestyledash.googlecode.com/svn/trunk/Freestyle/Tools/XEX/SPA.cpp + +enum XdbfSection : uint16_t { + kSectionMetadata = 0x0001, + kSectionImage = 0x0002, + kSectionStringTable = 0x0003, +}; + +// Found by dumping the kSectionStringTable sections of various games: + +enum XdbfLocale : uint32_t { + kLocaleEnglish = 1, + kLocaleJapanese = 2, + kLocaleGerman = 3, + kLocaleFrench = 4, + kLocaleSpanish = 5, + kLocaleItalian = 6, + kLocaleKorean = 7, + kLocaleChinese = 8, +}; + +struct XBDF_HEADER { + xe::be magic; + xe::be version; + xe::be entry_count; + xe::be entry_used; + xe::be free_count; + xe::be free_used; +}; +static_assert_size(XBDF_HEADER, 24); + +#pragma pack(push, 1) +struct XBDF_ENTRY { + xe::be section; + xe::be id; + xe::be offset; + xe::be size; +}; +static_assert_size(XBDF_ENTRY, 18); +#pragma pack(pop) + +struct XBDF_FILE_LOC { + xe::be offset; + xe::be size; +}; +static_assert_size(XBDF_FILE_LOC, 8); + +struct XDBF_XSTC { + xe::be magic; + xe::be version; + xe::be size; + xe::be default_language; +}; +static_assert_size(XDBF_XSTC, 16); + +#pragma pack(push, 1) +struct XDBF_XSTR_HEADER { + xe::be magic; + xe::be version; + xe::be size; + xe::be string_count; +}; +static_assert_size(XDBF_XSTR_HEADER, 14); +#pragma pack(pop) + +struct XDBF_STRINGTABLE_ENTRY { + xe::be id; + xe::be string_length; +}; +static_assert_size(XDBF_STRINGTABLE_ENTRY, 4); + +struct XdbfState { + XBDF_HEADER* header; + XBDF_ENTRY* entries; + XBDF_FILE_LOC* files; + uint8_t* offset; + uint8_t* data; + size_t size; +}; + +struct XdbfBlock { + uint8_t* buffer; + size_t size; +}; + +class XdbfWrapper { + public: + XdbfWrapper(); + + bool initialize(uint8_t* buffer, size_t length); + XdbfBlock get_entry(XdbfSection section, uint64_t id) const; + + private: + XBDF_HEADER& get_header() const; + XBDF_ENTRY& get_entry(uint32_t n) const; + XBDF_FILE_LOC& get_file(uint32_t n) const; + + XdbfState state_; +}; + +XdbfBlock get_icon(const XdbfWrapper& ref); +XdbfLocale get_default_language(const XdbfWrapper& ref); +std::string get_title(const XdbfWrapper& ref); + +} // namespace xdbf +} // namespace xe + +#endif // XENIA_XDBF_XDBF_UTILS_H_