Skip to content

Commit ed6cd3c

Browse files
committed
Complete iOS IPA packaging flow
1 parent 4f9e6cf commit ed6cd3c

13 files changed

Lines changed: 295 additions & 23 deletions

File tree

CMakePresets.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@
177177
"CMAKE_OSX_SYSROOT": "iphonesimulator",
178178
"CMAKE_OSX_ARCHITECTURES": "arm64",
179179
"CMAKE_BUILD_TYPE": "Debug",
180+
"UNLEASHED_RECOMP_SKIP_TARGET_TOOL_EXECUTABLES": true,
181+
"UNLEASHED_RECOMP_HOST_TOOLS_DIR": "${sourceDir}/out/build/macos-release",
180182
"CMAKE_TOOLCHAIN_FILE": {
181183
"value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
182184
"type": "FILEPATH"
@@ -207,6 +209,8 @@
207209
"CMAKE_OSX_ARCHITECTURES": "arm64",
208210
"CMAKE_BUILD_TYPE": "Release",
209211
"CMAKE_INTERPROCEDURAL_OPTIMIZATION": true,
212+
"UNLEASHED_RECOMP_SKIP_TARGET_TOOL_EXECUTABLES": true,
213+
"UNLEASHED_RECOMP_HOST_TOOLS_DIR": "${sourceDir}/out/build/macos-release",
210214
"CMAKE_TOOLCHAIN_FILE": {
211215
"value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
212216
"type": "FILEPATH"

UnleashedRecomp/CMakeLists.txt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
1-
project("UnleashedRecomp")
1+
project("UnleashedRecomp")
2+
3+
set(UNLEASHED_RECOMP_HOST_TOOLS_DIR "" CACHE PATH "Directory containing host-built recompilation tools.")
4+
5+
function(unleashed_recomp_resolve_tool OUT_VAR TARGET_NAME RELATIVE_PATH)
6+
if (UNLEASHED_RECOMP_HOST_TOOLS_DIR)
7+
set(${OUT_VAR} "${UNLEASHED_RECOMP_HOST_TOOLS_DIR}/${RELATIVE_PATH}" PARENT_SCOPE)
8+
elseif (TARGET ${TARGET_NAME})
9+
set(${OUT_VAR} "$<TARGET_FILE:${TARGET_NAME}>" PARENT_SCOPE)
10+
else()
11+
message(FATAL_ERROR "Tool ${TARGET_NAME} is not available. Set UNLEASHED_RECOMP_HOST_TOOLS_DIR.")
12+
endif()
13+
endfunction()
14+
15+
unleashed_recomp_resolve_tool(UNLEASHED_RECOMP_FILE_TO_C_TOOL file_to_c "tools/file_to_c/file_to_c")
216

317
if (WIN32)
418
option(UNLEASHED_RECOMP_D3D12 "Add D3D12 support for rendering" ON)
@@ -27,8 +41,8 @@ function(BIN2C)
2741
set(BIN2C_ARGS_COMPRESSION_TYPE "none")
2842
endif()
2943

30-
add_custom_command(OUTPUT "${BIN2C_ARGS_DEST_FILE}.c"
31-
COMMAND $<TARGET_FILE:file_to_c> "${BIN2C_ARGS_SOURCE_FILE}" "${BIN2C_ARGS_ARRAY_NAME}" "${BIN2C_ARGS_COMPRESSION_TYPE}" "${BIN2C_ARGS_DEST_FILE}.c" "${BIN2C_ARGS_DEST_FILE}.h"
44+
add_custom_command(OUTPUT "${BIN2C_ARGS_DEST_FILE}.c"
45+
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"
3246
DEPENDS "${BIN2C_ARGS_SOURCE_FILE}"
3347
BYPRODUCTS "${BIN2C_ARGS_DEST_FILE}.h"
3448
COMMENT "Generating binary header for ${BIN2C_ARGS_SOURCE_FILE}..."

UnleashedRecomp/install/update_checker.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,11 @@ void UpdateChecker::visitWebsite()
166166
#elif defined(__linux__)
167167
std::string command = "xdg-open " + std::string(VISIT_URL) + " &";
168168
std::system(command.c_str());
169-
#elif defined(__APPLE__)
169+
#elif defined(__APPLE__) && !defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__)
170170
std::string command = "open " + std::string(VISIT_URL) + " &";
171171
std::system(command.c_str());
172+
#elif defined(__APPLE__)
173+
// iOS apps cannot spawn external commands; leave website navigation to platform UI.
172174
#else
173175
static_assert(false, "Visit website not implemented for this platform.");
174176
#endif

UnleashedRecomp/install/xcontent_file_system.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
#include "xcontent_file_system.h"
1414

1515
#include <bit>
16+
#include <fstream>
1617
#include <set>
1718
#include <stack>
19+
#include <xbox.h>
1820

1921
enum class XContentPackageType
2022
{

UnleashedRecompLib/CMakeLists.txt

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,27 @@ else()
1010
add_compile_options(-ffp-model=strict)
1111
endif()
1212

13-
target_compile_definitions(XenonRecomp PRIVATE
14-
XENON_RECOMP_CONFIG_FILE_PATH=\"${CMAKE_CURRENT_SOURCE_DIR}/config/SWA.toml\"
15-
XENON_RECOMP_HEADER_FILE_PATH=\"${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp/XenonUtils/ppc_context.h\")
13+
set(UNLEASHED_RECOMP_HOST_TOOLS_DIR "" CACHE PATH "Directory containing host-built recompilation tools.")
14+
15+
function(unleashed_recomp_resolve_tool OUT_VAR TARGET_NAME RELATIVE_PATH)
16+
if (UNLEASHED_RECOMP_HOST_TOOLS_DIR)
17+
set(${OUT_VAR} "${UNLEASHED_RECOMP_HOST_TOOLS_DIR}/${RELATIVE_PATH}" PARENT_SCOPE)
18+
elseif (TARGET ${TARGET_NAME})
19+
set(${OUT_VAR} "$<TARGET_FILE:${TARGET_NAME}>" PARENT_SCOPE)
20+
else()
21+
message(FATAL_ERROR "Tool ${TARGET_NAME} is not available. Set UNLEASHED_RECOMP_HOST_TOOLS_DIR.")
22+
endif()
23+
endfunction()
24+
25+
unleashed_recomp_resolve_tool(UNLEASHED_RECOMP_XENON_RECOMP_TOOL XenonRecomp "tools/XenonRecomp/XenonRecomp/XenonRecomp")
26+
unleashed_recomp_resolve_tool(UNLEASHED_RECOMP_X_DECOMPRESS_TOOL x_decompress "tools/x_decompress/x_decompress")
27+
unleashed_recomp_resolve_tool(UNLEASHED_RECOMP_XENOS_RECOMP_TOOL XenosRecomp "tools/XenosRecomp/XenosRecomp/XenosRecomp")
28+
29+
if (TARGET XenonRecomp)
30+
target_compile_definitions(XenonRecomp PRIVATE
31+
XENON_RECOMP_CONFIG_FILE_PATH=\"${CMAKE_CURRENT_SOURCE_DIR}/config/SWA.toml\"
32+
XENON_RECOMP_HEADER_FILE_PATH=\"${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp/XenonUtils/ppc_context.h\")
33+
endif()
1634
1735
set(UNLEASHED_RECOMP_PPC_RECOMPILED_SOURCES
1836
"${CMAKE_CURRENT_SOURCE_DIR}/ppc/ppc_config.h"
@@ -30,7 +48,7 @@ add_custom_command(
3048
"${CMAKE_CURRENT_SOURCE_DIR}/private/default_patched.xex"
3149
${UNLEASHED_RECOMP_PPC_RECOMPILED_SOURCES}
3250
COMMAND
33-
$<TARGET_FILE:XenonRecomp>
51+
"${UNLEASHED_RECOMP_XENON_RECOMP_TOOL}"
3452
DEPENDS
3553
"${CMAKE_CURRENT_SOURCE_DIR}/private/default.xex"
3654
"${CMAKE_CURRENT_SOURCE_DIR}/private/default.xexp"
@@ -42,20 +60,22 @@ add_custom_command(
4260
OUTPUT
4361
"${CMAKE_CURRENT_SOURCE_DIR}/private/shader_decompressed.ar"
4462
COMMAND
45-
$<TARGET_FILE:x_decompress> "${CMAKE_CURRENT_SOURCE_DIR}/private/shader.ar" "${CMAKE_CURRENT_SOURCE_DIR}/private/shader_decompressed.ar"
63+
"${UNLEASHED_RECOMP_X_DECOMPRESS_TOOL}" "${CMAKE_CURRENT_SOURCE_DIR}/private/shader.ar" "${CMAKE_CURRENT_SOURCE_DIR}/private/shader_decompressed.ar"
4664
DEPENDS
4765
"${CMAKE_CURRENT_SOURCE_DIR}/private/shader.ar"
4866
)
4967
5068
set(XENOS_RECOMP_ROOT "${UNLEASHED_RECOMP_TOOLS_ROOT}/XenosRecomp/XenosRecomp")
5169
set(XENOS_RECOMP_INCLUDE "${XENOS_RECOMP_ROOT}/shader_common.h")
5270
53-
target_compile_definitions(XenosRecomp PRIVATE
54-
XENOS_RECOMP_INPUT=\"${CMAKE_CURRENT_SOURCE_DIR}/private\"
55-
XENOS_RECOMP_OUTPUT=\"${CMAKE_CURRENT_SOURCE_DIR}/shader/shader_cache.cpp\"
56-
XENOS_RECOMP_INCLUDE_INPUT=\"${XENOS_RECOMP_INCLUDE}\"
57-
UNLEASHED_RECOMP
58-
)
71+
if (TARGET XenosRecomp)
72+
target_compile_definitions(XenosRecomp PRIVATE
73+
XENOS_RECOMP_INPUT=\"${CMAKE_CURRENT_SOURCE_DIR}/private\"
74+
XENOS_RECOMP_OUTPUT=\"${CMAKE_CURRENT_SOURCE_DIR}/shader/shader_cache.cpp\"
75+
XENOS_RECOMP_INCLUDE_INPUT=\"${XENOS_RECOMP_INCLUDE}\"
76+
UNLEASHED_RECOMP
77+
)
78+
endif()
5979
6080
file(GLOB XENOS_RECOMP_SOURCES
6181
"${XENOS_RECOMP_ROOT}/*.h"
@@ -66,7 +86,7 @@ add_custom_command(
6686
OUTPUT
6787
"${CMAKE_CURRENT_SOURCE_DIR}/shader/shader_cache.cpp"
6888
COMMAND
69-
$<TARGET_FILE:XenosRecomp>
89+
"${UNLEASHED_RECOMP_XENOS_RECOMP_TOOL}"
7090
DEPENDS
7191
"${CMAKE_CURRENT_SOURCE_DIR}/private/default_patched.xex"
7292
"${CMAKE_CURRENT_SOURCE_DIR}/private/shader_decompressed.ar"

thirdparty/nfd_ios_stub.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ static const char* g_nfd_ios_error = "Native file dialogs are not implemented fo
66
nfdresult_t NFD_Init(void) { return NFD_OKAY; }
77
void NFD_Quit(void) {}
88
const char* NFD_GetError(void) { return g_nfd_ios_error; }
9+
void NFD_ClearError(void) {}
910

1011
void NFD_FreePathN(nfdnchar_t* filePath) { free(filePath); }
1112
void NFD_FreePathU8(nfdu8char_t* filePath) { free(filePath); }
@@ -37,6 +38,6 @@ void NFD_PathSet_FreePathN(const nfdnchar_t* filePath) { free((void*)filePath);
3738
void NFD_PathSet_FreePathU8(const nfdu8char_t* filePath) { free((void*)filePath); }
3839
void NFD_PathSet_Free(const nfdpathset_t* pathSet) { (void)pathSet; }
3940
void NFD_PathSet_FreeEnum(nfdpathsetenum_t* enumerator) { (void)enumerator; }
40-
nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, nfdpathsetenum_t** outEnumerator) { (void)pathSet; (void)outEnumerator; return NFD_ERROR; }
41+
nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, nfdpathsetenum_t* outEnumerator) { (void)pathSet; (void)outEnumerator; return NFD_ERROR; }
4142
nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath) { (void)enumerator; (void)outPath; return NFD_ERROR; }
4243
nfdresult_t NFD_PathSet_EnumNextU8(nfdpathsetenum_t* enumerator, nfdu8char_t** outPath) { (void)enumerator; (void)outPath; return NFD_ERROR; }

tools/CMakeLists.txt

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/bc_diff)
2-
add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/file_to_c)
32
add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/fshasher)
4-
add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/x_decompress)
5-
add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp)
6-
add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/XenosRecomp)
3+
if (NOT UNLEASHED_RECOMP_SKIP_TARGET_TOOL_EXECUTABLES)
4+
add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/file_to_c)
5+
add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/x_decompress)
6+
add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp)
7+
add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/XenosRecomp)
8+
else()
9+
set(THIRDPARTY_ROOT ${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp/thirdparty)
10+
add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp/thirdparty)
11+
add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp/XenonUtils)
12+
13+
if (NOT TARGET libzstd)
14+
add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/XenosRecomp/thirdparty/zstd/build/cmake)
15+
endif()
16+
endif()
17+
if (NOT UNLEASHED_RECOMP_SKIP_TARGET_TOOL_EXECUTABLES)
18+
add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/iso_extract)
19+
add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}/xcontent_extract)
20+
endif()

