diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index a40033203e..8bb4132603 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -34,6 +34,7 @@ #include "xenia/gpu/d3d12/d3d12_command_processor.h" #include "xenia/gpu/graphics_system.h" #include "xenia/hid/input_system.h" +#include "xenia/kernel/xam/xam_module.h" #include "xenia/ui/file_picker.h" #include "xenia/ui/graphics_provider.h" #include "xenia/ui/imgui_dialog.h" @@ -1451,7 +1452,7 @@ void EmulatorWindow::ToggleGPUSetting(gpu_cvar value) { switch (value) { case gpu_cvar::ClearMemoryPageState: CommonSaveGPUSetting(CommonGPUSetting::ClearMemoryPageState, - !cvars::clear_memory_page_state); + !cvars::clear_memory_page_state); break; case gpu_cvar::ReadbackResolve: D3D12SaveGPUSetting(D3D12GPUSetting::ReadbackResolve, @@ -1575,6 +1576,12 @@ xe::X_STATUS EmulatorWindow::RunTitle(std::filesystem::path path_to_file) { "Failed to launch title.\n\nCheck xenia.log for technical details."); } else { AddRecentlyLaunchedTitle(path_to_file, emulator_->title_name()); + + auto xam = + emulator_->kernel_state()->GetKernelModule( + "xam.xex"); + + xam->loader_data().host_path = xe::path_to_utf8(abs_path); } return result; diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc index 8180085d85..a223bac7c9 100644 --- a/src/xenia/app/xenia_main.cc +++ b/src/xenia/app/xenia_main.cc @@ -27,6 +27,7 @@ #include "xenia/config.h" #include "xenia/debug/ui/debug_window.h" #include "xenia/emulator.h" +#include "xenia/kernel/xam/xam_module.h" #include "xenia/ui/file_picker.h" #include "xenia/ui/window.h" #include "xenia/ui/window_listener.h" @@ -624,6 +625,20 @@ void EmulatorApp::EmulatorThread() { } } + auto xam = emulator_->kernel_state()->GetKernelModule( + "xam.xex"); + + if (xam) { + xam->LoadLoaderData(); + + if (xam->loader_data().launch_data_present) { + const std::filesystem::path host_path = xam->loader_data().host_path; + app_context().CallInUIThread([this, host_path]() { + return emulator_window_->RunTitle(host_path); + }); + } + } + // Now, we're going to use this thread to drive events related to emulation. while (!emulator_thread_quit_requested_.load(std::memory_order_relaxed)) { xe::threading::Wait(emulator_thread_event_.get(), false); diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index b878244cf8..83015c9453 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -53,8 +53,8 @@ #include "xenia/vfs/devices/disc_zarchive_device.h" #include "xenia/vfs/devices/host_path_device.h" #include "xenia/vfs/devices/null_device.h" -#include "xenia/vfs/virtual_file_system.h" #include "xenia/vfs/devices/xcontent_container_device.h" +#include "xenia/vfs/virtual_file_system.h" #if XE_ARCH_AMD64 #include "xenia/cpu/backend/x64/x64_backend.h" @@ -294,7 +294,6 @@ X_STATUS Emulator::Setup( } } - // Initialize emulator fallback exception handling last. ExceptionHandler::Install(Emulator::ExceptionCallbackThunk, this); @@ -331,12 +330,12 @@ const std::unique_ptr Emulator::CreateVfsDeviceBasedOnPath( mount_path, parent_path, !cvars::allow_game_relative_writes); } else if (extension == ".zar") { return std::make_unique(mount_path, path); - } - else if (extension == ".7z" || extension == ".zip" || extension == ".rar" || + } else if (extension == ".7z" || extension == ".zip" || extension == ".rar" || extension == ".tar" || extension == ".gz") { xe::ShowSimpleMessageBox( xe::SimpleMessageBoxType::Error, - fmt::format("Unsupported format!" + fmt::format( + "Unsupported format!" "Xenia does not support running software in an archived format.")); } return std::make_unique(mount_path, path); @@ -399,13 +398,13 @@ X_STATUS Emulator::MountPath(const std::filesystem::path& path, return X_STATUS_NO_SUCH_FILE; } - file_system_->UnregisterSymbolicLink("d:"); - file_system_->UnregisterSymbolicLink("game:"); + file_system_->UnregisterSymbolicLink(kDefaultPartitonSymbolicLink); + file_system_->UnregisterSymbolicLink(kDefaultGameSymbolicLink); file_system_->UnregisterSymbolicLink("plugins:"); // Create symlinks to the device. - file_system_->RegisterSymbolicLink("game:", mount_path); - file_system_->RegisterSymbolicLink("d:", mount_path); + file_system_->RegisterSymbolicLink(kDefaultGameSymbolicLink, mount_path); + file_system_->RegisterSymbolicLink(kDefaultPartitonSymbolicLink, mount_path); return X_STATUS_SUCCESS; } @@ -763,9 +762,12 @@ bool Emulator::ExceptionCallback(Exception* ex) { std::string crash_msg; crash_msg.append("==== CRASH DUMP ====\n"); crash_msg.append(fmt::format("Thread ID (Host: 0x{:08X} / Guest: 0x{:08X})\n", - current_thread->thread()->system_id(), current_thread->thread_id())); - crash_msg.append(fmt::format("Thread Handle: 0x{:08X}\n", current_thread->handle())); - crash_msg.append(fmt::format("PC: 0x{:08X}\n", + current_thread->thread()->system_id(), + current_thread->thread_id())); + crash_msg.append( + fmt::format("Thread Handle: 0x{:08X}\n", current_thread->handle())); + crash_msg.append( + fmt::format("PC: 0x{:08X}\n", guest_function->MapMachineCodeToGuestAddress(ex->pc()))); crash_msg.append("Registers:\n"); for (int i = 0; i < 32; i++) { @@ -860,6 +862,32 @@ void Emulator::RemoveGameConfigLoadCallback(GameConfigLoadCallback* callback) { std::string Emulator::FindLaunchModule() { std::string path("game:\\"); + auto xam = kernel_state()->GetKernelModule("xam.xex"); + + if (!xam->loader_data().launch_path.empty()) { + std::string symbolic_link_path; + if (kernel_state_->file_system()->FindSymbolicLink(kDefaultGameSymbolicLink, + symbolic_link_path)) { + std::filesystem::path file_path = symbolic_link_path; + // Remove previous symbolic links. + // Some titles can provide root within specific directory. + kernel_state_->file_system()->UnregisterSymbolicLink( + kDefaultPartitonSymbolicLink); + kernel_state_->file_system()->UnregisterSymbolicLink( + kDefaultGameSymbolicLink); + + file_path /= std::filesystem::path(xam->loader_data().launch_path); + + kernel_state_->file_system()->RegisterSymbolicLink( + kDefaultPartitonSymbolicLink, + xe::path_to_utf8(file_path.parent_path())); + kernel_state_->file_system()->RegisterSymbolicLink( + kDefaultGameSymbolicLink, xe::path_to_utf8(file_path.parent_path())); + + return xe::path_to_utf8(file_path); + } + } + if (!cvars::launch_module.empty()) { return path + cvars::launch_module; } @@ -986,25 +1014,6 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path, } } - if (xam) { - const std::filesystem::path launch_data_dir = "launch_data"; - const std::filesystem::path file_path = - launch_data_dir / - fmt::format("{:08X}_launch_data.bin", title_id_.value()); - - auto file = xe::filesystem::OpenFile(file_path, "rb"); - if (file) { - XELOGI("Found launch_data for {}", title_name_); - const uint64_t file_size = std::filesystem::file_size(file_path); - xam->loader_data().launch_data_present = true; - xam->loader_data().launch_data.resize(file_size); - fread(xam->loader_data().launch_data.data(), file_size, 1, file); - - fclose(file); - } - } - - // Try and load the resource database (xex only). if (module->title_id()) { auto title_id = fmt::format("{:08X}", module->title_id()); diff --git a/src/xenia/emulator.h b/src/xenia/emulator.h index 2e96f1d5fe..adc7e4d7ee 100644 --- a/src/xenia/emulator.h +++ b/src/xenia/emulator.h @@ -52,6 +52,8 @@ class Window; namespace xe { constexpr fourcc_t kEmulatorSaveSignature = make_fourcc("XSAV"); +static const std::string kDefaultGameSymbolicLink = "GAME:"; +static const std::string kDefaultPartitonSymbolicLink = "D:"; // The main type that runs the whole emulator. // This is responsible for initializing and managing all the various subsystems. diff --git a/src/xenia/kernel/xam/xam_content.cc b/src/xenia/kernel/xam/xam_content.cc index f0b04ff2b0..bb8cb34eed 100644 --- a/src/xenia/kernel/xam/xam_content.cc +++ b/src/xenia/kernel/xam/xam_content.cc @@ -14,10 +14,13 @@ #include "xenia/kernel/user_module.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xam/xam_content_device.h" +#include "xenia/kernel/xam/xam_module.h" #include "xenia/kernel/xam/xam_private.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_module.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_threading.h" #include "xenia/kernel/xenumerator.h" +#include "xenia/ui/imgui_dialog.h" +#include "xenia/ui/imgui_drawer.h" #include "xenia/xbox.h" DEFINE_int32( @@ -498,6 +501,50 @@ dword_result_t XamLoaderGetMediaInfoEx_entry(dword_t unk1, dword_t unk2, DECLARE_XAM_EXPORT1(XamLoaderGetMediaInfoEx, kContent, kStub); +dword_result_t XamContentLaunchImageFromFileInternal_entry( + lpstring_t image_location, lpstring_t xex_name, dword_t unk) { + const std::string image_path = image_location; + const std::string xex_name_ = xex_name; + + vfs::Entry* entry = kernel_state()->file_system()->ResolvePath(image_path); + + if (!entry) { + return 0; + } + + const std::filesystem::path host_path = + kernel_state()->emulator()->content_root() / entry->name(); + if (!std::filesystem::exists(host_path)) { + vfs::VirtualFileSystem::ExtractContentFile( + entry, kernel_state()->emulator()->content_root(), true); + } + + auto xam = kernel_state()->GetKernelModule("xam.xex"); + + auto& loader_data = xam->loader_data(); + loader_data.host_path = xe::path_to_utf8(host_path); + loader_data.launch_path = xex_name_; + + xam->SaveLoaderData(); + + auto display_window = kernel_state()->emulator()->display_window(); + auto imgui_drawer = kernel_state()->emulator()->imgui_drawer(); + + if (display_window && imgui_drawer) { + display_window->app_context().CallInUIThreadSynchronous([imgui_drawer]() { + xe::ui::ImGuiDialog::ShowMessageBox( + imgui_drawer, "Launching new title!", + "Launching new title. \nPlease close Xenia and launch it again. Game " + "should load automatically."); + }); + } + + kernel_state()->TerminateTitle(); + return 0; +} + +DECLARE_XAM_EXPORT1(XamContentLaunchImageFromFileInternal, kContent, kStub); + } // namespace xam } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/xam/xam_info.cc b/src/xenia/kernel/xam/xam_info.cc index ea6ddedf48..9beb10c22d 100644 --- a/src/xenia/kernel/xam/xam_info.cc +++ b/src/xenia/kernel/xam/xam_info.cc @@ -291,24 +291,6 @@ dword_result_t XamLoaderSetLaunchData_entry(lpvoid_t data, dword_t size) { loader_data.launch_data_present = size ? true : false; loader_data.launch_data.resize(size); std::memcpy(loader_data.launch_data.data(), data, size); - - // Because we have no way to restart game while it is working. Remove as soon - // as possible. - const std::filesystem::path launch_data_dir = "launch_data"; - - std::filesystem::path file_path = - launch_data_dir / - fmt::format("{:08X}_launch_data.bin", kernel_state()->title_id()); - - if (!std::filesystem::exists(launch_data_dir)) { - std::filesystem::create_directories(launch_data_dir); - } - - auto file = xe::filesystem::OpenFile(file_path, "wb+"); - if (file) { - fwrite(loader_data.launch_data.data(), size, 1, file); - fclose(file); - } return 0; } DECLARE_XAM_EXPORT1(XamLoaderSetLaunchData, kNone, kSketchy); @@ -357,15 +339,12 @@ void XamLoaderLaunchTitle_entry(lpstring_t raw_name_ptr, dword_t flags) { if (path.empty()) { loader_data.launch_path = "game:\\default.xex"; } else { - if (xe::utf8::find_name_from_guest_path(path) == path) { - path = xe::utf8::join_guest_paths( - xe::utf8::find_base_guest_path( - kernel_state()->GetExecutableModule()->path()), - path); - } - loader_data.launch_path = path; + loader_data.launch_path = xe::path_to_utf8(path); + loader_data.launch_data_present = true; } + xam->SaveLoaderData(); + if (loader_data.launch_data_present) { auto display_window = kernel_state()->emulator()->display_window(); auto imgui_drawer = kernel_state()->emulator()->imgui_drawer(); @@ -375,8 +354,8 @@ void XamLoaderLaunchTitle_entry(lpstring_t raw_name_ptr, dword_t flags) { [imgui_drawer]() { xe::ui::ImGuiDialog::ShowMessageBox( imgui_drawer, "Title was restarted", - "Title closed with new launch data. \nPlease close Xenia and " - "start title again."); + "Title closed with new launch data. \nPlease restart Xenia. " + "Game will be loaded automatically."); }); } } diff --git a/src/xenia/kernel/xam/xam_module.cc b/src/xenia/kernel/xam/xam_module.cc index c32e702bd5..f59302e634 100644 --- a/src/xenia/kernel/xam/xam_module.cc +++ b/src/xenia/kernel/xam/xam_module.cc @@ -63,6 +63,82 @@ void XamModule::RegisterExportTable(xe::cpu::ExportResolver* export_resolver) { XamModule::~XamModule() {} +void XamModule::LoadLoaderData() { + FILE* file = xe::filesystem::OpenFile(kXamModuleLoaderDataFileName, "rb"); + + if (!file) { + loader_data_.launch_data_present = false; + return; + } + + loader_data_.launch_data_present = true; + + auto string_read = [file]() { + uint16_t string_size = 0; + fread(&string_size, sizeof(string_size), 1, file); + + std::string result_string; + result_string.resize(string_size); + fread(result_string.data(), string_size, 1, file); + return result_string; + }; + + loader_data_.host_path = string_read(); + loader_data_.launch_path = string_read(); + + fread(&loader_data_.launch_flags, sizeof(loader_data_.launch_flags), 1, file); + + uint16_t launch_data_size = 0; + fread(&launch_data_size, sizeof(launch_data_size), 1, file); + + if (launch_data_size > 0) { + loader_data_.launch_data.resize(launch_data_size); + fread(loader_data_.launch_data.data(), launch_data_size, 1, file); + } + + fclose(file); + // We read launch data. Let's remove it till next request. + std::filesystem::remove(kXamModuleLoaderDataFileName); +} + +void XamModule::SaveLoaderData() { + FILE* file = xe::filesystem::OpenFile(kXamModuleLoaderDataFileName, "wb"); + + if (!file) { + return; + } + + std::filesystem::path host_path = loader_data_.host_path; + if (host_path.extension() == ".xex") { + host_path.remove_filename(); + host_path = host_path / loader_data_.launch_path; + loader_data_.launch_path = ""; + } + + const std::string host_path_as_string = xe::path_to_utf8(host_path); + const uint16_t host_path_length = + static_cast(host_path_as_string.size()); + + fwrite(&host_path_length, sizeof(host_path_length), 1, file); + fwrite(host_path_as_string.c_str(), host_path_length, 1, file); + + const uint16_t launch_path_length = + static_cast(loader_data_.launch_path.size()); + fwrite(&launch_path_length, sizeof(launch_path_length), 1, file); + fwrite(loader_data_.launch_path.c_str(), launch_path_length, 1, file); + + fwrite(&loader_data_.launch_flags, sizeof(loader_data_.launch_flags), 1, + file); + + const uint16_t launch_data_size = + static_cast(loader_data_.launch_data.size()); + fwrite(&launch_data_size, sizeof(launch_data_size), 1, file); + + fwrite(loader_data_.launch_data.data(), launch_data_size, 1, file); + + fclose(file); +} + } // namespace xam } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/xam/xam_module.h b/src/xenia/kernel/xam/xam_module.h index 6c5d88cbba..30f8ddd50b 100644 --- a/src/xenia/kernel/xam/xam_module.h +++ b/src/xenia/kernel/xam/xam_module.h @@ -23,6 +23,8 @@ namespace xam { bool xeXamIsUIActive(); +static const std::string kXamModuleLoaderDataFileName = "launch_data.bin"; + class XamModule : public KernelModule { public: XamModule(Emulator* emulator, KernelState* kernel_state); @@ -32,11 +34,17 @@ class XamModule : public KernelModule { struct LoaderData { bool launch_data_present = false; - std::vector launch_data; + std::string host_path; // Full host path to next title to load + std::string + launch_path; // Full guest path to next xex. might be default.xex + uint32_t launch_flags = 0; - std::string launch_path; // Full path to next xex + std::vector launch_data; }; + void LoadLoaderData(); + void SaveLoaderData(); + const LoaderData& loader_data() const { return loader_data_; } LoaderData& loader_data() { return loader_data_; } diff --git a/src/xenia/vfs/virtual_file_system.cc b/src/xenia/vfs/virtual_file_system.cc index 08c183c80f..35bf78bf39 100644 --- a/src/xenia/vfs/virtual_file_system.cc +++ b/src/xenia/vfs/virtual_file_system.cc @@ -329,76 +329,89 @@ X_STATUS VirtualFileSystem::OpenFile(Entry* root_entry, return result; } -X_STATUS VirtualFileSystem::ExtractContentFiles( - Device* device, std::filesystem::path base_path) { - // Run through all the files, breadth-first style. - std::queue queue; - auto root = device->ResolvePath("/"); - queue.push(root); - +X_STATUS VirtualFileSystem::ExtractContentFile(Entry* entry, + std::filesystem::path base_path, + bool extract_to_root) { // Allocate a buffer when needed. size_t buffer_size = 0; uint8_t* buffer = nullptr; - while (!queue.empty()) { - auto entry = queue.front(); - queue.pop(); - for (auto& entry : entry->children()) { - queue.push(entry.get()); - } + XELOGI("Extracting file: {}", entry->path()); - XELOGI("Extracting file: {}", entry->path()); - auto dest_name = base_path / xe::to_path(entry->path()); - if (entry->attributes() & kFileAttributeDirectory) { - std::error_code error_code; - std::filesystem::create_directories(dest_name, error_code); - if (error_code) { - return error_code.value(); - } - continue; - } + auto dest_name = base_path / xe::to_path(entry->path()); - vfs::File* in_file = nullptr; - if (entry->Open(FileAccess::kFileReadData, &in_file) != X_STATUS_SUCCESS) { - continue; - } + if (extract_to_root) { + dest_name = base_path / xe::to_path(entry->name()); + } - auto file = xe::filesystem::OpenFile(dest_name, "wb"); - if (!file) { - in_file->Destroy(); - continue; + if (entry->attributes() & kFileAttributeDirectory) { + std::error_code error_code; + std::filesystem::create_directories(dest_name, error_code); + if (error_code) { + return error_code.value(); } + return 0; + } - if (entry->can_map()) { - auto map = entry->OpenMapped(xe::MappedMemory::Mode::kRead); - fwrite(map->data(), map->size(), 1, file); - map->Close(); - } else { - // Can't map the file into memory. Read it into a temporary buffer. - if (!buffer || entry->size() > buffer_size) { - // Resize the buffer. - if (buffer) { - delete[] buffer; - } + vfs::File* in_file = nullptr; + X_STATUS result = entry->Open(FileAccess::kFileReadData, &in_file); + if (result != X_STATUS_SUCCESS) { + return result; + } - // Allocate a buffer rounded up to the nearest 512MB. - buffer_size = xe::round_up(entry->size(), 512_MiB); - buffer = new uint8_t[buffer_size]; + auto file = xe::filesystem::OpenFile(dest_name, "wb"); + if (!file) { + in_file->Destroy(); + return 1; + } + + if (entry->can_map()) { + auto map = entry->OpenMapped(xe::MappedMemory::Mode::kRead); + fwrite(map->data(), map->size(), 1, file); + map->Close(); + } else { + // Can't map the file into memory. Read it into a temporary buffer. + if (!buffer || entry->size() > buffer_size) { + // Resize the buffer. + if (buffer) { + delete[] buffer; } - size_t bytes_read = 0; - in_file->ReadSync(buffer, entry->size(), 0, &bytes_read); - fwrite(buffer, bytes_read, 1, file); + // Allocate a buffer rounded up to the nearest 512MB. + buffer_size = xe::round_up(entry->size(), 512_MiB); + buffer = new uint8_t[buffer_size]; } - fclose(file); - in_file->Destroy(); + size_t bytes_read = 0; + in_file->ReadSync(buffer, entry->size(), 0, &bytes_read); + fwrite(buffer, bytes_read, 1, file); } + fclose(file); + in_file->Destroy(); + if (buffer) { delete[] buffer; } + return 0; +} +X_STATUS VirtualFileSystem::ExtractContentFiles( + Device* device, std::filesystem::path base_path) { + // Run through all the files, breadth-first style. + std::queue queue; + auto root = device->ResolvePath("/"); + queue.push(root); + + while (!queue.empty()) { + auto entry = queue.front(); + queue.pop(); + for (auto& entry : entry->children()) { + queue.push(entry.get()); + } + + ExtractContentFile(entry, base_path); + } return X_STATUS_SUCCESS; } diff --git a/src/xenia/vfs/virtual_file_system.h b/src/xenia/vfs/virtual_file_system.h index 360a23bd55..3b83501bc6 100644 --- a/src/xenia/vfs/virtual_file_system.h +++ b/src/xenia/vfs/virtual_file_system.h @@ -47,6 +47,9 @@ class VirtualFileSystem { bool is_non_directory, File** out_file, FileAction* out_action); + static X_STATUS ExtractContentFile(Entry* entry, + std::filesystem::path base_path, + bool extract_to_root = false); static X_STATUS ExtractContentFiles(Device* device, std::filesystem::path base_path); static void ExtractContentHeader(Device* device,