diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 8bb413260..8188e04f4 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -1534,6 +1534,11 @@ void EmulatorWindow::DisplayHotKeysConfig() { msg); } +std::string EmulatorWindow::CanonicalizeFileExtension( + const std::filesystem::path& path) { + return xe::utf8::lower_ascii(xe::path_to_utf8(path.extension())); +} + xe::X_STATUS EmulatorWindow::RunTitle(std::filesystem::path path_to_file) { bool titleExists = !std::filesystem::exists(path_to_file); @@ -1564,6 +1569,20 @@ xe::X_STATUS EmulatorWindow::RunTitle(std::filesystem::path path_to_file) { // Prevent crashing the emulator by not loading a game if a game is already // loaded. auto abs_path = std::filesystem::absolute(path_to_file); + + auto extension = CanonicalizeFileExtension(abs_path); + + if (extension == ".7z" || extension == ".zip" || extension == ".rar" || + extension == ".tar" || extension == ".gz") { + xe::ShowSimpleMessageBox( + xe::SimpleMessageBoxType::Error, + fmt::format( + "Unsupported format!\n" + "Xenia does not support running software in an archived format.")); + + return X_STATUS_UNSUCCESSFUL; + } + auto result = emulator_->LaunchPath(abs_path); imgui_drawer_.get()->ClearDialogs(); diff --git a/src/xenia/app/emulator_window.h b/src/xenia/app/emulator_window.h index 8cdfcb40b..e1a4ca198 100644 --- a/src/xenia/app/emulator_window.h +++ b/src/xenia/app/emulator_window.h @@ -228,6 +228,9 @@ class EmulatorWindow { bool IsUseNexusForGameBarEnabled(); void DisplayHotKeysConfig(); + static std::string CanonicalizeFileExtension( + const std::filesystem::path& path); + void RunPreviouslyPlayedTitle(); void FillRecentlyLaunchedTitlesMenu(xe::ui::MenuItem* recent_menu); void LoadRecentlyLaunchedTitles(); diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 1dc790ad0..1dcf9b497 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -15,6 +15,7 @@ #include "config.h" #include "third_party/fmt/include/fmt/format.h" #include "third_party/tabulate/single_include/tabulate/tabulate.hpp" +#include "third_party/zarchive/include/zarchive/zarchivecommon.h" #include "xenia/apu/audio_system.h" #include "xenia/base/assert.h" #include "xenia/base/byte_stream.h" @@ -314,32 +315,35 @@ X_STATUS Emulator::TerminateTitle() { return X_STATUS_SUCCESS; } -std::string Emulator::CanonicalizeFileExtension( - const std::filesystem::path& path) { - return xe::utf8::lower_ascii(xe::path_to_utf8(path.extension())); -} - -const std::unique_ptr Emulator::CreateVfsDeviceBasedOnPath( +const std::unique_ptr Emulator::CreateVfsDevice( const std::filesystem::path& path, const std::string_view mount_path) { - if (!path.has_extension()) { - return vfs::XContentContainerDevice::CreateContentDevice(mount_path, path); + // Must check if the type has changed e.g. XamSwapDisc + switch (GetFileSignature(path)) { + case FileSignatureType::XEX1: + case FileSignatureType::XEX2: + case FileSignatureType::ELF: { + auto parent_path = path.parent_path(); + return std::make_unique( + mount_path, parent_path, !cvars::allow_game_relative_writes); + } break; + case FileSignatureType::LIVE: + case FileSignatureType::CON: + case FileSignatureType::PIRS: { + return vfs::XContentContainerDevice::CreateContentDevice(mount_path, + path); + } break; + case FileSignatureType::XISO: { + return std::make_unique(mount_path, path); + } break; + case FileSignatureType::ZAR: { + return std::make_unique(mount_path, path); + } break; + case FileSignatureType::EXE: + case FileSignatureType::Unknown: + default: + return nullptr; + break; } - auto extension = CanonicalizeFileExtension(path); - if (extension == ".xex" || extension == ".elf" || extension == ".exe") { - auto parent_path = path.parent_path(); - return std::make_unique( - 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" || - extension == ".tar" || extension == ".gz") { - xe::ShowSimpleMessageBox( - xe::SimpleMessageBoxType::Error, - fmt::format( - "Unsupported format!\n" - "Xenia does not support running software in an archived format.")); - } - return std::make_unique(mount_path, path); } uint64_t Emulator::GetPersistentEmulatorFlags() { @@ -386,7 +390,7 @@ void Emulator::SetPersistentEmulatorFlags(uint64_t new_flags) { X_STATUS Emulator::MountPath(const std::filesystem::path& path, const std::string_view mount_path) { - auto device = CreateVfsDeviceBasedOnPath(path, mount_path); + auto device = CreateVfsDevice(path, mount_path); if (!device || !device->Initialize()) { XELOGE( "Unable to mount the selected file, it is an unsupported format or " @@ -410,30 +414,108 @@ X_STATUS Emulator::MountPath(const std::filesystem::path& path, return X_STATUS_SUCCESS; } -X_STATUS Emulator::LaunchPath(const std::filesystem::path& path) { - // Launch based on file type. - // This is a silly guess based on file extension. +Emulator::FileSignatureType Emulator::GetFileSignature( + const std::filesystem::path& path) { + FILE* file = xe::filesystem::OpenFile(path, "rb"); + if (!file) { + return FileSignatureType::Unknown; + } + + const uint64_t file_size = std::filesystem::file_size(path); + const uint64_t header_size = 4; + + if (file_size < header_size) { + return FileSignatureType::Unknown; + } + + char file_magic[header_size]; + fread_s(file_magic, sizeof(file_magic), 1, header_size, file); + + fourcc_t magic_value = + make_fourcc(file_magic[0], file_magic[1], file_magic[2], file_magic[3]); + + fclose(file); + + switch (magic_value) { + case xe::cpu::kXEX1Signature: + return FileSignatureType::XEX1; + case xe::cpu::kXEX2Signature: + return FileSignatureType::XEX2; + case xe::vfs::kCONSignature: + return FileSignatureType::CON; + case xe::vfs::kLIVESignature: + return FileSignatureType::LIVE; + case xe::vfs::kPIRSSignature: + return FileSignatureType::PIRS; + case xe::vfs::kXSFSignature: + return FileSignatureType::XISO; + case xe::cpu::kElfSignature: + return FileSignatureType::ELF; + default: + break; + } + + magic_value = make_fourcc(file_magic[0], file_magic[1], 0, 0); + + if (xe::kernel::kEXESignature == magic_value) { + return FileSignatureType::EXE; + } + + file = xe::filesystem::OpenFile(path, "rb"); + xe::filesystem::Seek(file, header_size, SEEK_END); + fread_s(file_magic, sizeof(file_magic), 1, header_size, file); + fclose(file); + + magic_value = + make_fourcc(file_magic[0], file_magic[1], file_magic[2], file_magic[3]); + + if (xe::vfs::kZarMagic == magic_value) { + return FileSignatureType::ZAR; + } + + // Check if XISO + std::unique_ptr device = + std::make_unique("", path); + + XELOGI("Checking for XISO"); + + if (device->Initialize()) { + return FileSignatureType::XISO; + } + + return FileSignatureType::Unknown; +} + +X_STATUS Emulator::LaunchPath(const std::filesystem::path& path) { X_STATUS mount_result = X_STATUS_SUCCESS; - if (!path.has_extension()) { - // Likely an STFS container. - mount_result = MountPath(path, "\\Device\\Cdrom0"); - return mount_result ? mount_result : LaunchStfsContainer(path); - }; - auto extension = xe::utf8::lower_ascii(xe::path_to_utf8(path.extension())); - if (extension == ".xex" || extension == ".elf" || extension == ".exe") { - // Treat as a naked xex file. - mount_result = MountPath(path, "\\Device\\Harddisk0\\Partition1"); - return mount_result ? mount_result : LaunchXexFile(path); - } else if (extension == ".zar") { - // Assume a disc image. - mount_result = MountPath(path, "\\Device\\Cdrom0"); - return mount_result ? mount_result : LaunchDiscArchive(path); - } else { - // Assume a disc image. - mount_result = MountPath(path, "\\Device\\Cdrom0"); - return mount_result ? mount_result : LaunchDiscImage(path); + switch (GetFileSignature(path)) { + case FileSignatureType::XEX1: + case FileSignatureType::XEX2: + case FileSignatureType::ELF: { + mount_result = MountPath(path, "\\Device\\Harddisk0\\Partition1"); + return mount_result ? mount_result : LaunchXexFile(path); + } break; + case FileSignatureType::LIVE: + case FileSignatureType::CON: + case FileSignatureType::PIRS: { + mount_result = MountPath(path, "\\Device\\Cdrom0"); + return mount_result ? mount_result : LaunchStfsContainer(path); + } break; + case FileSignatureType::XISO: { + mount_result = MountPath(path, "\\Device\\Cdrom0"); + return mount_result ? mount_result : LaunchDiscImage(path); + } break; + case FileSignatureType::ZAR: { + mount_result = MountPath(path, "\\Device\\Cdrom0"); + return mount_result ? mount_result : LaunchDiscArchive(path); + } break; + case FileSignatureType::EXE: + case FileSignatureType::Unknown: + default: + return X_STATUS_NOT_SUPPORTED; + break; } } diff --git a/src/xenia/emulator.h b/src/xenia/emulator.h index 1ffb43f68..e2fcb3b7e 100644 --- a/src/xenia/emulator.h +++ b/src/xenia/emulator.h @@ -181,11 +181,28 @@ class Emulator { // Terminates the currently running title. X_STATUS TerminateTitle(); - const std::unique_ptr CreateVfsDeviceBasedOnPath( + const std::unique_ptr CreateVfsDevice( const std::filesystem::path& path, const std::string_view mount_path); X_STATUS MountPath(const std::filesystem::path& path, const std::string_view mount_path); + + enum class FileSignatureType { + XEX1, + XEX2, + ELF, + CON, + LIVE, + PIRS, + XISO, + ZAR, + EXE, + Unknown + }; + + // Determine the executable signature + FileSignatureType GetFileSignature(const std::filesystem::path& path); + // 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. @@ -233,8 +250,6 @@ class Emulator { enum : uint64_t { EmulatorFlagDisclaimerAcknowledged = 1ULL << 0 }; static uint64_t GetPersistentEmulatorFlags(); static void SetPersistentEmulatorFlags(uint64_t new_flags); - static std::string CanonicalizeFileExtension( - const std::filesystem::path& path); static bool ExceptionCallbackThunk(Exception* ex, void* data); bool ExceptionCallback(Exception* ex); diff --git a/src/xenia/kernel/user_module.cc b/src/xenia/kernel/user_module.cc index 88bc6a14b..61e088f8c 100644 --- a/src/xenia/kernel/user_module.cc +++ b/src/xenia/kernel/user_module.cc @@ -126,9 +126,12 @@ X_STATUS UserModule::LoadFromMemory(const void* addr, const size_t length) { } else if (magic == xe::cpu::kElfSignature) { module_format_ = kModuleFormatElf; } else { - be magic16; - magic16.value = xe::load(addr); - if (magic16 == 0x4D5A) { + uint8_t M = xe::load(addr); + uint8_t Z = xe::load(reinterpret_cast( + reinterpret_cast(addr) + sizeof(uint8_t))); + + magic = make_fourcc(M, Z, 0, 0); + if (magic == kEXESignature) { XELOGE("XNA executables are not yet implemented"); return X_STATUS_NOT_IMPLEMENTED; } else { diff --git a/src/xenia/kernel/user_module.h b/src/xenia/kernel/user_module.h index c2f89e070..8d2b2d2f2 100644 --- a/src/xenia/kernel/user_module.h +++ b/src/xenia/kernel/user_module.h @@ -31,6 +31,8 @@ class XThread; namespace xe { namespace kernel { +constexpr fourcc_t kEXESignature = make_fourcc('M', 'Z', 0, 0); + class UserModule : public XModule { public: UserModule(KernelState* kernel_state); diff --git a/src/xenia/vfs/devices/disc_image_device.h b/src/xenia/vfs/devices/disc_image_device.h index cbbfa5024..3b098944c 100644 --- a/src/xenia/vfs/devices/disc_image_device.h +++ b/src/xenia/vfs/devices/disc_image_device.h @@ -21,6 +21,8 @@ namespace vfs { class DiscImageEntry; +constexpr fourcc_t kXSFSignature = make_fourcc(0x58, 0x53, 0x46, 0x1A); + class DiscImageDevice : public Device { public: DiscImageDevice(const std::string_view mount_path, diff --git a/src/xenia/vfs/devices/disc_zarchive_device.h b/src/xenia/vfs/devices/disc_zarchive_device.h index 822aae29b..8ea3188a2 100644 --- a/src/xenia/vfs/devices/disc_zarchive_device.h +++ b/src/xenia/vfs/devices/disc_zarchive_device.h @@ -21,6 +21,11 @@ namespace xe { namespace vfs { +const fourcc_t kZarMagic = make_fourcc((_ZARCHIVE::Footer::kMagic >> 24 & 0xFF), + (_ZARCHIVE::Footer::kMagic >> 16 & 0xFF), + (_ZARCHIVE::Footer::kMagic >> 8 & 0xFF), + (_ZARCHIVE::Footer::kMagic & 0xFF)); + class DiscZarchiveEntry; class DiscZarchiveDevice : public Device { diff --git a/src/xenia/vfs/devices/xcontent_container_device.h b/src/xenia/vfs/devices/xcontent_container_device.h index 1cf4adf3a..5395f844b 100644 --- a/src/xenia/vfs/devices/xcontent_container_device.h +++ b/src/xenia/vfs/devices/xcontent_container_device.h @@ -22,6 +22,11 @@ namespace xe { namespace vfs { + +constexpr fourcc_t kLIVESignature = make_fourcc("LIVE"); +constexpr fourcc_t kCONSignature = make_fourcc("CON "); +constexpr fourcc_t kPIRSSignature = make_fourcc("PIRS"); + class XContentContainerDevice : public Device { public: const static uint32_t kBlockSize = 0x1000;