tools/iso_extract/CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
project("iso_extract")
2+
3+
add_executable(iso_extract
4+
"iso_extract.cpp"
5+
"${CMAKE_SOURCE_DIR}/UnleashedRecomp/install/iso_file_system.cpp"
6+
)
7+
8+
target_include_directories(iso_extract PRIVATE
9+
"${CMAKE_SOURCE_DIR}/UnleashedRecomp/install"
10+
"${UNLEASHED_RECOMP_TOOLS_ROOT}/XenonRecomp/XenonUtils"
11+
)
12+
13+
target_link_libraries(iso_extract PRIVATE XenonUtils)

tools/iso_extract/iso_extract.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#include <cstdint>
2+
#include <filesystem>
3+
#include <fstream>
4+
#include <iostream>
5+
#include <string>
6+
#include <vector>
7+
8+
#include <iso_file_system.h>
9+
10+
static bool ExtractFile(const ISOFileSystem& iso, const std::string& sourcePath, const std::filesystem::path& outputPath)
11+
{
12+
std::vector<uint8_t> data;
13+
size_t dataSize = iso.getSize(sourcePath);
14+
if (dataSize == 0)
15+
{
16+
std::cerr << "File not found in ISO: " << sourcePath << "\n";
17+
return false;
18+
}
19+
20+
data.resize(dataSize);
21+
if (!iso.load(sourcePath, data.data(), data.size()))
22+
{
23+
std::cerr << "File not found in ISO: " << sourcePath << "\n";
24+
return false;
25+
}
26+
27+
std::error_code ec;
28+
std::filesystem::create_directories(outputPath.parent_path(), ec);
29+
if (ec)
30+
{
31+
std::cerr << "Failed to create output directory: " << outputPath.parent_path() << "\n";
32+
return false;
33+
}
34+
35+
std::ofstream output(outputPath, std::ios::binary);
36+
if (!output)
37+
{
38+
std::cerr << "Failed to open output file: " << outputPath << "\n";
39+
return false;
40+
}
41+
42+
output.write(reinterpret_cast<const char*>(data.data()), static_cast<std::streamsize>(data.size()));
43+
if (!output)
44+
{
45+
std::cerr << "Failed to write output file: " << outputPath << "\n";
46+
return false;
47+
}
48+
49+
return true;
50+
}
51+
52+
int main(int argc, char** argv)
53+
{
54+
if (argc < 4 || ((argc - 2) % 2) != 0)
55+
{
56+
std::cerr << "Usage: iso_extract <game.iso> <iso-path> <output-path> [<iso-path> <output-path> ...]\n";
57+
return 2;
58+
}
59+
60+
auto iso = ISOFileSystem::create(argv[1]);
61+
if (!iso)
62+
{
63+
std::cerr << "Failed to open Xbox 360 ISO: " << argv[1] << "\n";
64+
return 1;
65+
}
66+
67+
bool ok = true;
68+
for (int i = 2; i < argc; i += 2)
69+
{
70+
ok = ExtractFile(*iso, argv[i], argv[i + 1]) && ok;
71+
}
72+
73+
return ok ? 0 : 1;
74+
}

