From a89d7554cdabedc2c8c8a1f851673551fa8e0331 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Mon, 12 Aug 2024 00:44:37 +0200 Subject: [PATCH] Added ability to launch XBEs inside XISO images (redump style isos are also supported) --- CMakeLists.txt | 4 + README.md | 4 +- import/lib86cpu | 2 +- src/console.hpp | 14 +-- src/files.cpp | 51 ++++------ src/files.hpp | 4 +- src/hw/cpu.cpp | 2 +- src/io.cpp | 243 +++++++++++++++++++++++++++++------------------ src/io.hpp | 3 +- src/main.cpp | 23 +++-- src/nxbx.cpp | 26 +++++ src/nxbx.hpp | 12 ++- src/settings.cpp | 4 +- src/settings.hpp | 2 +- src/xbe.cpp | 31 ++++++ src/xbe.hpp | 12 +++ src/xiso.cpp | 189 ++++++++++++++++++++++++++++++++++++ src/xiso.hpp | 24 +++++ 18 files changed, 494 insertions(+), 156 deletions(-) create mode 100644 src/xbe.cpp create mode 100644 src/xbe.hpp create mode 100644 src/xiso.cpp create mode 100644 src/xiso.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9066fa3..a6d3ae1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,8 @@ set(HEADERS "${NXBX_ROOT_DIR}/src/pe.hpp" "${NXBX_ROOT_DIR}/src/settings.hpp" "${NXBX_ROOT_DIR}/src/util.hpp" + "${NXBX_ROOT_DIR}/src/xbe.hpp" + "${NXBX_ROOT_DIR}/src/xiso.hpp" "${NXBX_ROOT_DIR}/src/xpartition.hpp" "${NXBX_ROOT_DIR}/src/hw/cmos.hpp" "${NXBX_ROOT_DIR}/src/hw/cpu.hpp" @@ -89,6 +91,8 @@ set(SOURCES "${NXBX_ROOT_DIR}/src/main.cpp" "${NXBX_ROOT_DIR}/src/settings.cpp" "${NXBX_ROOT_DIR}/src/util.cpp" + "${NXBX_ROOT_DIR}/src/xbe.cpp" + "${NXBX_ROOT_DIR}/src/xiso.cpp" "${NXBX_ROOT_DIR}/src/xpartition.cpp" "${NXBX_ROOT_DIR}/src/hw/cmos.cpp" "${NXBX_ROOT_DIR}/src/hw/cpu.cpp" diff --git a/README.md b/README.md index 3e59fee..696b18c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Nxbx - XBE launcher +# Nxbx - XBE / XISO launcher -Nxbx is a software to start executing XBE (original xbox executable) programs. To do this, it uses [lib86cpu](https://github.com/ergo720/lib86cpu), +Nxbx is a software to start executing XBE (original xbox executable) programs or to launch XISO images (original xbox iso). To do this, it uses [lib86cpu](https://github.com/ergo720/lib86cpu), a cpu emulation library, and [nboxkrnl](https://github.com/ergo720/nboxkrnl), a re-implementation of the kernel of the original xbox.\ **NOTE: It doesn't run any games right now.**\ The only supported architecture is x86-64. diff --git a/import/lib86cpu b/import/lib86cpu index b288e1b..d3b3486 160000 --- a/import/lib86cpu +++ b/import/lib86cpu @@ -1 +1 @@ -Subproject commit b288e1bae527175cfadb4039e35b2dd455f52b8e +Subproject commit d3b3486986868f3282aca3a157b4ef785749aac8 diff --git a/src/console.hpp b/src/console.hpp index a5c2ac0..73d8db2 100644 --- a/src/console.hpp +++ b/src/console.hpp @@ -21,10 +21,10 @@ class console { bool init(const init_info_t &init_info) { if (m_is_init == false) { - if (!((init_info.m_type == console_t::xbox) || - (init_info.m_type == console_t::chihiro) || - (init_info.m_type == console_t::devkit))) { - logger_nxbx(error, "Attempted to create unrecognized machine of type %" PRIu32, (uint32_t)init_info.m_type); + if (!((init_info.m_console_type == console_t::xbox) || + (init_info.m_console_type == console_t::chihiro) || + (init_info.m_console_type == console_t::devkit))) { + logger_nxbx(error, "Attempted to create unrecognized machine of type %" PRIu32, (uint32_t)init_info.m_console_type); return false; } timer::init(); @@ -32,11 +32,11 @@ class console { m_machine.deinit(); return false; } - if (!io::init(init_info.m_nxbx_path, init_info.m_xbe_path, m_machine.get())) { + if (!io::init(init_info, m_machine.get())) { m_machine.deinit(); return false; } - m_type = init_info.m_type; + m_console_type = init_info.m_console_type; m_is_init = true; } return true; @@ -61,5 +61,5 @@ class console { machine m_machine; bool m_is_init; - console_t m_type; + console_t m_console_type; }; diff --git a/src/files.cpp b/src/files.cpp index aee8393..ff496d4 100755 --- a/src/files.cpp +++ b/src/files.cpp @@ -18,29 +18,29 @@ bool -file_exists(std::pair path_to_check, std::filesystem::path &resolved_path) +file_exists(std::filesystem::path dev_path, std::string remaining_name, std::filesystem::path &resolved_path) { - // path_to_check.first -> base device part, decided by host, path_to_check.second -> remaining variable part, decided by xbox + // dev_path -> base device part, decided by host, remaining_name -> remaining variable part, decided by xbox try { - resolved_path = path_to_check.first / path_to_check.second; + resolved_path = dev_path / remaining_name; bool exists = std::filesystem::exists(resolved_path); if (!exists) { // If it failed, the path might still exists, but the OS filesystem doesn't do case-insensitive comparisons (which the xbox always does) // E.g.: on Linux, the ext4 filesystem might trigger this case - if (path_to_check.second.empty()) { + if (remaining_name.empty()) { return false; } // This starts from the device path, and checks each file name component of the path for a case-insensitive match with a host file in the current inspected directory - size_t pos_to_check = 0, name_size = path_to_check.second.size(); + size_t pos_to_check = 0, name_size = remaining_name.size(); int64_t remaining_path_size = name_size; - std::filesystem::path local_path(path_to_check.first); + std::filesystem::path local_path(dev_path); do { - size_t pos = path_to_check.second.find_first_of('\\', pos_to_check); + size_t pos = remaining_name.find_first_of(std::filesystem::path::preferred_separator, pos_to_check); pos = std::min(pos, name_size); - util::xbox_string_view xbox_name(util::traits_cast>(std::string_view(&path_to_check.second[pos_to_check], pos - pos_to_check))); + util::xbox_string_view xbox_name(util::traits_cast>(std::string_view(&remaining_name[pos_to_check], pos - pos_to_check))); for (const auto &directory_entry : std::filesystem::directory_iterator(local_path)) { std::string host_name(directory_entry.path().filename().string()); util::xbox_string xbox_host_name(util::traits_cast>(host_name)); @@ -61,10 +61,7 @@ file_exists(std::pair path_to_check, std::fi } return true; } - catch (const std::filesystem::filesystem_error &e) { - logger_en(info, "Failed to check existence of path %s, the error was %s", resolved_path.string().c_str(), e.what()); - } - catch (const std::bad_alloc &e) { + catch (const std::exception &e) { logger_en(info, "Failed to check existence of path %s, the error was %s", resolved_path.string().c_str(), e.what()); } @@ -72,17 +69,14 @@ file_exists(std::pair path_to_check, std::fi } bool -file_exists(std::pair path_to_check, std::filesystem::path &resolved_path, bool *is_directory) +file_exists(std::filesystem::path dev_path, std::string remaining_name, std::filesystem::path &resolved_path, bool *is_directory) { - if (file_exists(path_to_check, resolved_path)) { + if (file_exists(dev_path, remaining_name, resolved_path)) { try { *is_directory = std::filesystem::is_directory(resolved_path); return true; } - catch (const std::filesystem::filesystem_error &e) { - logger_en(info, "Failed to determine the file type of path %s, the error was %s", resolved_path.string().c_str(), e.what()); - } - catch (const std::bad_alloc &e) { + catch (const std::exception &e) { logger_en(info, "Failed to determine the file type of path %s, the error was %s", resolved_path.string().c_str(), e.what()); } } @@ -96,10 +90,7 @@ file_exists(std::filesystem::path path) try { return std::filesystem::exists(path); } - catch (const std::filesystem::filesystem_error &e) { - logger_en(info, "Failed to determine the file type of path %s, the error was %s", path.string().c_str(), e.what()); - } - catch (const std::bad_alloc &e) { + catch (const std::exception &e) { logger_en(info, "Failed to determine the file type of path %s, the error was %s", path.string().c_str(), e.what()); } @@ -127,10 +118,7 @@ create_directory(std::filesystem::path path) return true; } - catch (const std::filesystem::filesystem_error &e) { - logger_en(info, "Failed to created directory %s, the error was %s", path.string().c_str(), e.what()); - } - catch (const std::bad_alloc &e) { + catch (const std::exception &e) { logger_en(info, "Failed to created directory %s, the error was %s", path.string().c_str(), e.what()); } @@ -153,10 +141,7 @@ create_file(std::filesystem::path path, uint64_t initial_size) try { std::filesystem::resize_file(path, initial_size); } - catch (const std::filesystem::filesystem_error &e) { - logger_en(info, "Failed to set the initial file size of path %s, the error was %s", path.string().c_str(), e.what()); - } - catch (const std::bad_alloc &e) { + catch (const std::exception &e) { logger_en(info, "Failed to set the initial file size of path %s, the error was %s", path.string().c_str(), e.what()); } } @@ -179,11 +164,7 @@ open_file(std::filesystem::path path, std::uintmax_t *size) try { *size = std::filesystem::file_size(path); } - catch (const std::filesystem::filesystem_error &e) { - *size = 0; - logger_en(info, "Failed to determine the file size of path %s, the error was %s", path.string().c_str(), e.what()); - } - catch (const std::bad_alloc &e) { + catch (const std::exception &e) { *size = 0; logger_en(info, "Failed to determine the file size of path %s, the error was %s", path.string().c_str(), e.what()); } diff --git a/src/files.hpp b/src/files.hpp index 9acf66c..4a71e1a 100755 --- a/src/files.hpp +++ b/src/files.hpp @@ -10,8 +10,8 @@ bool create_directory(std::filesystem::path path); -bool file_exists(std::pair path_to_check, std::filesystem::path &resolved_path); -bool file_exists(std::pair path_to_check, std::filesystem::path &resolved_path, bool *is_directory); +bool file_exists(std::filesystem::path dev_path, std::string remaining_name, std::filesystem::path &resolved_path); +bool file_exists(std::filesystem::path dev_path, std::string remaining_name, std::filesystem::path &resolved_path, bool *is_directory); bool file_exists(std::filesystem::path path); std::optional create_file(std::filesystem::path path); std::optional create_file(std::filesystem::path path, uint64_t initial_size); diff --git a/src/hw/cpu.cpp b/src/hw/cpu.cpp index 40d0fba..fcc6b4b 100644 --- a/src/hw/cpu.cpp +++ b/src/hw/cpu.cpp @@ -62,7 +62,7 @@ cpu::reset() bool cpu::init(const init_info_t &init_info) { - m_ramsize = init_info.m_type == console_t::xbox ? RAM_SIZE64 : RAM_SIZE128; + m_ramsize = init_info.m_console_type == console_t::xbox ? RAM_SIZE64 : RAM_SIZE128; // Load the nboxkrnl exe file std::ifstream ifs(init_info.m_kernel.c_str(), std::ios_base::in | std::ios_base::binary); diff --git a/src/io.cpp b/src/io.cpp index d3eda2c..4a83f89 100755 --- a/src/io.cpp +++ b/src/io.cpp @@ -7,6 +7,7 @@ #include "cpu.hpp" #include "eeprom.hpp" #include "kernel.hpp" +#include "xiso.hpp" #include "xpartition.hpp" #include #include @@ -94,6 +95,11 @@ namespace io { not_exists }; + enum class dev_t : uint32_t { + hdd, + dvd, + }; + // io_request as used in nboxkrnl, also packed to make sure it has the same padding and alignment #pragma pack(1) struct packed_io_request { @@ -142,6 +148,20 @@ namespace io { std::unique_ptr io_buffer; // holds the data to be transferred (r/w requests only) }; + // Info about an opened file + struct io_file_info { + std::fstream fs; // opened file stream + std::string path; // host path of the file + uint64_t offset; // file offset inside xiso, or zero for everything else + }; + + // Info about a parsed xbox file path + struct io_path_info { + std::filesystem::path dev_path; + std::string remaining_name; + dev_t dev_type; + }; + io_request::~io_request() { io_request_type io_type = IO_GET_TYPE(this->type); @@ -152,11 +172,12 @@ namespace io { } static cpu_t *lc86cpu; + static input_t dvd_input_type; static std::jthread jthr; static std::deque> curr_io_queue; static std::vector> pending_io_vec; static std::unordered_map> completed_io_info; - static std::array>, NUM_OF_DEVS> xbox_handle_map; + static std::array, NUM_OF_DEVS> xbox_handle_map; static std::mutex queue_mtx; static std::mutex completed_io_mtx; static std::atomic_flag io_pending; @@ -174,7 +195,7 @@ namespace io { return false; } else { - auto pair = xbox_handle_map[handle].emplace(handle, std::make_pair(std::move(*opt), resolved_path.string())); + auto pair = xbox_handle_map[handle].emplace(handle, io_file_info{ std::move(*opt), resolved_path.string(), 0 }); assert(pair.second == true); return true; } @@ -195,7 +216,7 @@ namespace io { return true; } - static std::pair + static io_path_info parse_path(io_request *curr_io_request) { // NOTE1: Paths from the kernel should have the form "\device\\\" @@ -205,27 +226,32 @@ namespace io { std::string_view path(curr_io_request->path, curr_io_request->size); size_t dev_pos = path.find_first_of('\\', 1); // discards "device" assert(dev_pos != std::string_view::npos); - size_t pos = path.find_first_of('\\', dev_pos + 1); + size_t pos = std::min(path.find_first_of('\\', dev_pos + 1), path.length() + 1); assert(pos != std::string_view::npos); util::xbox_string_view device = util::traits_cast>(path.substr(dev_pos + 1, pos - dev_pos - 1)); // extracts device name std::filesystem::path resolved_path; + io_path_info path_info; if (device.compare("CdRom0") == 0) { resolved_path = dvd_path; + path_info.dev_path = resolved_path; + path_info.dev_type = dev_t::dvd; } else { resolved_path = hdd_path; - size_t pos2 = path.find_first_of('\\', pos + 1); - assert(pos2 != std::string_view::npos); + size_t pos2 = std::min(path.find_first_of('\\', pos + 1), path.length()); unsigned partition_num = std::strtoul(&path[pos2 - 1], nullptr, 10); assert(partition_num < XBOX_NUM_OF_PARTITIONS); std::string partition = "Partition" + std::to_string(partition_num); // extracts partition number resolved_path /= partition; pos = pos2; + path_info.dev_path = resolved_path; + path_info.dev_type = dev_t::hdd; } - std::string name(path.substr(pos + 1)); + std::string name(path.substr(std::min(pos + 1, path.length()))); xbox_to_host_separator(name); + path_info.remaining_name = name; - return std::make_pair(resolved_path, name); + return path_info; } static void @@ -263,17 +289,17 @@ namespace io { if (io_type == open) { // This code opens/creates the file according to the CreateDisposition parameter used by NtCreate/OpenFile - auto path_pair = parse_path(curr_io_request.get()); + auto path_info = parse_path(curr_io_request.get()); io_info_block io_result(error, no_data, 0); uint32_t disposition = IO_GET_DISPOSITION(curr_io_request->type); uint32_t flags = IO_GET_FLAGS(curr_io_request->type); - bool host_is_directory, is_directory = flags & io_flags::is_directory; + bool is_directory = flags & io_flags::is_directory; - const auto add_to_map = [&curr_io_request, dev](auto &&opt, io_info_block *io_result, std::filesystem::path resolved_path) { + const auto add_to_map = [&curr_io_request, dev](auto &&opt, io_info_block *io_result, std::filesystem::path resolved_path, uint64_t file_offset) { // NOTE: this insertion will fail when the guest creates a new handle to the same file. This, because it will pass the same host handle, and std::unordered_map // doesn't allow duplicated keys. This is ok though, because we can reuse the same std::fstream for the same file and it will have the same path too logger_en(info, "Opened %s with handle %" PRIu64 " and path %s", opt->is_open() ? "file" : "directory", curr_io_request->handle_oc, resolved_path.string().c_str()); - auto pair = xbox_handle_map[dev].emplace(curr_io_request->handle_oc, std::make_pair(std::move(*opt), resolved_path.string())); + auto pair = xbox_handle_map[dev].emplace(curr_io_request->handle_oc, io_file_info{ std::move(*opt), resolved_path.string(), file_offset }); io_result->status = success; }; const auto check_dir_flags = [flags](bool host_is_directory, io_info_block *io_result) -> bool { @@ -290,80 +316,102 @@ namespace io { } }; - std::filesystem::path resolved_path; - if (file_exists(path_pair, resolved_path, &host_is_directory)) { - io_result.info = exists; - if (disposition == IO_CREATE) { - // Create if doesn't exist - FILE_CREATE - io_result.status = failed; + if ((path_info.dev_type == dev_t::dvd) && (dvd_input_type == input_t::xiso)) { + xiso::file_info_t file_info = xiso::search_file(path_info.remaining_name); + if (file_info.exists) { + assert((disposition == IO_OPEN) || (disposition == IO_OPEN_IF)); io_result.info = exists; - } - else if ((disposition == IO_OPEN) || (disposition == IO_OPEN_IF)) { - // Open if exists - FILE_OPEN - // Open always - FILE_OPEN_IF - if (check_dir_flags(host_is_directory, &io_result)) { + if (check_dir_flags(file_info.is_directory, &io_result)) { + io_result.info = opened; if (is_directory) { - // Open directory: nothing to do - io_result.info = opened; - add_to_map(std::make_optional(), &io_result, resolved_path); + // Open directory + add_to_map(std::make_optional(), &io_result, xiso::dvd_image_path, 0); } else { // Open file - if (auto opt = open_file(resolved_path, &io_result.info2_or_id); opt) { + io_result.info2_or_id = file_info.size; + add_to_map(std::make_optional(std::move(file_info.fs)), &io_result, xiso::dvd_image_path, file_info.offset); + } + } + } + } + else { + bool host_is_directory; + std::filesystem::path resolved_path; + if (file_exists(path_info.dev_path, path_info.remaining_name, resolved_path, &host_is_directory)) { + io_result.info = exists; + if (disposition == IO_CREATE) { + // Create if doesn't exist - FILE_CREATE + io_result.status = failed; + io_result.info = exists; + } + else if ((disposition == IO_OPEN) || (disposition == IO_OPEN_IF)) { + // Open if exists - FILE_OPEN + // Open always - FILE_OPEN_IF + if (check_dir_flags(host_is_directory, &io_result)) { + if (is_directory) { + // Open directory: nothing to do io_result.info = opened; - add_to_map(opt, &io_result, resolved_path); + add_to_map(std::make_optional(), &io_result, resolved_path, 0); + } + else { + // Open file + if (auto opt = open_file(resolved_path, &io_result.info2_or_id); opt) { + io_result.info = opened; + add_to_map(opt, &io_result, resolved_path, 0); + } + } + } + } + else { + // Create always - FILE_SUPERSEDE + // Truncate if exists - FILE_OVERWRITE + // Truncate always - FILE_OVERWRITE_IF + if (check_dir_flags(host_is_directory, &io_result)) { + if (is_directory) { + // Create directory: already exists + io_result.info = exists; + add_to_map(std::make_optional(), &io_result, resolved_path, 0); + } + else { + // Create file + if (auto opt = create_file(resolved_path, curr_io_request->initial_size); opt) { + io_result.info = (disposition == IO_SUPERSEDE) ? superseded : overwritten; + add_to_map(opt, &io_result, resolved_path, 0); + } } } } } else { - // Create always - FILE_SUPERSEDE - // Truncate if exists - FILE_OVERWRITE - // Truncate always - FILE_OVERWRITE_IF - if (check_dir_flags(host_is_directory, &io_result)) { + io_result.info = not_exists; + if ((disposition == IO_CREATE) || (disposition == IO_SUPERSEDE) || (disposition == IO_OPEN_IF) || (disposition == IO_OVERWRITE_IF)) { + // Create if doesn't exist - FILE_CREATE + // Create always - FILE_SUPERSEDE + // Open always - FILE_OPEN_IF + // Truncate always - FILE_OVERWRITE_IF if (is_directory) { - // Create directory: already exists - io_result.info = exists; - add_to_map(std::make_optional(), &io_result, resolved_path); + // Create directory + if (::create_directory(resolved_path)) { + io_result.info = created; + add_to_map(std::make_optional(), &io_result, resolved_path, 0); + } } else { // Create file if (auto opt = create_file(resolved_path, curr_io_request->initial_size); opt) { - io_result.info = (disposition == IO_SUPERSEDE) ? superseded : overwritten; - add_to_map(opt, &io_result, resolved_path); + io_result.info = created; + add_to_map(opt, &io_result, resolved_path, 0); } } } - } - } - else { - io_result.info = not_exists; - if ((disposition == IO_CREATE) || (disposition == IO_SUPERSEDE) || (disposition == IO_OPEN_IF) || (disposition == IO_OVERWRITE_IF)) { - // Create if doesn't exist - FILE_CREATE - // Create always - FILE_SUPERSEDE - // Open always - FILE_OPEN_IF - // Truncate always - FILE_OVERWRITE_IF - if (is_directory) { - // Create directory - if (::create_directory(resolved_path)) { - io_result.info = created; - add_to_map(std::make_optional(), &io_result, resolved_path); - } - } else { - // Create file - if (auto opt = create_file(resolved_path, curr_io_request->initial_size); opt) { - io_result.info = created; - add_to_map(opt, &io_result, resolved_path); - } + // Open if exists - FILE_OPEN + // Truncate if exists - FILE_OVERWRITE + io_result.status = not_found; + io_result.info = not_exists; } } - else { - // Open if exists - FILE_OPEN - // Truncate if exists - FILE_OVERWRITE - io_result.status = not_found; - io_result.info = not_exists; - } } curr_io_request->info = io_result; @@ -384,24 +432,24 @@ namespace io { continue; } - std::fstream *fs = &it->second.first; + std::fstream *fs = &it->second.fs; switch (io_type) { case io_request_type::close: - logger_en(info, "Closed file handle %" PRIu64 " with path %s", it->first, it->second.second.c_str()); + logger_en(info, "Closed file handle %" PRIu64 " with path %s", it->first, it->second.path.c_str()); xbox_handle_map[dev].erase(it); break; case io_request_type::read: if (!fs->is_open()) [[unlikely]] { // Read operation on a directory (this should not happen...) - logger_en(warn, "Read operation to directory handle %" PRIu64 " with path %s", it->first, it->second.second.c_str()); + logger_en(warn, "Read operation to directory handle %" PRIu64 " with path %s", it->first, it->second.path.c_str()); io_result.status = error; io_result.info = no_data; break; } curr_io_request->io_buffer = std::unique_ptr(new char[curr_io_request->size]); - fs->seekg(curr_io_request->offset, fs->beg); + fs->seekg(curr_io_request->offset + it->second.offset); fs->read(curr_io_request->io_buffer.get(), curr_io_request->size); if (fs->good()) { io_result.info = static_cast(fs->gcount()); @@ -412,26 +460,26 @@ namespace io { io_result.status = error; fs->clear(); logger_en(info, "Read operation to file handle %" PRIu64 " with path %s, offset=0x%08" PRIX32 ", size=0x%08" PRIX32 " -> FAILED!", - it->first, it->second.second.c_str(), curr_io_request->offset, curr_io_request->size); + it->first, it->second.path.c_str(), curr_io_request->offset, curr_io_request->size); } break; case io_request_type::write: if (!fs->is_open()) [[unlikely]] { // Write operation on a directory (this should not happen...) - logger_en(warn, "Write operation to directory handle %" PRIu64 " with path %s", it->first, it->second.second.c_str()); + logger_en(warn, "Write operation to directory handle %" PRIu64 " with path %s", it->first, it->second.path.c_str()); io_result.status = error; io_result.info = no_data; break; } - fs->seekg(curr_io_request->offset, fs->beg); + fs->seekg(curr_io_request->offset + it->second.offset); fs->write(curr_io_request->io_buffer.get(), curr_io_request->size); if (!fs->good()) { io_result.status = error; io_result.info = no_data; fs->clear(); logger_en(info, "Write operation to file handle %" PRIu64 " with path %s, offset=0x%08" PRIX32 ", size=0x%08" PRIX32 " -> FAILED!", - it->first, it->second.second.c_str(), curr_io_request->offset, curr_io_request->size); + it->first, it->second.path.c_str(), curr_io_request->offset, curr_io_request->size); } else { io_result.info = static_cast(curr_io_request->size); @@ -442,7 +490,7 @@ namespace io { case io_request_type::remove1: { logger_en(info, "Deleted %s with handle %" PRIu64, fs->is_open() ? "file" : "directory", it->first); - std::string file_path(it->second.second); + std::string file_path(it->second.path); xbox_handle_map[dev].erase(it); std::error_code ec; std::filesystem::remove(file_path, ec); @@ -547,10 +595,10 @@ namespace io { } bool - init(std::string nxbx_path, std::string xbe_path_, cpu_t *cpu) + init(const init_info_t &init_info, cpu_t *cpu) { lc86cpu = cpu; - std::filesystem::path curr_dir = nxbx_path; + std::filesystem::path curr_dir = init_info.m_nxbx_path; curr_dir = curr_dir.remove_filename(); std::filesystem::path hdd_dir = curr_dir; hdd_dir /= "Harddisk/"; @@ -587,25 +635,36 @@ namespace io { } } - std::filesystem::path local_xbe_path = std::filesystem::path(xbe_path_).make_preferred(); - xbe_name = util::traits_cast>(local_xbe_path.filename().string()); - hdd_path = hdd_dir; - dvd_path = local_xbe_path.remove_filename(); - eeprom_path = eeprom_dir.remove_filename(); - xbe_path = "\\Device\\CdRom0\\" + xbe_name; - if (dvd_path.string().starts_with(hdd_path.string())) { - // XBE is installed inside a HDD partition, so set the dvd drive to be empty by setting th dvd path to an invalid directory - // TODO: this should also set the SMC tray state to have no media - size_t partition_num_off = hdd_path.string().size() + 9; - std::string xbox_hdd_dir = "\\Device\\Harddisk0\\Partition" + std::to_string(dvd_path.string()[partition_num_off] - '0'); - std::string xbox_remaining_hdd_dir = dvd_path.string().substr(partition_num_off + 1); - for (size_t pos = 0; pos < xbox_remaining_hdd_dir.size(); ++pos) { - if (xbox_remaining_hdd_dir[pos] == '/') { - xbox_remaining_hdd_dir[pos] = '\\'; // convert to xbox path separator + if (init_info.m_input_type == input_t::xiso) { + xbe_name = "default.xbe"; + hdd_path = hdd_dir; + dvd_path = std::filesystem::path(init_info.m_input_path).make_preferred().remove_filename(); + eeprom_path = eeprom_dir.remove_filename(); + xbe_path = "\\Device\\CdRom0\\" + xbe_name; + dvd_input_type = input_t::xiso; + } + else { + std::filesystem::path local_xbe_path = std::filesystem::path(init_info.m_input_path).make_preferred(); + xbe_name = util::traits_cast>(local_xbe_path.filename().string()); + hdd_path = hdd_dir; + dvd_path = local_xbe_path.remove_filename(); + eeprom_path = eeprom_dir.remove_filename(); + xbe_path = "\\Device\\CdRom0\\" + xbe_name; + dvd_input_type = input_t::xbe; + if (dvd_path.string().starts_with(hdd_path.string())) { + // XBE is installed inside a HDD partition, so set the dvd drive to be empty by setting th dvd path to an invalid directory + // TODO: this should also set the SMC tray state to have no media + size_t partition_num_off = hdd_path.string().size() + 9; + std::string xbox_hdd_dir = "\\Device\\Harddisk0\\Partition" + std::to_string(dvd_path.string()[partition_num_off] - '0'); + std::string xbox_remaining_hdd_dir = dvd_path.string().substr(partition_num_off + 1); + for (size_t pos = 0; pos < xbox_remaining_hdd_dir.size(); ++pos) { + if (xbox_remaining_hdd_dir[pos] == '/') { + xbox_remaining_hdd_dir[pos] = '\\'; // convert to xbox path separator + } } + xbe_path = util::traits_cast>(xbox_hdd_dir + xbox_remaining_hdd_dir + xbe_name.c_str()); + dvd_path = ""; } - xbe_path = util::traits_cast>(xbox_hdd_dir + xbox_remaining_hdd_dir + xbe_name.c_str()); - dvd_path = ""; } if (!open_special_files()) { diff --git a/src/io.hpp b/src/io.hpp index dac50aa..357a1a1 100644 --- a/src/io.hpp +++ b/src/io.hpp @@ -5,6 +5,7 @@ #pragma once #include "util.hpp" +#include "nxbx.hpp" struct cpu_t; @@ -14,7 +15,7 @@ namespace io { inline util::xbox_string xbe_name; inline util::xbox_string xbe_path; - bool init(std::string nxbx_path, std::string xbe_path_, cpu_t *cpu); + bool init(const init_info_t &init_info, cpu_t *cpu); void stop(); void submit_io_packet(uint32_t addr); void flush_pending_packets(); diff --git a/src/main.cpp b/src/main.cpp index 0286098..96f0eda 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ static void print_help() { static const char *help = - "usage: [options] \n\ + "usage: [options] \n\ options: \n\ -k Path to nboxkrnl (xbox kernel) to run\n\ -s Specify assembly syntax (default is AT&T)\n\ @@ -29,7 +29,7 @@ main(int argc, char **argv) { init_info_t init_info; init_info.m_syntax = disas_syntax::att; - init_info.m_type = console_t::xbox; + init_info.m_console_type = console_t::xbox; init_info.m_use_dbg = 0; char option = ' '; @@ -79,16 +79,16 @@ main(int argc, char **argv) } std::string console = argv[idx]; if (console == nxbx::console_to_string(console_t::xbox)) { - init_info.m_type = console_t::xbox; + init_info.m_console_type = console_t::xbox; } else if (console == nxbx::console_to_string(console_t::chihiro)) { - init_info.m_type = console_t::chihiro; + init_info.m_console_type = console_t::chihiro; } else if (console == nxbx::console_to_string(console_t::devkit)) { - init_info.m_type = console_t::devkit; + init_info.m_console_type = console_t::devkit; } else { - switch (init_info.m_type = static_cast(std::stoul(console, nullptr, 0))) + switch (init_info.m_console_type = static_cast(std::stoul(console, nullptr, 0))) { case console_t::xbox: case console_t::chihiro: @@ -118,7 +118,10 @@ main(int argc, char **argv) } } else if ((idx + 1) == argc) { - init_info.m_xbe_path = std::move(arg_str); + if (!nxbx::validate_input_file(init_info, arg_str)) { + return 1; + } + init_info.m_input_path = std::move(arg_str); break; } else { @@ -134,14 +137,14 @@ main(int argc, char **argv) } } - if (init_info.m_xbe_path.empty()) { + if (init_info.m_input_path.empty()) { logger("Input file is required"); return 1; } // FIXME: remove this when the chihiro and devkit console types are supported - if ((init_info.m_type == console_t::chihiro) || (init_info.m_type == console_t::devkit)) { - logger("The %s console type is currently not supported", nxbx::console_to_string(init_info.m_type).data()); + if ((init_info.m_console_type == console_t::chihiro) || (init_info.m_console_type == console_t::devkit)) { + logger("The %s console type is currently not supported", nxbx::console_to_string(init_info.m_console_type).data()); return 1; } diff --git a/src/nxbx.cpp b/src/nxbx.cpp index 25ce938..929ff1b 100644 --- a/src/nxbx.cpp +++ b/src/nxbx.cpp @@ -5,6 +5,9 @@ #include "nxbx.hpp" #include "console.hpp" #include "settings.hpp" +#include "files.hpp" +#include "xbe.hpp" +#include "xiso.hpp" namespace nxbx { @@ -19,6 +22,29 @@ namespace nxbx { return console::get().init(init_info); } + + bool + validate_input_file(init_info_t &init_info, std::string_view arg_str) + { + if (auto opt = open_file(arg_str)) { + if (xbe::validate(arg_str)) { + init_info.m_input_type = input_t::xbe; + return true; + } + + if (xiso::validate(arg_str)) { + init_info.m_input_type = input_t::xiso; + return true; + } + + logger("Unrecognized input file (must be an XBE or XISO)"); + return false; + } + + logger(("Failed to open file \"" + std::string(arg_str) + "\"").c_str()); + return false; + } + bool init_settings(const init_info_t &init_info) { diff --git a/src/nxbx.hpp b/src/nxbx.hpp index dad3635..cf750f5 100644 --- a/src/nxbx.hpp +++ b/src/nxbx.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "logger.hpp" @@ -26,13 +27,19 @@ enum class console_t : uint32_t { devkit, }; +enum class input_t : uint32_t { + xbe, + xiso, +}; + struct init_info_t { std::string m_kernel; std::string m_nxbx_path; - std::string m_xbe_path; + std::string m_input_path; disas_syntax m_syntax; uint32_t m_use_dbg; - console_t m_type; + console_t m_console_type; + input_t m_input_type; }; // Settings struct declarations, used in the settings class @@ -46,6 +53,7 @@ struct core_s { namespace nxbx { bool init_console(const init_info_t &init_info); + bool validate_input_file(init_info_t &init_info, std::string_view arg_str); bool init_settings(const init_info_t &init_info); void save_settings(); template diff --git a/src/settings.cpp b/src/settings.cpp index 5a92c36..66f03e2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -12,10 +12,10 @@ settings::init(const init_info_t &init_info) { m_ini.Reset(); m_ini.SetMultiKey(true); - m_type = init_info.m_type; + m_console_type = init_info.m_console_type; std::filesystem::path curr_dir = init_info.m_nxbx_path; curr_dir = curr_dir.remove_filename(); - curr_dir /= "nxbx-" + nxbx::console_to_string(m_type) + ".ini"; + curr_dir /= "nxbx-" + nxbx::console_to_string(m_console_type) + ".ini"; m_ini_path = curr_dir.string(); SI_Error err = m_ini.LoadFile(m_ini_path.c_str()); diff --git a/src/settings.hpp b/src/settings.hpp index d50cd62..497bec3 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -42,7 +42,7 @@ class settings { static constexpr const char *log_modules1 = "log_modules1"; } m_core_str; std::string m_ini_path; - console_t m_type; + console_t m_console_type; static constexpr uint32_t m_version = 1; static constexpr uint32_t m_log_version = 2; // add one to this every time the log modules change }; diff --git a/src/xbe.cpp b/src/xbe.cpp new file mode 100644 index 0000000..73301bd --- /dev/null +++ b/src/xbe.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: 2024 ergo720 + +#include "xbe.hpp" +#include "files.hpp" +#include "logger.hpp" + + +namespace xbe { + constexpr char magic[] = { 'X', 'B', 'E', 'H' }; + + + bool + validate(std::string_view arg_str) + { + if (auto opt = open_file(arg_str)) { + // XBE: magic is 4 bytes at the very beginning of the file + + char buff[4]; + opt->seekg(0); + opt->read(buff, 4); + if (opt->good() && (std::memcmp(buff, magic, 4) == 0)) { + logger("Detected xbe file"); + return true; + } + } + + return false; + } +} diff --git a/src/xbe.hpp b/src/xbe.hpp new file mode 100644 index 0000000..7889458 --- /dev/null +++ b/src/xbe.hpp @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: 2024 ergo720 + +#pragma once + +#include + + +namespace xbe { + bool validate(std::string_view arg_str); +} diff --git a/src/xiso.cpp b/src/xiso.cpp new file mode 100644 index 0000000..fe38510 --- /dev/null +++ b/src/xiso.cpp @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: 2024 ergo720 + +#include "xiso.hpp" +#include "files.hpp" +#include "util.hpp" +#include "logger.hpp" +#include + +#define SECTOR_SIZE 2048 +#define ROOT_DIR_SECTOR 32 +#define GAME_PARTITION_OFFSET (SECTOR_SIZE * ROOT_DIR_SECTOR * 6192) +#define FILE_DIRECTORY 0x10 + + +namespace xiso { +#pragma pack(1) + struct volume_desc_t { + uint8_t magic1[20]; + uint32_t root_dirent_first_sector; + uint32_t root_dirent_file_size; + int64_t timestamp; + uint8_t unused[1992]; + uint8_t magic2[20]; + }; + + struct dirent_t { + uint16_t left_idx; + uint16_t right_idx; + uint32_t file_sector; + uint32_t file_size; + uint8_t attributes; + uint8_t file_name_length; + char file_name[1]; // on xiso, max length is 255 chars + }; +#pragma pack() + + struct file_entry_t { + uint16_t left_idx; // offset to add to reach the left dirent on this directory level + uint16_t right_idx; // offset to add to reach the right dirent on this directory level + uint32_t file_sector; // sector number of the file pointed by the current dirent + uint32_t file_size; // size of the file pointed by the current dirent + uint8_t attributes; // attributes of the file pointed by the current dirent + char file_name[256]; // name of the file pointed by the current dirent + }; + + + static_assert(sizeof(volume_desc_t) == 2048); + static constexpr char magic[] = { 'M', 'I', 'C', 'R', 'O', 'S', 'O', 'F', 'T', '*', 'X', 'B', 'O', 'X', '*', 'M', 'E', 'D', 'I', 'A' }; + static size_t image_offset; // offset to add to reach the game partition + static uint32_t root_dirent_first_sector; + + + bool + validate(std::string_view arg_str) + { + if (auto opt = open_file(arg_str)) { + // XISO: magic is 20 bytes at start of sector 32 and also at offset 0x7EC of the same sector + + char buff[2048]; + const auto validate_image = [&](size_t offset) { + opt->seekg(SECTOR_SIZE * ROOT_DIR_SECTOR + offset); + opt->read(buff, 2048); + volume_desc_t *volume_desc = (volume_desc_t *)buff; + if (opt->good() && + (std::memcmp(volume_desc->magic1, magic, 20) == 0) && + (std::memcmp(volume_desc->magic2, magic, 20) == 0) && + (volume_desc->root_dirent_first_sector) && + (volume_desc->root_dirent_file_size)) + { + root_dirent_first_sector = volume_desc->root_dirent_first_sector; + return true; + } + return false; + }; + + if (validate_image(0)) { + image_offset = 0; + dvd_image_path = arg_str; + logger("Detected scrubbed xiso file"); + return true; + } + + if (validate_image(GAME_PARTITION_OFFSET)) { + image_offset = GAME_PARTITION_OFFSET; + dvd_image_path = arg_str; + logger("Detected redump xiso file"); + return true; + } + } + + return false; + } + + bool + read_dirent(std::fstream *fs, file_entry_t &file_entry, uint64_t sector, uint64_t offset) + { + char buff[SECTOR_SIZE]; + fs->seekg(SECTOR_SIZE * sector + image_offset + offset); + fs->read(buff, 255 + sizeof(dirent_t) - 1); + if (fs->good()) { + dirent_t *dirent = (dirent_t *)buff; + file_entry.left_idx = dirent->left_idx; + file_entry.right_idx = dirent->right_idx; + file_entry.file_sector = dirent->file_sector; + file_entry.file_size = dirent->file_size; + file_entry.attributes = dirent->attributes; + std::strncpy(file_entry.file_name, dirent->file_name, dirent->file_name_length); + file_entry.file_name[dirent->file_name_length] = '\0'; + return true; + } + + return false; + } + + file_info_t + search_file(std::string_view arg_str) + { + if (arg_str.empty()) { + // special case: open the root directory of the dvd + return file_info_t + { + .fs = std::fstream(), + .exists = true, + .is_directory = true, + .offset = image_offset, + .size = 0 + }; + } + + if (auto opt = open_file(dvd_image_path); opt) { + std::fstream fs = std::move(*opt); + uint64_t offset = 0, curr_pos = 0; + uint32_t curr_sector = root_dirent_first_sector; + file_entry_t file_entry; + util::xbox_string_view path_to_parse = util::traits_cast>(arg_str); + uint64_t pos = std::min(path_to_parse.find_first_of(std::filesystem::path::preferred_separator, curr_pos), path_to_parse.length()); + util::xbox_string_view curr_name = path_to_parse.substr(curr_pos, pos - curr_pos); + + while (true) { + if (read_dirent(&fs, file_entry, curr_sector, offset)) { + int ret = curr_name.compare(file_entry.file_name); + if (ret < 0) { + uint64_t new_offset = (uint64_t)file_entry.left_idx << 2; + if ((new_offset == 0) || // bottom of the tree + (new_offset <= offset)) { // prevent infinite loops + return file_info_t{ .exists = false }; + } + offset = new_offset; + } + else if (ret > 0) { + uint64_t new_offset = (uint64_t)file_entry.right_idx << 2; + if ((new_offset == 0) || // bottom of the tree + (new_offset <= offset)) { // prevent infinite loops + return file_info_t{ .exists = false }; + } + offset = new_offset; + } + else { + curr_pos = pos + 1; + pos = std::min(path_to_parse.find_first_of(std::filesystem::path::preferred_separator, curr_pos), path_to_parse.length()); + if (pos == path_to_parse.length()) { + return file_info_t // processed all path -> we found the requested file/directory + { + .fs = std::move(fs), + .exists = true, + .is_directory = (bool)(file_entry.attributes & FILE_DIRECTORY), + .offset = file_entry.file_sector * SECTOR_SIZE + image_offset, + .size = file_entry.file_size + }; + } + // Some path still remains -> we can only proceed if the current file is a directory + if (file_entry.attributes & FILE_DIRECTORY) { + offset = 0; + curr_sector = file_entry.file_sector * SECTOR_SIZE + image_offset; + curr_name = path_to_parse.substr(curr_pos, pos - curr_pos); + continue; + } + break; + } + } + break; + } + } + + return file_info_t{ .exists = false }; + } +} diff --git a/src/xiso.hpp b/src/xiso.hpp new file mode 100644 index 0000000..a012ac2 --- /dev/null +++ b/src/xiso.hpp @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: 2024 ergo720 + +#pragma once + +#include +#include + + +namespace xiso { + struct file_info_t { + std::fstream fs; + bool exists; + bool is_directory; + uint64_t offset; + size_t size; + }; + + inline std::filesystem::path dvd_image_path; + + bool validate(std::string_view arg_str); + file_info_t search_file(std::string_view arg_str); +}