From ed6cd3cdab997d1d0770556968d8f4c1df5ca9a6 Mon Sep 17 00:00:00 2001 From: aperezro Date: Fri, 5 Jun 2026 14:01:26 -0600 Subject: [PATCH] Complete iOS IPA packaging flow --- CMakePresets.json | 4 + UnleashedRecomp/CMakeLists.txt | 20 ++++- UnleashedRecomp/install/update_checker.cpp | 4 +- .../install/xcontent_file_system.cpp | 2 + UnleashedRecompLib/CMakeLists.txt | 44 ++++++++--- thirdparty/nfd_ios_stub.c | 3 +- tools/CMakeLists.txt | 22 +++++- tools/iso_extract/CMakeLists.txt | 13 ++++ tools/iso_extract/iso_extract.cpp | 74 +++++++++++++++++++ tools/package_ios_ipa.sh | 26 ++++++- tools/patches/plume-ios-sdl-vulkan.patch | 20 ++++- tools/xcontent_extract/CMakeLists.txt | 13 ++++ tools/xcontent_extract/xcontent_extract.cpp | 73 ++++++++++++++++++ 13 files changed, 295 insertions(+), 23 deletions(-) create mode 100644 tools/iso_extract/CMakeLists.txt create mode 100644 tools/iso_extract/iso_extract.cpp create mode 100644 tools/xcontent_extract/CMakeLists.txt create mode 100644 tools/xcontent_extract/xcontent_extract.cpp diff --git a/CMakePresets.json b/CMakePresets.json index cbd2260a..1aa41db5 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -177,6 +177,8 @@ "CMAKE_OSX_SYSROOT": "iphonesimulator", "CMAKE_OSX_ARCHITECTURES": "arm64", "CMAKE_BUILD_TYPE": "Debug", + "UNLEASHED_RECOMP_SKIP_TARGET_TOOL_EXECUTABLES": true, + "UNLEASHED_RECOMP_HOST_TOOLS_DIR": "${sourceDir}/out/build/macos-release", "CMAKE_TOOLCHAIN_FILE": { "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", "type": "FILEPATH" @@ -207,6 +209,8 @@ "CMAKE_OSX_ARCHITECTURES": "arm64", "CMAKE_BUILD_TYPE": "Release", "CMAKE_INTERPROCEDURAL_OPTIMIZATION": true, + "UNLEASHED_RECOMP_SKIP_TARGET_TOOL_EXECUTABLES": true, + "UNLEASHED_RECOMP_HOST_TOOLS_DIR": "${sourceDir}/out/build/macos-release", "CMAKE_TOOLCHAIN_FILE": { "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", "type": "FILEPATH" diff --git a/UnleashedRecomp/CMakeLists.txt b/UnleashedRecomp/CMakeLists.txt index d22da7b9..904d0222 100644 --- a/UnleashedRecomp/CMakeLists.txt +++ b/UnleashedRecomp/CMakeLists.txt @@ -1,4 +1,18 @@ -project("UnleashedRecomp") +project("UnleashedRecomp") + +set(UNLEASHED_RECOMP_HOST_TOOLS_DIR "" CACHE PATH "Directory containing host-built recompilation tools.") + +function(unleashed_recomp_resolve_tool OUT_VAR TARGET_NAME RELATIVE_PATH) + if (UNLEASHED_RECOMP_HOST_TOOLS_DIR) + set(${OUT_VAR} "${UNLEASHED_RECOMP_HOST_TOOLS_DIR}/${RELATIVE_PATH}" PARENT_SCOPE) + elseif (TARGET ${TARGET_NAME}) + set(${OUT_VAR} "$" PARENT_SCOPE) + else() + message(FATAL_ERROR "Tool ${TARGET_NAME} is not available. Set UNLEASHED_RECOMP_HOST_TOOLS_DIR.") + endif() +endfunction() + +unleashed_recomp_resolve_tool(UNLEASHED_RECOMP_FILE_TO_C_TOOL file_to_c "tools/file_to_c/file_to_c") if (WIN32) option(UNLEASHED_RECOMP_D3D12 "Add D3D12 support for rendering" ON) @@ -27,8 +41,8 @@ function(BIN2C) set(BIN2C_ARGS_COMPRESSION_TYPE "none") endif() - add_custom_command(OUTPUT "${BIN2C_ARGS_DEST_FILE}.c" - COMMAND $ "${BIN2C_ARGS_SOURCE_FILE}" "${BIN2C_ARGS_ARRAY_NAME}" "${BIN2C_ARGS_COMPRESSION_TYPE}" "${BIN2C_ARGS_DEST_FILE}.c" "${BIN2C_ARGS_DEST_FILE}.h" + add_custom_command(OUTPUT "${BIN2C_ARGS_DEST_FILE}.c" + COMMAND "${UNLEASHED_RECOMP_FILE_TO_C_TOOL}" "${BIN2C_ARGS_SOURCE_FILE}" "${BIN2C_ARGS_ARRAY_NAME}" "${BIN2C_ARGS_COMPRESSION_TYPE}" "${BIN2C_ARGS_DEST_FILE}.c" "${BIN2C_ARGS_DEST_FILE}.h" DEPENDS "${BIN2C_ARGS_SOURCE_FILE}" BYPRODUCTS "${BIN2C_ARGS_DEST_FILE}.h" COMMENT "Generating binary header for ${BIN2C_ARGS_SOURCE_FILE}..." diff --git a/UnleashedRecomp/install/update_checker.cpp b/UnleashedRecomp/install/update_checker.cpp index 2a6f5b1f..6e08de0e 100644 --- a/UnleashedRecomp/install/update_checker.cpp +++ b/UnleashedRecomp/install/update_checker.cpp @@ -166,9 +166,11 @@ void UpdateChecker::visitWebsite() #elif defined(__linux__) std::string command = "xdg-open " + std::string(VISIT_URL) + " &"; std::system(command.c_str()); -#elif defined(__APPLE__) +#elif defined(__APPLE__) && !defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) std::string command = "open " + std::string(VISIT_URL) + " &"; std::system(command.c_str()); +#elif defined(__APPLE__) + // iOS apps cannot spawn external commands; leave website navigation to platform UI. #else static_assert(false, "Visit website not implemented for this platform."); #endif diff --git a/UnleashedRecomp/install/xcontent_file_system.cpp b/UnleashedRecomp/install/xcontent_file_system.cpp index 68605fbd..911d6962 100644 --- a/UnleashedRecomp/install/xcontent_file_system.cpp +++ b/UnleashedRecomp/install/xcontent_file_system.cpp @@ -13,8 +13,10 @@ #include "xcontent_file_system.h" #include +#include #include #include +#include enum class XContentPackageType { diff --git a/UnleashedRecompLib/CMakeLists.txt b/UnleashedRecompLib/CMakeLists.txt index 8514bde8..a9464d99 100644 --- a/UnleashedRecompLib/CMakeLists.txt +++ b/UnleashedRecompLib/CMakeLists.txt @@ -10,9 +10,27 @@ else() add_compile_options(-ffp-model=strict) endif() -target_compile_definitions(XenonRecomp PRIVATE - XENON_RECOMP_CONFIG_FILE_PATH=\"${CMAKE_CURRENT_SOURCE_DIR}/config/SWA.toml\" - XENON_RECOMP_HEADER_FILE_PATH=\"${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp/XenonUtils/ppc_context.h\") +set(UNLEASHED_RECOMP_HOST_TOOLS_DIR "" CACHE PATH "Directory containing host-built recompilation tools.") + +function(unleashed_recomp_resolve_tool OUT_VAR TARGET_NAME RELATIVE_PATH) + if (UNLEASHED_RECOMP_HOST_TOOLS_DIR) + set(${OUT_VAR} "${UNLEASHED_RECOMP_HOST_TOOLS_DIR}/${RELATIVE_PATH}" PARENT_SCOPE) + elseif (TARGET ${TARGET_NAME}) + set(${OUT_VAR} "$" PARENT_SCOPE) + else() + message(FATAL_ERROR "Tool ${TARGET_NAME} is not available. Set UNLEASHED_RECOMP_HOST_TOOLS_DIR.") + endif() +endfunction() + +unleashed_recomp_resolve_tool(UNLEASHED_RECOMP_XENON_RECOMP_TOOL XenonRecomp "tools/XenonRecomp/XenonRecomp/XenonRecomp") +unleashed_recomp_resolve_tool(UNLEASHED_RECOMP_X_DECOMPRESS_TOOL x_decompress "tools/x_decompress/x_decompress") +unleashed_recomp_resolve_tool(UNLEASHED_RECOMP_XENOS_RECOMP_TOOL XenosRecomp "tools/XenosRecomp/XenosRecomp/XenosRecomp") + +if (TARGET XenonRecomp) + target_compile_definitions(XenonRecomp PRIVATE + XENON_RECOMP_CONFIG_FILE_PATH=\"${CMAKE_CURRENT_SOURCE_DIR}/config/SWA.toml\" + XENON_RECOMP_HEADER_FILE_PATH=\"${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp/XenonUtils/ppc_context.h\") +endif() set(UNLEASHED_RECOMP_PPC_RECOMPILED_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/ppc/ppc_config.h" @@ -30,7 +48,7 @@ add_custom_command( "${CMAKE_CURRENT_SOURCE_DIR}/private/default_patched.xex" ${UNLEASHED_RECOMP_PPC_RECOMPILED_SOURCES} COMMAND - $ + "${UNLEASHED_RECOMP_XENON_RECOMP_TOOL}" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/private/default.xex" "${CMAKE_CURRENT_SOURCE_DIR}/private/default.xexp" @@ -42,7 +60,7 @@ add_custom_command( OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/private/shader_decompressed.ar" COMMAND - $ "${CMAKE_CURRENT_SOURCE_DIR}/private/shader.ar" "${CMAKE_CURRENT_SOURCE_DIR}/private/shader_decompressed.ar" + "${UNLEASHED_RECOMP_X_DECOMPRESS_TOOL}" "${CMAKE_CURRENT_SOURCE_DIR}/private/shader.ar" "${CMAKE_CURRENT_SOURCE_DIR}/private/shader_decompressed.ar" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/private/shader.ar" ) @@ -50,12 +68,14 @@ add_custom_command( set(XENOS_RECOMP_ROOT "${UNLEASHED_RECOMP_TOOLS_ROOT}/XenosRecomp/XenosRecomp") set(XENOS_RECOMP_INCLUDE "${XENOS_RECOMP_ROOT}/shader_common.h") -target_compile_definitions(XenosRecomp PRIVATE - XENOS_RECOMP_INPUT=\"${CMAKE_CURRENT_SOURCE_DIR}/private\" - XENOS_RECOMP_OUTPUT=\"${CMAKE_CURRENT_SOURCE_DIR}/shader/shader_cache.cpp\" - XENOS_RECOMP_INCLUDE_INPUT=\"${XENOS_RECOMP_INCLUDE}\" - UNLEASHED_RECOMP -) +if (TARGET XenosRecomp) + target_compile_definitions(XenosRecomp PRIVATE + XENOS_RECOMP_INPUT=\"${CMAKE_CURRENT_SOURCE_DIR}/private\" + XENOS_RECOMP_OUTPUT=\"${CMAKE_CURRENT_SOURCE_DIR}/shader/shader_cache.cpp\" + XENOS_RECOMP_INCLUDE_INPUT=\"${XENOS_RECOMP_INCLUDE}\" + UNLEASHED_RECOMP + ) +endif() file(GLOB XENOS_RECOMP_SOURCES "${XENOS_RECOMP_ROOT}/*.h" @@ -66,7 +86,7 @@ add_custom_command( OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/shader/shader_cache.cpp" COMMAND - $ + "${UNLEASHED_RECOMP_XENOS_RECOMP_TOOL}" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/private/default_patched.xex" "${CMAKE_CURRENT_SOURCE_DIR}/private/shader_decompressed.ar" diff --git a/thirdparty/nfd_ios_stub.c b/thirdparty/nfd_ios_stub.c index 05476b3e..9f5fd902 100644 --- a/thirdparty/nfd_ios_stub.c +++ b/thirdparty/nfd_ios_stub.c @@ -6,6 +6,7 @@ static const char* g_nfd_ios_error = "Native file dialogs are not implemented fo nfdresult_t NFD_Init(void) { return NFD_OKAY; } void NFD_Quit(void) {} const char* NFD_GetError(void) { return g_nfd_ios_error; } +void NFD_ClearError(void) {} void NFD_FreePathN(nfdnchar_t* filePath) { free(filePath); } void NFD_FreePathU8(nfdu8char_t* filePath) { free(filePath); } @@ -37,6 +38,6 @@ void NFD_PathSet_FreePathN(const nfdnchar_t* filePath) { free((void*)filePath); void NFD_PathSet_FreePathU8(const nfdu8char_t* filePath) { free((void*)filePath); } void NFD_PathSet_Free(const nfdpathset_t* pathSet) { (void)pathSet; } void NFD_PathSet_FreeEnum(nfdpathsetenum_t* enumerator) { (void)enumerator; } -nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, nfdpathsetenum_t** outEnumerator) { (void)pathSet; (void)outEnumerator; return NFD_ERROR; } +nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, nfdpathsetenum_t* outEnumerator) { (void)pathSet; (void)outEnumerator; return NFD_ERROR; } nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath) { (void)enumerator; (void)outPath; return NFD_ERROR; } nfdresult_t NFD_PathSet_EnumNextU8(nfdpathsetenum_t* enumerator, nfdu8char_t** outPath) { (void)enumerator; (void)outPath; return NFD_ERROR; } diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index bdcf384a..327d2fe3 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,6 +1,20 @@ add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/bc_diff) -add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/file_to_c) add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/fshasher) -add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/x_decompress) -add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp) -add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/XenosRecomp) +if (NOT UNLEASHED_RECOMP_SKIP_TARGET_TOOL_EXECUTABLES) + add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/file_to_c) + add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/x_decompress) + add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp) + add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/XenosRecomp) +else() + set(THIRDPARTY_ROOT ${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp/thirdparty) + add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp/thirdparty) + add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp/XenonUtils) + + if (NOT TARGET libzstd) + add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/XenosRecomp/thirdparty/zstd/build/cmake) + endif() +endif() +if (NOT UNLEASHED_RECOMP_SKIP_TARGET_TOOL_EXECUTABLES) + add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/iso_extract) + add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/xcontent_extract) +endif() diff --git a/tools/iso_extract/CMakeLists.txt b/tools/iso_extract/CMakeLists.txt new file mode 100644 index 00000000..d38f5218 --- /dev/null +++ b/tools/iso_extract/CMakeLists.txt @@ -0,0 +1,13 @@ +project("iso_extract") + +add_executable(iso_extract + "iso_extract.cpp" + "${CMAKE_SOURCE_DIR}/UnleashedRecomp/install/iso_file_system.cpp" +) + +target_include_directories(iso_extract PRIVATE + "${CMAKE_SOURCE_DIR}/UnleashedRecomp/install" + "${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp/XenonUtils" +) + +target_link_libraries(iso_extract PRIVATE XenonUtils) diff --git a/tools/iso_extract/iso_extract.cpp b/tools/iso_extract/iso_extract.cpp new file mode 100644 index 00000000..4333b99d --- /dev/null +++ b/tools/iso_extract/iso_extract.cpp @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include +#include + +#include + +static bool ExtractFile(const ISOFileSystem& iso, const std::string& sourcePath, const std::filesystem::path& outputPath) +{ + std::vector data; + size_t dataSize = iso.getSize(sourcePath); + if (dataSize == 0) + { + std::cerr << "File not found in ISO: " << sourcePath << "\n"; + return false; + } + + data.resize(dataSize); + if (!iso.load(sourcePath, data.data(), data.size())) + { + std::cerr << "File not found in ISO: " << sourcePath << "\n"; + return false; + } + + std::error_code ec; + std::filesystem::create_directories(outputPath.parent_path(), ec); + if (ec) + { + std::cerr << "Failed to create output directory: " << outputPath.parent_path() << "\n"; + return false; + } + + std::ofstream output(outputPath, std::ios::binary); + if (!output) + { + std::cerr << "Failed to open output file: " << outputPath << "\n"; + return false; + } + + output.write(reinterpret_cast(data.data()), static_cast(data.size())); + if (!output) + { + std::cerr << "Failed to write output file: " << outputPath << "\n"; + return false; + } + + return true; +} + +int main(int argc, char** argv) +{ + if (argc < 4 || ((argc - 2) % 2) != 0) + { + std::cerr << "Usage: iso_extract [ ...]\n"; + return 2; + } + + auto iso = ISOFileSystem::create(argv[1]); + if (!iso) + { + std::cerr << "Failed to open Xbox 360 ISO: " << argv[1] << "\n"; + return 1; + } + + bool ok = true; + for (int i = 2; i < argc; i += 2) + { + ok = ExtractFile(*iso, argv[i], argv[i + 1]) && ok; + } + + return ok ? 0 : 1; +} diff --git a/tools/package_ios_ipa.sh b/tools/package_ios_ipa.sh index aab2c222..409c8891 100755 --- a/tools/package_ios_ipa.sh +++ b/tools/package_ios_ipa.sh @@ -3,14 +3,35 @@ set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" BUILD_DIR="$ROOT/out/build/ios-device-release" +HOST_BUILD_DIR="$ROOT/out/build/macos-release" IPA_DIR="$ROOT/out/ipa" PAYLOAD_DIR="$IPA_DIR/Payload" -APP_PATH="$BUILD_DIR/Unleashed Recompiled.app" +APP_PATH="$BUILD_DIR/UnleashedRecomp/Unleashed Recompiled.app" IPA_PATH="$IPA_DIR/UnleashedRecompiled.ipa" DEFAULT_SIGNING_IDENTITY="Apple Development: aroblesalago@gmail.com (MK28YRUCCG)" "$ROOT/tools/apply_ios_submodule_patches.sh" +if [[ -n "${ISO_PATH:-}" ]]; then + cmake --preset macos-release + cmake --build "$HOST_BUILD_DIR" --target iso_extract -j "${JOBS:-8}" + "$HOST_BUILD_DIR/tools/iso_extract/iso_extract" "$ISO_PATH" \ + default.xex "$ROOT/UnleashedRecompLib/private/default.xex" \ + shader.ar "$ROOT/UnleashedRecompLib/private/shader.ar" +fi + +if [[ -n "${XEXP_PATH:-}" ]]; then + mkdir -p "$ROOT/UnleashedRecompLib/private" + cp "$XEXP_PATH" "$ROOT/UnleashedRecompLib/private/default.xexp" +fi + +if [[ -n "${UPDATE_PATH:-}" ]]; then + cmake --preset macos-release + cmake --build "$HOST_BUILD_DIR" --target xcontent_extract -j "${JOBS:-8}" + "$HOST_BUILD_DIR/tools/xcontent_extract/xcontent_extract" "$UPDATE_PATH" \ + default.xexp "$ROOT/UnleashedRecompLib/private/default.xexp" +fi + missing=0 for file in \ "$ROOT/UnleashedRecompLib/private/default.xex" \ @@ -27,6 +48,9 @@ if [[ "$missing" -ne 0 ]]; then exit 1 fi +cmake --preset macos-release +cmake --build "$HOST_BUILD_DIR" --target XenonRecomp XenosRecomp x_decompress file_to_c -j "${JOBS:-8}" + cmake --preset ios-device-release cmake --build "$BUILD_DIR" --target UnleashedRecomp -j "${JOBS:-8}" diff --git a/tools/patches/plume-ios-sdl-vulkan.patch b/tools/patches/plume-ios-sdl-vulkan.patch index e2a6e17e..a5604ec8 100644 --- a/tools/patches/plume-ios-sdl-vulkan.patch +++ b/tools/patches/plume-ios-sdl-vulkan.patch @@ -1,5 +1,5 @@ diff --git a/CMakeLists.txt b/CMakeLists.txt -index 6a7645a..bf336b9 100644 +index 6a7645a..6e6e887 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,10 +8,16 @@ if(APPLE) @@ -20,6 +20,24 @@ index 6a7645a..bf336b9 100644 cmake_dependent_option(D3D12_AGILITY_SDK_ENABLED "Enable D3D12 Agility SDK" OFF WIN32 OFF) option(PLUME_BUILD_EXAMPLES "Build example applications" OFF) +@@ -50,7 +56,7 @@ set(PLUME_SOURCES + ) + + # Platform-specific files +-if(APPLE) ++if(APPLE AND NOT IS_IOS) + list(APPEND PLUME_SOURCES + plume_metal.cpp + plume_metal.h +@@ -94,7 +100,7 @@ if(D3D12_AGILITY_SDK_ENABLED) + endif() + + # Platform-specific includes +-if(APPLE) ++if(APPLE AND NOT IS_IOS) + target_include_directories(plume PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/contrib/metal-cpp + ) diff --git a/plume_vulkan.cpp b/plume_vulkan.cpp index 9103ca8..02c3591 100644 --- a/plume_vulkan.cpp diff --git a/tools/xcontent_extract/CMakeLists.txt b/tools/xcontent_extract/CMakeLists.txt new file mode 100644 index 00000000..230c17c9 --- /dev/null +++ b/tools/xcontent_extract/CMakeLists.txt @@ -0,0 +1,13 @@ +project("xcontent_extract") + +add_executable(xcontent_extract + "xcontent_extract.cpp" + "${CMAKE_SOURCE_DIR}/UnleashedRecomp/install/xcontent_file_system.cpp" +) + +target_include_directories(xcontent_extract PRIVATE + "${CMAKE_SOURCE_DIR}/UnleashedRecomp/install" + "${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp/XenonUtils" +) + +target_link_libraries(xcontent_extract PRIVATE XenonUtils) diff --git a/tools/xcontent_extract/xcontent_extract.cpp b/tools/xcontent_extract/xcontent_extract.cpp new file mode 100644 index 00000000..a532a030 --- /dev/null +++ b/tools/xcontent_extract/xcontent_extract.cpp @@ -0,0 +1,73 @@ +#include +#include +#include +#include +#include +#include + +#include + +static bool ExtractFile(const XContentFileSystem& content, const std::string& sourcePath, const std::filesystem::path& outputPath) +{ + size_t dataSize = content.getSize(sourcePath); + if (dataSize == 0) + { + std::cerr << "File not found in XContent package: " << sourcePath << "\n"; + return false; + } + + std::vector data(dataSize); + if (!content.load(sourcePath, data.data(), data.size())) + { + std::cerr << "Failed to read XContent file: " << sourcePath << "\n"; + return false; + } + + std::error_code ec; + std::filesystem::create_directories(outputPath.parent_path(), ec); + if (ec) + { + std::cerr << "Failed to create output directory: " << outputPath.parent_path() << "\n"; + return false; + } + + std::ofstream output(outputPath, std::ios::binary); + if (!output) + { + std::cerr << "Failed to open output file: " << outputPath << "\n"; + return false; + } + + output.write(reinterpret_cast(data.data()), static_cast(data.size())); + if (!output) + { + std::cerr << "Failed to write output file: " << outputPath << "\n"; + return false; + } + + return true; +} + +int main(int argc, char** argv) +{ + if (argc < 4 || ((argc - 2) % 2) != 0) + { + std::cerr << "Usage: xcontent_extract [ ...]\n"; + return 2; + } + + auto content = XContentFileSystem::create(argv[1]); + if (!content) + { + std::cerr << "Failed to open XContent package: " << argv[1] << "\n"; + return 1; + } + + bool ok = true; + for (int i = 2; i < argc; i += 2) + { + ok = ExtractFile(*content, argv[i], argv[i + 1]) && ok; + } + + return ok ? 0 : 1; +}