mirror of
https://github.com/xeghosted/OrbisForge.git
synced 2026-10-06 08:00:37 +02:00
A GoldHEN plugin is not a plain PRX. The loader enters the module at _init, which the GoldHEN SDK's crtprx.o forwards to module_start/module_stop. Built the stock way it links without complaint and then silently never loads on console, so add_orbis_target(TYPE GHPLUGIN) applies all three differences: crtprx.o instead of crtlib.o, -e _init, and SceLibcInternal/SceSysmodule instead of c/c++. It also checks for the SDK up front and fails with install instructions rather than producing an unloadable artifact. The scaffolded starter plugin shows a PS4 notification from module_start. It is emitted as C++ with extern "C" entry points -- generated projects are project(<name> CXX), and without that the mangled names leave crtprx.o's _init unresolved. Reachable as --type ghplugin (aliases: goldhen, plugin) and as a fourth entry in the GUI wizard. CI installs the SDK and builds one, since a plugin that links but cannot load is exactly the failure unit tests cannot catch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
258 lines
13 KiB
C#
258 lines
13 KiB
C#
namespace OrbisForge.Core.Templates;
|
|
|
|
internal static class OrbisTargetsTemplate
|
|
{
|
|
public const string OrbisTargetsCMake = """
|
|
# add_orbis_target() / add_orbis_package() - generated by OrbisForge.
|
|
#
|
|
# add_orbis_target(<name>
|
|
# TYPE EXE|PRX|SPRX|GHPLUGIN
|
|
# SOURCES <files...>
|
|
# [LIBS <names...>] # default: c kernel c++
|
|
# # (GHPLUGIN: SceLibcInternal kernel SceSysmodule GoldHEN_Hook)
|
|
# [PTYPE <ptype>] # default: "fake", or "system_dynlib" for SPRX
|
|
# [PAID <hex>] # default: 0x3800000000000011
|
|
# [LIBNAME <name>] # library types only, passed to create-fself -libname
|
|
# )
|
|
#
|
|
# Links <name> as a PS4 ELF via the OpenOrbis toolchain, then runs
|
|
# create-fself as a POST_BUILD step to produce the final artifact:
|
|
# EXE -> eboot.bin
|
|
# PRX -> <name>.prx
|
|
# SPRX -> <name>.sprx
|
|
# GHPLUGIN -> <name>.prx
|
|
#
|
|
# GHPLUGIN builds a GoldHEN plugin. It differs from PRX in three ways that
|
|
# all have to be right or the plugin builds cleanly and never loads: it
|
|
# links the GoldHEN SDK's crtprx.o instead of crtlib.o, enters at _init,
|
|
# and links SceLibcInternal/SceSysmodule rather than c/c++. Requires the
|
|
# GoldHEN Plugin SDK installed into the toolchain. Entry points are
|
|
# module_start/module_stop, which must have C linkage.
|
|
# readoelf then runs over the packaged .oelf as a build-time sanity check.
|
|
# A plain, unsigned <name>.elf (copy of that same validated .oelf) is
|
|
# produced alongside it -- raw-ELF loader tools inspect a file's magic
|
|
# bytes rather than its extension, so they can't load eboot.bin/*.prx/
|
|
# *.sprx (which are SELF-wrapped, not plain ELF) no matter what it's
|
|
# renamed to; point those tools at the .elf instead.
|
|
|
|
function(add_orbis_target name)
|
|
set(options "")
|
|
set(oneValueArgs TYPE PTYPE PAID LIBNAME)
|
|
set(multiValueArgs SOURCES LIBS)
|
|
cmake_parse_arguments(ORBIS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
|
|
|
if(NOT ORBIS_SOURCES)
|
|
message(FATAL_ERROR "add_orbis_target(${name}): SOURCES is required")
|
|
endif()
|
|
|
|
if(NOT ORBIS_TYPE)
|
|
set(ORBIS_TYPE "EXE")
|
|
endif()
|
|
string(TOUPPER "${ORBIS_TYPE}" ORBIS_TYPE)
|
|
|
|
add_executable(${name} ${ORBIS_SOURCES})
|
|
|
|
set(_default_libs c kernel c++)
|
|
set(_entry_args "")
|
|
|
|
if(ORBIS_TYPE STREQUAL "EXE")
|
|
set(_crt_object "${OO_PS4_TOOLCHAIN}/lib/crt1.o")
|
|
set(_default_ptype "fake")
|
|
set(_out_name "eboot.bin")
|
|
set(_pkg_flag "--eboot")
|
|
elseif(ORBIS_TYPE STREQUAL "PRX")
|
|
set(_crt_object "${OO_PS4_TOOLCHAIN}/lib/crtlib.o")
|
|
set(_default_ptype "fake")
|
|
set(_out_name "${name}.prx")
|
|
set(_pkg_flag "--lib")
|
|
elseif(ORBIS_TYPE STREQUAL "SPRX")
|
|
set(_crt_object "${OO_PS4_TOOLCHAIN}/lib/crtlib.o")
|
|
set(_default_ptype "system_dynlib")
|
|
set(_out_name "${name}.sprx")
|
|
set(_pkg_flag "--lib")
|
|
elseif(ORBIS_TYPE STREQUAL "GHPLUGIN")
|
|
# GoldHEN enters a plugin at _init, which the SDK's crtprx.o forwards
|
|
# to module_start/module_stop. Built with the stock crtlib.o instead,
|
|
# a plugin links without complaint and then never loads on console --
|
|
# so the SDK is checked for here rather than failing silently later.
|
|
set(_crt_object "${OO_PS4_TOOLCHAIN}/lib/crtprx.o")
|
|
if(NOT EXISTS "${_crt_object}")
|
|
message(FATAL_ERROR
|
|
"add_orbis_target(${name}): TYPE GHPLUGIN needs the GoldHEN Plugin SDK "
|
|
"installed into the toolchain, but ${_crt_object} is missing. "
|
|
"Build the SDK (build_static.bat on Windows, make on Linux), then copy "
|
|
"crtprx.o and libGoldHEN_Hook.a into ${OO_PS4_TOOLCHAIN}/lib and the SDK's "
|
|
"include/* into ${OO_PS4_TOOLCHAIN}/include/GoldHEN.")
|
|
endif()
|
|
set(_default_ptype "fake")
|
|
set(_out_name "${name}.prx")
|
|
set(_pkg_flag "--lib")
|
|
set(_default_libs SceLibcInternal kernel SceSysmodule GoldHEN_Hook)
|
|
set(_entry_args "LINKER:-e,_init")
|
|
else()
|
|
message(FATAL_ERROR "add_orbis_target(${name}): TYPE must be EXE, PRX, SPRX, or GHPLUGIN (got '${ORBIS_TYPE}')")
|
|
endif()
|
|
|
|
if(NOT ORBIS_LIBS)
|
|
set(ORBIS_LIBS ${_default_libs})
|
|
endif()
|
|
|
|
if(_entry_args)
|
|
target_link_options(${name} PRIVATE "${_entry_args}")
|
|
endif()
|
|
|
|
# Order matters here and mirrors the toolchain's own sample Makefiles:
|
|
# object files, then -l libs, then the CRT object last, so crt1.o's own
|
|
# references into libkernel resolve against a linker that has already
|
|
# seen -lkernel.
|
|
target_link_libraries(${name} PRIVATE ${ORBIS_LIBS} "${_crt_object}")
|
|
|
|
if(ORBIS_PTYPE)
|
|
set(_ptype "${ORBIS_PTYPE}")
|
|
else()
|
|
set(_ptype "${_default_ptype}")
|
|
endif()
|
|
|
|
if(NOT ORBIS_PAID)
|
|
set(ORBIS_PAID "0x3800000000000011")
|
|
endif()
|
|
|
|
set(_libname_args "")
|
|
if(ORBIS_LIBNAME)
|
|
set(_libname_args -libname "${ORBIS_LIBNAME}")
|
|
endif()
|
|
|
|
set(_out_path "${CMAKE_CURRENT_BINARY_DIR}/${_out_name}")
|
|
set(_elf_path "${CMAKE_CURRENT_BINARY_DIR}/${name}.elf")
|
|
|
|
add_custom_command(TARGET ${name} POST_BUILD
|
|
COMMAND "${OO_PS4_TOOLCHAIN_BIN}/create-fself"
|
|
-in=$<TARGET_FILE:${name}>
|
|
-out=$<TARGET_FILE:${name}>.oelf
|
|
${_pkg_flag} "${_out_path}"
|
|
-ptype ${_ptype}
|
|
--paid ${ORBIS_PAID}
|
|
${_libname_args}
|
|
COMMAND "${OO_PS4_TOOLCHAIN_BIN}/readoelf" "$<TARGET_FILE:${name}>.oelf"
|
|
COMMAND "${CMAKE_COMMAND}" -E copy "$<TARGET_FILE:${name}>.oelf" "${_elf_path}"
|
|
BYPRODUCTS "${_out_path}" "${_elf_path}"
|
|
COMMENT "orbis: packaging ${name} -> ${_out_name} + ${name}.elf (type=${ORBIS_TYPE}, ptype=${_ptype})"
|
|
VERBATIM
|
|
)
|
|
|
|
set_target_properties(${name} PROPERTIES
|
|
ORBIS_OUTPUT_PATH "${_out_path}"
|
|
ORBIS_OUTPUT_NAME "${_out_name}"
|
|
ORBIS_TYPE "${ORBIS_TYPE}"
|
|
ORBIS_ELF_PATH "${_elf_path}"
|
|
)
|
|
endfunction()
|
|
|
|
# add_orbis_package(<target>
|
|
# TITLE_ID <id> CONTENT_ID <id> TITLE <string> [VERSION <string>]
|
|
# [ASSETS_DIR <dir>] [SCE_MODULE_DIR <dir>]
|
|
# )
|
|
#
|
|
# Builds a full retail-style .pkg installer from an EXE target created by
|
|
# add_orbis_target(): param.sfo -> pkg.gp4 -> <content-id>.pkg, exactly
|
|
# mirroring the toolchain's own sample Makefiles. Adds a "<target>_pkg"
|
|
# target you build explicitly (it is not part of the default `all`).
|
|
function(add_orbis_package target)
|
|
set(oneValueArgs TITLE_ID CONTENT_ID TITLE VERSION ASSETS_DIR SCE_MODULE_DIR)
|
|
cmake_parse_arguments(PKG "" "${oneValueArgs}" "" ${ARGN})
|
|
|
|
if(NOT PKG_TITLE_ID OR NOT PKG_CONTENT_ID OR NOT PKG_TITLE)
|
|
message(FATAL_ERROR "add_orbis_package(${target}): TITLE_ID, CONTENT_ID, and TITLE are required")
|
|
endif()
|
|
if(NOT PKG_VERSION)
|
|
set(PKG_VERSION "1.00")
|
|
endif()
|
|
if(NOT PKG_ASSETS_DIR)
|
|
set(PKG_ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets")
|
|
endif()
|
|
if(NOT PKG_SCE_MODULE_DIR)
|
|
set(PKG_SCE_MODULE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/sce_module")
|
|
endif()
|
|
|
|
get_target_property(_eboot ${target} ORBIS_OUTPUT_PATH)
|
|
if(NOT _eboot)
|
|
message(FATAL_ERROR "add_orbis_package(${target}): target has no ORBIS_OUTPUT_PATH; call add_orbis_target(${target} TYPE EXE ...) first")
|
|
endif()
|
|
|
|
set(_sce_sys "${CMAKE_CURRENT_SOURCE_DIR}/sce_sys")
|
|
|
|
# create-gp4's --files list has no separate source-vs-target-path syntax:
|
|
# each entry is a path relative to the working directory it runs from,
|
|
# and that SAME relative path becomes the file's location inside the
|
|
# package. eboot.bin and param.sfo are built elsewhere (under
|
|
# CMAKE_CURRENT_BINARY_DIR), so they need to be staged into a directory
|
|
# tree that already looks like the final package layout before create-gp4
|
|
# runs from it -- otherwise the package ends up with eboot.bin nested
|
|
# under a stray "build/" path instead of at its root, where the PS4
|
|
# loader requires it.
|
|
set(_stage "${CMAKE_CURRENT_BINARY_DIR}/pkg_root")
|
|
set(_sfo "${_stage}/sce_sys/param.sfo")
|
|
set(_gp4 "${_stage}/pkg.gp4")
|
|
# PkgTool.Core's pkg_build takes the gp4 path plus a "project directory"
|
|
# it resolves the gp4's relative file entries against AND writes the
|
|
# final .pkg into -- that has to be the same staging directory too.
|
|
set(_pkg_out "${_stage}/${PKG_CONTENT_ID}.pkg")
|
|
|
|
add_custom_command(
|
|
OUTPUT "${_sfo}"
|
|
COMMAND "${CMAKE_COMMAND}" -E make_directory "${_stage}/sce_sys/about" "${_stage}/sce_module" "${_stage}/assets"
|
|
COMMAND "${CMAKE_COMMAND}" -E copy "${_eboot}" "${_stage}/eboot.bin"
|
|
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${_sce_sys}/icon0.png" "${_stage}/sce_sys/icon0.png"
|
|
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${_sce_sys}/about/right.sprx" "${_stage}/sce_sys/about/right.sprx"
|
|
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${PKG_SCE_MODULE_DIR}" "${_stage}/sce_module"
|
|
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${PKG_ASSETS_DIR}" "${_stage}/assets"
|
|
COMMAND "${OO_PS4_TOOLCHAIN_BIN}/PkgTool.Core" sfo_new "${_sfo}"
|
|
COMMAND "${OO_PS4_TOOLCHAIN_BIN}/PkgTool.Core" sfo_setentry "${_sfo}" APP_TYPE --type Integer --maxsize 4 --value 1
|
|
COMMAND "${OO_PS4_TOOLCHAIN_BIN}/PkgTool.Core" sfo_setentry "${_sfo}" APP_VER --type Utf8 --maxsize 8 --value "${PKG_VERSION}"
|
|
COMMAND "${OO_PS4_TOOLCHAIN_BIN}/PkgTool.Core" sfo_setentry "${_sfo}" ATTRIBUTE --type Integer --maxsize 4 --value 0
|
|
COMMAND "${OO_PS4_TOOLCHAIN_BIN}/PkgTool.Core" sfo_setentry "${_sfo}" CATEGORY --type Utf8 --maxsize 4 --value gd
|
|
COMMAND "${OO_PS4_TOOLCHAIN_BIN}/PkgTool.Core" sfo_setentry "${_sfo}" CONTENT_ID --type Utf8 --maxsize 48 --value "${PKG_CONTENT_ID}"
|
|
COMMAND "${OO_PS4_TOOLCHAIN_BIN}/PkgTool.Core" sfo_setentry "${_sfo}" DOWNLOAD_DATA_SIZE --type Integer --maxsize 4 --value 0
|
|
COMMAND "${OO_PS4_TOOLCHAIN_BIN}/PkgTool.Core" sfo_setentry "${_sfo}" SYSTEM_VER --type Integer --maxsize 4 --value 0
|
|
COMMAND "${OO_PS4_TOOLCHAIN_BIN}/PkgTool.Core" sfo_setentry "${_sfo}" TITLE --type Utf8 --maxsize 128 --value "${PKG_TITLE}"
|
|
COMMAND "${OO_PS4_TOOLCHAIN_BIN}/PkgTool.Core" sfo_setentry "${_sfo}" TITLE_ID --type Utf8 --maxsize 12 --value "${PKG_TITLE_ID}"
|
|
COMMAND "${OO_PS4_TOOLCHAIN_BIN}/PkgTool.Core" sfo_setentry "${_sfo}" VERSION --type Utf8 --maxsize 8 --value "${PKG_VERSION}"
|
|
DEPENDS "${target}"
|
|
COMMENT "orbis: staging package contents + generating param.sfo for ${target}"
|
|
VERBATIM
|
|
)
|
|
|
|
file(GLOB_RECURSE _assets RELATIVE "${PKG_ASSETS_DIR}" CONFIGURE_DEPENDS "${PKG_ASSETS_DIR}/*")
|
|
file(GLOB _modules RELATIVE "${PKG_SCE_MODULE_DIR}" CONFIGURE_DEPENDS "${PKG_SCE_MODULE_DIR}/*")
|
|
list(TRANSFORM _assets PREPEND "assets/")
|
|
list(TRANSFORM _modules PREPEND "sce_module/")
|
|
set(_all_files eboot.bin sce_sys/about/right.sprx sce_sys/param.sfo sce_sys/icon0.png ${_modules} ${_assets})
|
|
list(JOIN _all_files " " _files_arg)
|
|
|
|
add_custom_command(
|
|
OUTPUT "${_gp4}"
|
|
COMMAND "${OO_PS4_TOOLCHAIN_BIN}/create-gp4"
|
|
-out "${_gp4}"
|
|
--content-id=${PKG_CONTENT_ID}
|
|
--files "${_files_arg}"
|
|
WORKING_DIRECTORY "${_stage}"
|
|
DEPENDS "${_sfo}"
|
|
COMMENT "orbis: generating pkg.gp4 for ${target}"
|
|
VERBATIM
|
|
)
|
|
|
|
add_custom_command(
|
|
OUTPUT "${_pkg_out}"
|
|
COMMAND "${OO_PS4_TOOLCHAIN_BIN}/PkgTool.Core" pkg_build "${_gp4}" "${_stage}"
|
|
WORKING_DIRECTORY "${_stage}"
|
|
DEPENDS "${_gp4}"
|
|
COMMENT "orbis: building ${PKG_CONTENT_ID}.pkg"
|
|
VERBATIM
|
|
)
|
|
|
|
add_custom_target(${target}_pkg DEPENDS "${_pkg_out}")
|
|
set_target_properties(${target}_pkg PROPERTIES ORBIS_PKG_PATH "${_pkg_out}")
|
|
endfunction()
|
|
""";
|
|
}
|