mirror of
https://github.com/kamb205/KB-RPCS3.git
synced 2026-10-23 19:01:10 +02:00
183 lines
9.8 KiB
Bash
Executable File
183 lines
9.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# PS5 Vulkan Template - link the title and package its folder.
|
|
#
|
|
# link-title.sh BUILD_DIR PS5_VULKAN_DIR SDK OBJECT...
|
|
#
|
|
# Run by the build (ps5/CMakeLists.txt) once the objects are compiled. The link
|
|
# is PS5_Vulkan's: its RADV release archive (RADV, ACO, NIR and Mesa's runtime,
|
|
# built from PS5_Mesa), its link recipe (tools/radv-link.sh: the platform layer
|
|
# of this title's SDK pin, the heap and thread wraps, the libc names bound to ps5_*),
|
|
# its CRT, its native tool (ELF -> the console's fake SELF) and its libc.prx,
|
|
# as its CTS title links them. The title folder, dist/<TITLE_ID>/, gets the
|
|
# signed eboot.bin, sce_sys/, the shaders of the samples linked in and the
|
|
# assets (tools/build-assets.py).
|
|
#
|
|
# Copyright (C) 2026 Mihawk
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
set -euo pipefail
|
|
[[ -n ${LINK_TRACE:-} ]] && set -x
|
|
|
|
work=$1 vulkan=$2 sdk=$3
|
|
shift 3
|
|
objects=("$@")
|
|
ps5=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
|
root=$(dirname "$ps5")
|
|
archive=${RADV_ARCHIVE:-$vulkan/.deps/native/radv-release/lib/libvulkan_radeon.ps5.a}
|
|
export PS5_CLANG=${PS5_CLANG:-$(command -v clang || true)}
|
|
tool="$vulkan/build/host/ps5-native-tool"
|
|
native="$vulkan/tooling/native"
|
|
param="$ps5/sce_sys/param.json"
|
|
|
|
for file in "$archive" "$tool" "$param" "$sdk/bin/prospero-lld" "$vulkan/tools/radv-link.sh" \
|
|
"$vulkan/runtime/libc.prx" "$work/samples.txt"; do
|
|
[[ -e $file ]] || { echo "missing: $file" >&2; exit 2; }
|
|
done
|
|
mkdir -p "$work/link/obj" "$work/link/stubs"
|
|
cc() { PS5_PAYLOAD_SDK="$sdk" sh "$vulkan/tooling/prospero-clang18" "$@"; }
|
|
|
|
# The CRT. The C++ runtime is libc++abi's (the samples throw and catch), as in
|
|
# PS5_Vulkan's CTS title.
|
|
cc -std=c++20 -O2 -fno-exceptions -fno-rtti -c "$native/app_crt.cpp" -o "$work/link/obj/app_crt.o"
|
|
# RADV calls AGC, which the SDK has no stubs for: these name its imports.
|
|
stub() {
|
|
local library=$1 source=$2
|
|
cc -std=c11 -O2 -fPIC -c "$vulkan/$source" -o "$work/link/obj/${library}_stub.o"
|
|
"$sdk/bin/prospero-lld" --shared -soname "${library}.prx" \
|
|
-o "$work/link/stubs/${library}.so" "$work/link/obj/${library}_stub.o"
|
|
}
|
|
stub libSceAgc vendor/ps5/sdk/stubs/agc_canary_link_stub.c
|
|
stub libSceAgcDriver vendor/ps5/sdk/stubs/agc_driver_canary_link_stub.c
|
|
|
|
# shellcheck source=/dev/null
|
|
source "$vulkan/tools/radv-link.sh"
|
|
radv_link_recipe "$vulkan" "$sdk" "$archive" || exit 2
|
|
# localeconv in the C locale, '.' the decimal point (the console's gives none, and
|
|
# tinygltf's JSON parser then read 0.62 as 0): the platform layer's since SDK fork
|
|
# fa69d00 (tools/setup-sdk.sh's pin is never older), bound by PS5_Vulkan's recipe
|
|
# since its 6a6dfa6. With an older recipe the title binds it here, kept local as
|
|
# the recipe keeps its bound names. The symbol listing is read whole before it
|
|
# is searched: piped into grep -q under pipefail, llvm-nm's SIGPIPE made the
|
|
# found symbol a miss about half the time once the archive grew (SDK adc8dd7).
|
|
platform_has_localeconv=false
|
|
grep -q " T ps5_localeconv$" <<<"$("$sdk/bin/llvm-nm" --defined-only "$sdk/target/lib/libps5platform.a" 2>/dev/null)" &&
|
|
platform_has_localeconv=true
|
|
if $platform_has_localeconv && [[ " ${radv_link_flags[*]} " != *" --defsym=localeconv=ps5_localeconv "* ]]; then
|
|
printf '{\n local:\n localeconv;\n};\n' > "$work/link/localeconv-local.map"
|
|
radv_link_flags+=(--defsym=localeconv=ps5_localeconv --version-script "$work/link/localeconv-local.map")
|
|
fi
|
|
# RPCS3 PS5: the environment lives in the title (src/ps5_env.c): the console leaves a
|
|
# title's getenv import pointing at nothing, and RPCS3 reads it from static constructors.
|
|
# Bound like the recipe's names, and kept local for the same reason (no exports).
|
|
# clock_gettime / sched_yield / pthread_yield: src/ps5_fastclock.c (no system call: ~1 us and ~28 us each on the
|
|
# console, and RPCS3's wait loops call them constantly)
|
|
rpcs3_local=(getenv setenv unsetenv putenv cpuset_getaffinity pathconf dlopen dlsym dlerror dlclose
|
|
clock_gettime sched_yield pthread_yield)
|
|
for name in "${rpcs3_local[@]}"; do
|
|
radv_link_flags+=("--defsym=$name=rpcs3ps5_$name")
|
|
done
|
|
# ... and the platform layer's drop-ins (ps5platform/libc.h) for the libc functions RPCS3
|
|
# imports that a title cannot use as the console gives them: getcwd faults, statfs and
|
|
# sysconf answer wrongly for a title, pthread_exit must run thread_local destructors first,
|
|
# the process functions must fail cleanly.
|
|
# Not pthread_exit or sysconf: ps5_pthread_exit and ps5_sysconf call the system's own, and
|
|
# binding the name to them made each call itself until the stack ran out (console run 4).
|
|
# Any name whose ps5_ function references it is refused here for the same reason.
|
|
platform_objects=$(mktemp -d)
|
|
(cd "$platform_objects" && "$sdk/bin/llvm-ar" x "$sdk/target/lib/libps5platform.a")
|
|
for name in fchown fork fstatfs gai_strerror getcwd getnameinfo isatty link mkstemp \
|
|
pthread_getaffinity_np pthread_setaffinity_np readlink realpath setsid statfs symlink \
|
|
umask wait4; do
|
|
for object in "$platform_objects"/*.o; do
|
|
if grep -q " T ps5_$name$" <<<"$("$sdk/bin/llvm-nm" --defined-only "$object" 2>/dev/null)"; then
|
|
if grep -qw "$name" <<<"$("$sdk/bin/llvm-nm" -u "$object" 2>/dev/null)"; then
|
|
echo "link-title.sh: ps5_$name calls $name itself; it cannot be bound in its place" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
done
|
|
radv_link_flags+=("--defsym=$name=ps5_$name")
|
|
rpcs3_local+=("$name")
|
|
done
|
|
rm -rf "$platform_objects"
|
|
{ printf '{\n local:\n'; printf ' %s;\n' "${rpcs3_local[@]}"; printf '};\n'; } > "$work/link/rpcs3-local.map"
|
|
radv_link_flags+=(--version-script "$work/link/rpcs3-local.map")
|
|
|
|
# RPCS3 PS5: the emulator's static archives (its frontend, rpcs3_emu, LLVM, FFmpeg and the
|
|
# rest), one path per line in the file RPCS3_LINK_INPUTS names; linked as one group
|
|
rpcs3_inputs=()
|
|
if [[ -n ${RPCS3_LINK_INPUTS:-} && -f $RPCS3_LINK_INPUTS ]]; then
|
|
mapfile -t rpcs3_archives < "$RPCS3_LINK_INPUTS"
|
|
rpcs3_inputs=(--start-group "${rpcs3_archives[@]}" --end-group)
|
|
fi
|
|
"$sdk/bin/prospero-lld" --error-limit=0 "${radv_linker_script[@]}" --eh-frame-hdr "${radv_link_flags[@]}" \
|
|
--version-script "$native/app-symbols.map" --exclude-libs=ALL \
|
|
-e _start -o "$work/link/llvm-pie.elf" \
|
|
"$work/link/obj/app_crt.o" "${objects[@]}" "${rpcs3_inputs[@]}" \
|
|
"$work/link/stubs/libSceAgc.so" "$work/link/stubs/libSceAgcDriver.so" \
|
|
"${radv_link_inputs[@]}" \
|
|
--as-needed "$sdk"/target/lib/*.so
|
|
# A title whose localeconv is still the console's reads every glTF fraction as 0
|
|
# (black materials, models scaled to nothing): refuse it here, not on the console
|
|
if $platform_has_localeconv &&
|
|
grep -q " U localeconv$" <<<"$("$sdk/bin/llvm-nm" "$work/link/llvm-pie.elf" 2>/dev/null)"; then
|
|
echo "link-title.sh: localeconv is not bound to the platform layer's ps5_localeconv" >&2
|
|
exit 1
|
|
fi
|
|
"$tool" link --in "$work/link/llvm-pie.elf" --out "$work/eboot.elf.new" \
|
|
--stub-dir "$sdk/target/lib" --stub "$work/link/stubs/libSceAgc.so" \
|
|
--stub "$work/link/stubs/libSceAgcDriver.so" --module-sdk 0x02000009 \
|
|
--companion-sdk 0x08050001 --file-name eboot.elf
|
|
|
|
# The title folder
|
|
title_id=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["titleId"])' "$param")
|
|
app="$root/dist/$title_id"
|
|
mkdir -p "$app/sce_sys" "$app/sce_module" "$app/shaders/glsl"
|
|
"$tool" self --sign --in "$work/eboot.elf.new" --out "$app/eboot.bin" --magic 0x1D3D154F
|
|
"$tool" self --inspect --file "$app/eboot.bin" > /dev/null
|
|
cp "$param" "$app/sce_sys/param.json"
|
|
for asset in icon0.png pic0.dds pic1.dds snd0.at9; do
|
|
[[ -f $ps5/sce_sys/$asset ]] && cp "$ps5/sce_sys/$asset" "$app/sce_sys/$asset"
|
|
done
|
|
(cd "$vulkan/runtime" && sha256sum --check --strict --quiet libc.prx.sha256)
|
|
cp "$vulkan/runtime/libc.prx" "$app/sce_module/libc.prx"
|
|
# The precompiled SPIR-V of the base class and of each sample linked in
|
|
# (CMake writes the list without a final newline: read then reports end of file).
|
|
# A sample that draws only with the UI kit has none: the kit carries its own.
|
|
IFS=';' read -r -a samples < "$work/samples.txt" || (( ${#samples[@]} ))
|
|
rm -rf "$app/shaders/glsl"
|
|
mkdir -p "$app/shaders/glsl"
|
|
for dir in base "${samples[@]}"; do
|
|
compgen -G "$root/shaders/glsl/$dir/*.spv" > /dev/null || continue
|
|
mkdir -p "$app/shaders/glsl/$dir"
|
|
cp "$root/shaders/glsl/$dir/"*.spv "$app/shaders/glsl/$dir/"
|
|
done
|
|
mv "$work/eboot.elf.new" "$work/eboot.elf"
|
|
# RPCS3 PS5: the title's code is execute-only on the console, and RPCS3's access violation handler
|
|
# reads the instruction that faulted; it reads it from this copy of the executable segment instead
|
|
# (rpcs3/ps5/ps5_text_mirror.cpp in the RPCS3 tree): "RPCS3PS5TEXT", link address, size, bytes.
|
|
if [[ -n ${RPCS3_LINK_INPUTS:-} ]]; then
|
|
python3 - "$work/eboot.elf" "$app/rpcs3ps5-text.bin" <<'PY'
|
|
import struct, sys
|
|
elf = open(sys.argv[1], "rb").read()
|
|
assert elf[:4] == b"\x7fELF"
|
|
phoff, = struct.unpack_from("<Q", elf, 0x20)
|
|
phentsize, phnum = struct.unpack_from("<HH", elf, 0x36)
|
|
for i in range(phnum):
|
|
p_type, p_flags, p_offset, p_vaddr, _, p_filesz, _, _ = struct.unpack_from("<IIQQQQQQ", elf, phoff + i * phentsize)
|
|
if p_type == 1 and p_flags & 1: # the first executable PT_LOAD
|
|
with open(sys.argv[2], "wb") as out:
|
|
out.write(b"RPCS3PS5TEXT" + struct.pack("<QQ", p_vaddr, p_filesz))
|
|
out.write(elf[p_offset:p_offset + p_filesz])
|
|
print("==> code copy: vaddr %#x, %d bytes" % (p_vaddr, p_filesz))
|
|
break
|
|
else:
|
|
sys.exit("link-title.sh: no executable segment in eboot.elf")
|
|
PY
|
|
fi
|
|
archive_revision=$(sed -n 's/^revision: //p' "$(dirname "$(dirname "$archive")")/PROVENANCE.txt" 2>/dev/null || true)
|
|
printf '==> %s: %s (eboot.bin %s bytes; %d samples; RADV %s)\n' "$title_id" "$app" \
|
|
"$(stat -c %s "$app/eboot.bin")" "${#samples[@]}" "${archive_revision:0:11}"
|
|
# The notices beside eboot.bin: LEGAL.txt and licenses/, each part with its revision
|
|
python3 "$ps5/tools/stage-notices.py" "$app" "$vulkan" "$archive"
|