tools/package_ios_ipa.sh

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,35 @@ set -euo pipefail
33

44
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
55
BUILD_DIR="$ROOT/out/build/ios-device-release"
6+
HOST_BUILD_DIR="$ROOT/out/build/macos-release"
67
IPA_DIR="$ROOT/out/ipa"
78
PAYLOAD_DIR="$IPA_DIR/Payload"
8-
APP_PATH="$BUILD_DIR/Unleashed Recompiled.app"
9+
APP_PATH="$BUILD_DIR/UnleashedRecomp/Unleashed Recompiled.app"
910
IPA_PATH="$IPA_DIR/UnleashedRecompiled.ipa"
1011
DEFAULT_SIGNING_IDENTITY="Apple Development: aroblesalago@gmail.com (MK28YRUCCG)"
1112

1213
"$ROOT/tools/apply_ios_submodule_patches.sh"
1314

15+
if [[ -n "${ISO_PATH:-}" ]]; then
16+
cmake --preset macos-release
17+
cmake --build "$HOST_BUILD_DIR" --target iso_extract -j "${JOBS:-8}"
18+
"$HOST_BUILD_DIR/tools/iso_extract/iso_extract" "$ISO_PATH" \
19+
default.xex "$ROOT/UnleashedRecompLib/private/default.xex" \
20+
shader.ar "$ROOT/UnleashedRecompLib/private/shader.ar"
21+
fi
22+
23+
if [[ -n "${XEXP_PATH:-}" ]]; then
24+
mkdir -p "$ROOT/UnleashedRecompLib/private"
25+
cp "$XEXP_PATH" "$ROOT/UnleashedRecompLib/private/default.xexp"
26+
fi
27+
28+
if [[ -n "${UPDATE_PATH:-}" ]]; then
29+
cmake --preset macos-release
30+
cmake --build "$HOST_BUILD_DIR" --target xcontent_extract -j "${JOBS:-8}"
31+
"$HOST_BUILD_DIR/tools/xcontent_extract/xcontent_extract" "$UPDATE_PATH" \
32+
default.xexp "$ROOT/UnleashedRecompLib/private/default.xexp"
33+
fi
34+
1435
missing=0
1536
for file in \
1637
"$ROOT/UnleashedRecompLib/private/default.xex" \
@@ -27,6 +48,9 @@ if [[ "$missing" -ne 0 ]]; then
2748
exit 1
2849
fi
2950

51+
cmake --preset macos-release
52+
cmake --build "$HOST_BUILD_DIR" --target XenonRecomp XenosRecomp x_decompress file_to_c -j "${JOBS:-8}"
53+
3054
cmake --preset ios-device-release
3155
cmake --build "$BUILD_DIR" --target UnleashedRecomp -j "${JOBS:-8}"
3256

0 commit comments

Comments
 (0)