mirror of
https://github.com/xeghosted/OrbisForge.git
synced 2026-10-17 13:27:01 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6859cb8c8 | ||
|
|
6da25b6e69 | ||
|
|
d863dd462d | ||
|
|
abd288c882 | ||
|
|
895858e650 |
No files matched your search
@@ -41,6 +41,10 @@ jobs:
|
||||
type: sprx
|
||||
extra_args: ""
|
||||
artifact: build/ci_sprx.sprx
|
||||
- name: ci_ghplugin
|
||||
type: ghplugin
|
||||
extra_args: ""
|
||||
artifact: build/ci_ghplugin.prx
|
||||
- name: ci_pkg
|
||||
type: exe
|
||||
extra_args: "--pkg"
|
||||
@@ -86,6 +90,19 @@ jobs:
|
||||
echo "OO_PS4_TOOLCHAIN=$HOME/OpenOrbis-PS4-Toolchain" >> "$GITHUB_ENV"
|
||||
echo "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install GoldHEN Plugin SDK
|
||||
if: matrix.type == 'ghplugin'
|
||||
run: |
|
||||
set -e
|
||||
# The SDK's Makefile archives with llvm-ar, which the clang package does
|
||||
# not pull in on the runner image.
|
||||
sudo apt-get install -y llvm
|
||||
git clone --depth 1 https://github.com/GoldHEN/GoldHEN_Plugins_SDK.git /tmp/goldhen-sdk
|
||||
make -C /tmp/goldhen-sdk install
|
||||
# 'make install' ships the headers and libGoldHEN_Hook.a but leaves crtprx.o
|
||||
# behind in the SDK's own build dir; TYPE GHPLUGIN looks for it in lib/.
|
||||
cp /tmp/goldhen-sdk/build/crtprx.o "$OO_PS4_TOOLCHAIN/lib/"
|
||||
|
||||
- name: Build OrbisForge CLI
|
||||
run: dotnet publish src/OrbisForge.Cli -c Release -r linux-x64 --self-contained -p:PublishSingleFile=true -o publish/linux-x64
|
||||
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
|
||||
OrbisForge generates self-contained CMake projects for the [OpenOrbis PS4
|
||||
Toolchain](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain), producing `eboot.bin`
|
||||
executables, `.prx` user libraries, `.sprx` signed system libraries, and (optionally) full
|
||||
signed/encrypted `.pkg` installers — through a single `add_orbis_target()` CMake function.
|
||||
executables, `.prx` user libraries, `.sprx` signed system libraries, GoldHEN plugins, and
|
||||
(optionally) full signed/encrypted `.pkg` installers — through a single `add_orbis_target()`
|
||||
CMake function.
|
||||
A Windows GUI wizard and a cross-platform CLI both scaffold the same project layout.
|
||||
|
||||
> The GUI wizard is Windows-only, built on [UIFramework](https://github.com/xeghosted/UIFramework)
|
||||
@@ -19,9 +20,17 @@ A Windows GUI wizard and a cross-platform CLI both scaffold the same project lay
|
||||
|
||||
## Features
|
||||
|
||||
- **All three artifact types** — EXE (`eboot.bin`), PRX, and SPRX, via one `add_orbis_target()`
|
||||
call. `TYPE` selects the right CRT object (`crt1.o` vs `crtlib.o`), the right `create-fself`
|
||||
packaging mode (`--eboot` vs `--lib`), and a sensible default `-ptype`.
|
||||
- **Every artifact type** — EXE (`eboot.bin`), PRX, SPRX, and GHPLUGIN, via one
|
||||
`add_orbis_target()` call. `TYPE` selects the right CRT object (`crt1.o` vs `crtlib.o` vs
|
||||
`crtprx.o`), the right `create-fself` packaging mode (`--eboot` vs `--lib`), and a sensible
|
||||
default `-ptype`.
|
||||
- **GoldHEN plugins** — `TYPE GHPLUGIN` builds a plugin the GoldHEN loader actually accepts.
|
||||
It differs from a plain PRX in three ways that must all be right or the plugin builds
|
||||
cleanly and then silently never loads: the [GoldHEN Plugin
|
||||
SDK](https://github.com/GoldHEN/GoldHEN_Plugins_SDK)'s `crtprx.o` instead of `crtlib.o`,
|
||||
entry at `_init`, and `SceLibcInternal`/`SceSysmodule` instead of `c`/`c++`. Requires the
|
||||
SDK installed into the toolchain — the generated CMake fails with instructions if it isn't.
|
||||
The scaffolded starter plugin shows a PS4 notification from `module_start`.
|
||||
- **Optional full `.pkg` installer** — `add_orbis_package()` stages `eboot.bin` and a generated
|
||||
`param.sfo` into a directory tree matching the final package layout, then drives
|
||||
`create-gp4` + `PkgTool.Core pkg_build` to produce a real signed/encrypted `.pkg`.
|
||||
|
||||
@@ -5,13 +5,15 @@ var help = """
|
||||
OrbisForge - project scaffolder for the OpenOrbis PS4 Toolchain (CMake-based)
|
||||
|
||||
Usage:
|
||||
orbisforge new --name <name> --type exe|prx|sprx [options]
|
||||
orbisforge new --name <name> --type exe|prx|sprx|ghplugin [options]
|
||||
orbisforge build [path] [--clean]
|
||||
orbisforge deploy [path]
|
||||
|
||||
'new' options:
|
||||
--name <name> Project / target name (required)
|
||||
--type <type> exe, prx, or sprx (required)
|
||||
--type <type> exe, prx, sprx, or ghplugin (required). ghplugin builds a
|
||||
GoldHEN plugin and needs the GoldHEN Plugin SDK installed
|
||||
into the toolchain (crtprx.o + libGoldHEN_Hook.a in lib/).
|
||||
--output <dir> Output directory (default: current directory)
|
||||
--pkg EXE only: also scaffold a full .pkg installer step
|
||||
--ptype <ptype> Override create-fself -ptype (advanced)
|
||||
@@ -33,6 +35,7 @@ var help = """
|
||||
Examples:
|
||||
orbisforge new --name hello_cmake --type exe
|
||||
orbisforge new --name my_plugin --type sprx --output E:\Projects\PS4
|
||||
orbisforge new --name notify_plugin --type ghplugin --output E:\Projects\PS4
|
||||
orbisforge new --name my_game --type exe --pkg
|
||||
orbisforge new --name my_game --type exe --deploy-host 192.168.1.50 --deploy-path /data/homebrew/my_game/
|
||||
orbisforge build
|
||||
@@ -121,7 +124,7 @@ int RunNew(string[] args)
|
||||
|
||||
if (typeText is null || !OrbisTargetTypeExtensions.TryParse(typeText, out var type))
|
||||
{
|
||||
Console.Error.WriteLine("--type is required and must be one of: exe, prx, sprx.");
|
||||
Console.Error.WriteLine("--type is required and must be one of: exe, prx, sprx, ghplugin.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ public enum OrbisTargetType
|
||||
|
||||
/// <summary>Signed system library/module -> <name>.sprx (ptype "system_dynlib").</summary>
|
||||
Sprx,
|
||||
|
||||
/// <summary>GoldHEN plugin -> <name>.prx (ptype "fake"). Like Prx, but entered at
|
||||
/// _init via the GoldHEN SDK's crtprx.o so the loader reaches module_start.</summary>
|
||||
GoldHenPlugin,
|
||||
}
|
||||
|
||||
public static class OrbisTargetTypeExtensions
|
||||
@@ -23,12 +27,18 @@ public static class OrbisTargetTypeExtensions
|
||||
OrbisTargetType.Exe => "EXE",
|
||||
OrbisTargetType.Prx => "PRX",
|
||||
OrbisTargetType.Sprx => "SPRX",
|
||||
OrbisTargetType.GoldHenPlugin => "GHPLUGIN",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
|
||||
public static bool IsLibrary(this OrbisTargetType type) =>
|
||||
type is OrbisTargetType.Prx or OrbisTargetType.Sprx;
|
||||
|
||||
/// <summary>True when the project's primary source is src/<name>.cpp rather than
|
||||
/// src/main.cpp. Libraries and GoldHEN plugins both have no main().</summary>
|
||||
public static bool UsesNamedSourceFile(this OrbisTargetType type) =>
|
||||
type.IsLibrary() || type is OrbisTargetType.GoldHenPlugin;
|
||||
|
||||
/// <summary>Path to the packaged artifact create-fself produces, relative to the
|
||||
/// project root (i.e. under the CMake build directory).</summary>
|
||||
public static string ArtifactRelativePath(this OrbisTargetType type, string name) => type switch
|
||||
@@ -36,6 +46,7 @@ public static class OrbisTargetTypeExtensions
|
||||
OrbisTargetType.Exe => "build/eboot.bin",
|
||||
OrbisTargetType.Prx => $"build/{name}.prx",
|
||||
OrbisTargetType.Sprx => $"build/{name}.sprx",
|
||||
OrbisTargetType.GoldHenPlugin => $"build/{name}.prx",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
|
||||
@@ -46,6 +57,8 @@ public static class OrbisTargetTypeExtensions
|
||||
case "EXE": case "BIN": type = OrbisTargetType.Exe; return true;
|
||||
case "PRX": type = OrbisTargetType.Prx; return true;
|
||||
case "SPRX": type = OrbisTargetType.Sprx; return true;
|
||||
case "GHPLUGIN": case "GOLDHEN": case "PLUGIN":
|
||||
type = OrbisTargetType.GoldHenPlugin; return true;
|
||||
default: type = default; return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,16 @@ public static class ProjectGenerator
|
||||
WriteFile(Path.Combine(root, "build.bat"), Windows(BuildScriptsTemplate.BuildBat), files);
|
||||
WriteFile(Path.Combine(root, ".vscode", "tasks.json"), VsCodeTemplate.TasksJson, files);
|
||||
WriteFile(Path.Combine(root, ".vscode", "c_cpp_properties.json"), VsCodeTemplate.CCppPropertiesJson, files);
|
||||
WriteFile(Path.Combine(root, ".vscode", "settings.json"), VsCodeTemplate.SettingsJson, files);
|
||||
WriteFile(Path.Combine(root, ".gitignore"), Unix(GitignoreTemplate.Gitignore), files);
|
||||
|
||||
if (options.Type.IsLibrary())
|
||||
if (options.Type == OrbisTargetType.GoldHenPlugin)
|
||||
{
|
||||
// A plugin is entered at module_start, so it gets neither a main() nor
|
||||
// the library pair's public header.
|
||||
WriteFile(Path.Combine(root, "src", $"{name}.cpp"), SourceTemplate.PluginCpp(name), files);
|
||||
}
|
||||
else if (options.Type.IsLibrary())
|
||||
{
|
||||
WriteFile(Path.Combine(root, "src", $"{name}.cpp"), SourceTemplate.LibCpp(name), files);
|
||||
WriteFile(Path.Combine(root, "src", $"{name}.h"), SourceTemplate.LibHeader(name), files);
|
||||
|
||||
@@ -24,19 +24,55 @@ internal static class BuildScriptsTemplate
|
||||
cmake --build build
|
||||
""";
|
||||
|
||||
// Windows entry point: shells into WSL. Deliberately avoids ever referencing an
|
||||
// unquoted $PATH inside the wsl bash -c command line -- WSL auto-injects the full
|
||||
// Windows PATH (with spaces and parentheses like "Program Files (x86)") into that
|
||||
// environment, and passing it through unquoted breaks bash's parser. Instead this
|
||||
// queries OO_PS4_TOOLCHAIN once via its own isolated call and builds an explicit,
|
||||
// clean PATH from it.
|
||||
// Windows entry point. Builds natively when the toolchain is visible from Windows
|
||||
// (its bin\windows tools are then usable), otherwise shells into WSL.
|
||||
//
|
||||
// The WSL path deliberately avoids ever referencing an unquoted $PATH inside the
|
||||
// bash -c command line -- WSL auto-injects the full Windows PATH (with spaces and
|
||||
// parentheses like "Program Files (x86)") into that environment, and passing it
|
||||
// through unquoted breaks bash's parser. Instead this queries OO_PS4_TOOLCHAIN once
|
||||
// via its own isolated call and builds an explicit, clean PATH from it.
|
||||
public const string BuildBat = """
|
||||
@echo off
|
||||
setlocal
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
set "SCRIPTDIR=%~dp0"
|
||||
if "%SCRIPTDIR:~-1%"=="\" set "SCRIPTDIR=%SCRIPTDIR:~0,-1%"
|
||||
|
||||
rem vswhere lives under a path containing "(x86)"; resolve it up front, because
|
||||
rem expanding that inside a parenthesised if-block breaks cmd's parser.
|
||||
set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||
|
||||
if not defined OO_PS4_TOOLCHAIN goto :wsl
|
||||
|
||||
if /i "%~1"=="clean" (
|
||||
if exist "%SCRIPTDIR%\build" rmdir /s /q "%SCRIPTDIR%\build"
|
||||
echo Cleaned.
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
where cmake >nul 2>&1
|
||||
if errorlevel 1 echo cmake was not found on PATH. & exit /b 1
|
||||
where clang >nul 2>&1
|
||||
if errorlevel 1 echo clang was not found on PATH -- install LLVM and add its bin directory. & exit /b 1
|
||||
|
||||
rem The Visual Studio generators cannot drive this cross-compile, so a
|
||||
rem single-config generator is required. Ninja ships inside VS when it is not
|
||||
rem already on PATH.
|
||||
set "NINJA="
|
||||
for /f "delims=" %%i in ('where ninja 2^>nul') do set "NINJA=%%i"
|
||||
if not defined NINJA if exist "%VSWHERE%" for /f "usebackq delims=" %%i in (`"%VSWHERE%" -latest -products * -property installationPath 2^>nul`) do (
|
||||
if exist "%%i\Common7\IDE\CommonExtensions\Microsoft\CMake\Ninja\ninja.exe" set "NINJA=%%i\Common7\IDE\CommonExtensions\Microsoft\CMake\Ninja\ninja.exe"
|
||||
)
|
||||
if not defined NINJA echo Ninja was not found on PATH or in a Visual Studio install. Try: winget install Ninja-build.Ninja & exit /b 1
|
||||
|
||||
cmake -B "%SCRIPTDIR%\build" -S "%SCRIPTDIR%" -G Ninja -DCMAKE_MAKE_PROGRAM="!NINJA!" -DCMAKE_TOOLCHAIN_FILE="%SCRIPTDIR%/cmake/oo-ps4-toolchain.cmake"
|
||||
if errorlevel 1 exit /b 1
|
||||
cmake --build "%SCRIPTDIR%\build"
|
||||
if errorlevel 1 exit /b 1
|
||||
exit /b 0
|
||||
|
||||
:wsl
|
||||
for /f "usebackq tokens=2 delims==" %%i in (`wsl -- bash -ic "env | grep ^OO_PS4_TOOLCHAIN="`) do set "OO_TOOLCHAIN_WSL=%%i"
|
||||
if "%OO_TOOLCHAIN_WSL%"=="" (
|
||||
echo OO_PS4_TOOLCHAIN is not set in your WSL environment. See the OpenOrbis PS4 Toolchain README.
|
||||
|
||||
@@ -17,7 +17,7 @@ internal static class CMakeListsTemplate
|
||||
sb.AppendLine("endif()");
|
||||
sb.AppendLine();
|
||||
|
||||
var sources = options.Type.IsLibrary()
|
||||
var sources = options.Type.UsesNamedSourceFile()
|
||||
? $"src/{name}.cpp"
|
||||
: "src/main.cpp";
|
||||
|
||||
|
||||
@@ -6,20 +6,34 @@ internal static class OrbisTargetsTemplate
|
||||
# add_orbis_target() / add_orbis_package() - generated by OrbisForge.
|
||||
#
|
||||
# add_orbis_target(<name>
|
||||
# TYPE EXE|PRX|SPRX
|
||||
# TYPE EXE|PRX|SPRX|GHPLUGIN
|
||||
# SOURCES <files...>
|
||||
# [LIBS <names...>] # default: c kernel c++
|
||||
# [PTYPE <ptype>] # default: "fake" (EXE/PRX) or "system_dynlib" (SPRX)
|
||||
# # (GHPLUGIN: SceLibcInternal kernel SceSysmodule GoldHEN_Hook)
|
||||
# [PTYPE <ptype>] # default: "fake", or "system_dynlib" for SPRX
|
||||
# [PAID <hex>] # default: 0x3800000000000011
|
||||
# [LIBNAME <name>] # PRX/SPRX only, passed to create-fself -libname
|
||||
# [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
|
||||
# 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 "")
|
||||
@@ -36,12 +50,11 @@ internal static class OrbisTargetsTemplate
|
||||
endif()
|
||||
string(TOUPPER "${ORBIS_TYPE}" ORBIS_TYPE)
|
||||
|
||||
if(NOT ORBIS_LIBS)
|
||||
set(ORBIS_LIBS c kernel c++)
|
||||
endif()
|
||||
|
||||
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")
|
||||
@@ -57,8 +70,35 @@ internal static class OrbisTargetsTemplate
|
||||
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, or SPRX (got '${ORBIS_TYPE}')")
|
||||
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:
|
||||
@@ -83,6 +123,7 @@ internal static class OrbisTargetsTemplate
|
||||
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"
|
||||
@@ -93,8 +134,9 @@ internal static class OrbisTargetsTemplate
|
||||
--paid ${ORBIS_PAID}
|
||||
${_libname_args}
|
||||
COMMAND "${OO_PS4_TOOLCHAIN_BIN}/readoelf" "$<TARGET_FILE:${name}>.oelf"
|
||||
BYPRODUCTS "${_out_path}"
|
||||
COMMENT "orbis: packaging ${name} -> ${_out_name} (type=${ORBIS_TYPE}, ptype=${_ptype})"
|
||||
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
|
||||
)
|
||||
|
||||
@@ -102,6 +144,7 @@ internal static class OrbisTargetsTemplate
|
||||
ORBIS_OUTPUT_PATH "${_out_path}"
|
||||
ORBIS_OUTPUT_NAME "${_out_name}"
|
||||
ORBIS_TYPE "${ORBIS_TYPE}"
|
||||
ORBIS_ELF_PATH "${_elf_path}"
|
||||
)
|
||||
endfunction()
|
||||
|
||||
|
||||
@@ -41,4 +41,66 @@ internal static class SourceTemplate
|
||||
|
||||
int {{projectName}}_hello(char *buf, size_t buf_size, int num);
|
||||
""";
|
||||
|
||||
public static string PluginCpp(string projectName) => $$"""
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <GoldHEN/Common.h>
|
||||
|
||||
#define PLUGIN_NAME "{{projectName}}"
|
||||
#define PLUGIN_VERSION "1.00"
|
||||
|
||||
// GoldHEN reads these to list the plugin.
|
||||
#define attr_public __attribute__((visibility("default")))
|
||||
|
||||
attr_public const char *g_pluginName = PLUGIN_NAME;
|
||||
attr_public const char *g_pluginDesc = "Generated by OrbisForge";
|
||||
attr_public const char *g_pluginAuth = "unknown";
|
||||
attr_public const char *g_pluginVersion = PLUGIN_VERSION;
|
||||
|
||||
// This is compiled as C++, so the entry points need C linkage: crtprx.o's
|
||||
// _init resolves module_start/module_stop by their unmangled names.
|
||||
extern "C" {
|
||||
|
||||
static void notify(const char *msg)
|
||||
{
|
||||
OrbisNotificationRequest req;
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
|
||||
req.type = NotificationRequest;
|
||||
req.targetId = -1;
|
||||
req.useIconImageUri = 1;
|
||||
|
||||
strncpy(req.iconUri, "cxml://psnotification/tex_icon_system", sizeof(req.iconUri) - 1);
|
||||
strncpy(req.message, msg, sizeof(req.message) - 1);
|
||||
|
||||
// Non-blocking: module_start must not stall the title's startup.
|
||||
sceKernelSendNotificationRequest(0, &req, sizeof(req), 0);
|
||||
}
|
||||
|
||||
int module_start(size_t argc, const void *argp)
|
||||
{
|
||||
(void)argc;
|
||||
(void)argp;
|
||||
|
||||
notify(PLUGIN_NAME " v" PLUGIN_VERSION " loaded");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int module_stop(size_t argc, const void *argp)
|
||||
{
|
||||
(void)argc;
|
||||
(void)argp;
|
||||
|
||||
notify(PLUGIN_NAME " unloaded");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
}
|
||||
@@ -15,7 +15,18 @@ internal static class ToolchainTemplate
|
||||
message(FATAL_ERROR "OO_PS4_TOOLCHAIN environment variable is not set")
|
||||
endif()
|
||||
set(OO_PS4_TOOLCHAIN "$ENV{OO_PS4_TOOLCHAIN}" CACHE PATH "OpenOrbis PS4 toolchain root")
|
||||
set(OO_PS4_TOOLCHAIN_BIN "${OO_PS4_TOOLCHAIN}/bin/linux" CACHE PATH "OpenOrbis PS4 toolchain host tools")
|
||||
|
||||
# create-fself/readoelf are host binaries, so this follows the machine running
|
||||
# the build, not the PS4 target. Overridable via -DOO_PS4_TOOLCHAIN_BIN=... for
|
||||
# the mixed case (e.g. a Windows checkout of the toolchain driven from WSL).
|
||||
if(CMAKE_HOST_WIN32)
|
||||
set(_oo_host_bin "windows")
|
||||
elseif(CMAKE_HOST_APPLE)
|
||||
set(_oo_host_bin "macos")
|
||||
else()
|
||||
set(_oo_host_bin "linux")
|
||||
endif()
|
||||
set(OO_PS4_TOOLCHAIN_BIN "${OO_PS4_TOOLCHAIN}/bin/${_oo_host_bin}" CACHE PATH "OpenOrbis PS4 toolchain host tools")
|
||||
|
||||
set(CMAKE_C_COMPILER clang)
|
||||
set(CMAKE_CXX_COMPILER clang++)
|
||||
|
||||
@@ -54,4 +54,15 @@ internal static class VsCodeTemplate
|
||||
"version": 4
|
||||
}
|
||||
""";
|
||||
|
||||
// The CMakePresets.json we generate is for command-line/WSL cmake use, not for the
|
||||
// CMake Tools extension to drive itself. Outside a WSL/Remote-WSL session it'd pick a
|
||||
// local MSVC kit and try to configure with cl.exe, which can't parse the toolchain
|
||||
// file's clang-style flags (-fPIC, -isysroot, ...) and fails confusingly. Building
|
||||
// always goes through tasks.json (Ctrl+Shift+B) -> build.bat/build.sh instead.
|
||||
public const string SettingsJson = """
|
||||
{
|
||||
"cmake.configureOnOpen": false
|
||||
}
|
||||
""";
|
||||
}
|
||||
@@ -71,6 +71,7 @@ namespace OrbisForge.Gui
|
||||
_typeBox.Items.Add("EXE (eboot.bin, homebrew app)");
|
||||
_typeBox.Items.Add("PRX (user-mode library)");
|
||||
_typeBox.Items.Add("SPRX (signed system library)");
|
||||
_typeBox.Items.Add("GHPLUGIN (GoldHEN plugin)");
|
||||
_typeBox.SelectedIndex = 0;
|
||||
_typeBox.Location = new Point(left, y);
|
||||
_typeBox.Size = new Size(fieldWidth, 24);
|
||||
@@ -208,6 +209,7 @@ namespace OrbisForge.Gui
|
||||
{
|
||||
case 1: return OrbisTargetType.Prx;
|
||||
case 2: return OrbisTargetType.Sprx;
|
||||
case 3: return OrbisTargetType.GoldHenPlugin;
|
||||
default: return OrbisTargetType.Exe;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ public class OrbisTargetTypeTests
|
||||
[InlineData(OrbisTargetType.Exe, "EXE")]
|
||||
[InlineData(OrbisTargetType.Prx, "PRX")]
|
||||
[InlineData(OrbisTargetType.Sprx, "SPRX")]
|
||||
[InlineData(OrbisTargetType.GoldHenPlugin, "GHPLUGIN")]
|
||||
public void ToCMakeString_ReturnsExpectedToken(OrbisTargetType type, string expected)
|
||||
{
|
||||
Assert.Equal(expected, type.ToCMakeString());
|
||||
@@ -18,17 +19,33 @@ public class OrbisTargetTypeTests
|
||||
[InlineData(OrbisTargetType.Exe, false)]
|
||||
[InlineData(OrbisTargetType.Prx, true)]
|
||||
[InlineData(OrbisTargetType.Sprx, true)]
|
||||
[InlineData(OrbisTargetType.GoldHenPlugin, false)]
|
||||
public void IsLibrary_OnlyTrueForPrxAndSprx(OrbisTargetType type, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, type.IsLibrary());
|
||||
}
|
||||
|
||||
// A plugin isn't a "library" in the lib.cpp + public-header sense, but it still
|
||||
// has no main(), so it shares the src/<name>.cpp layout.
|
||||
[Theory]
|
||||
[InlineData(OrbisTargetType.Exe, false)]
|
||||
[InlineData(OrbisTargetType.Prx, true)]
|
||||
[InlineData(OrbisTargetType.Sprx, true)]
|
||||
[InlineData(OrbisTargetType.GoldHenPlugin, true)]
|
||||
public void UsesNamedSourceFile_TrueForEverythingWithoutMain(OrbisTargetType type, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, type.UsesNamedSourceFile());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("exe", OrbisTargetType.Exe)]
|
||||
[InlineData("EXE", OrbisTargetType.Exe)]
|
||||
[InlineData("bin", OrbisTargetType.Exe)]
|
||||
[InlineData(" prx ", OrbisTargetType.Prx)]
|
||||
[InlineData("SPRX", OrbisTargetType.Sprx)]
|
||||
[InlineData("ghplugin", OrbisTargetType.GoldHenPlugin)]
|
||||
[InlineData("GoldHEN", OrbisTargetType.GoldHenPlugin)]
|
||||
[InlineData(" plugin ", OrbisTargetType.GoldHenPlugin)]
|
||||
public void TryParse_AcceptsKnownAliasesCaseInsensitively(string text, OrbisTargetType expected)
|
||||
{
|
||||
Assert.True(OrbisTargetTypeExtensions.TryParse(text, out var type));
|
||||
@@ -53,6 +70,7 @@ public class OrbisTargetTypeTests
|
||||
[Theory]
|
||||
[InlineData(OrbisTargetType.Prx, "my_lib", "build/my_lib.prx")]
|
||||
[InlineData(OrbisTargetType.Sprx, "my_plugin", "build/my_plugin.sprx")]
|
||||
[InlineData(OrbisTargetType.GoldHenPlugin, "notify", "build/notify.prx")]
|
||||
public void ArtifactRelativePath_Libraries_UseNameInFilename(OrbisTargetType type, string name, string expected)
|
||||
{
|
||||
Assert.Equal(expected, type.ArtifactRelativePath(name));
|
||||
|
||||
@@ -41,6 +41,7 @@ public class ProjectGeneratorTests : IDisposable
|
||||
AssertFileExists(result, "build.sh");
|
||||
AssertFileExists(result, ".vscode/tasks.json");
|
||||
AssertFileExists(result, ".vscode/c_cpp_properties.json");
|
||||
AssertFileExists(result, ".vscode/settings.json");
|
||||
AssertFileExists(result, ".gitignore");
|
||||
AssertFileExists(result, "README.md");
|
||||
|
||||
@@ -66,10 +67,36 @@ public class ProjectGeneratorTests : IDisposable
|
||||
AssertFileMissing(result, "src/main.cpp");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generate_GoldHenPlugin_WritesPluginSourceWithoutHeaderOrMain()
|
||||
{
|
||||
var result = ProjectGenerator.Generate(new ProjectOptions
|
||||
{
|
||||
Name = "notify_plugin",
|
||||
Type = OrbisTargetType.GoldHenPlugin,
|
||||
OutputDirectory = _outputDir,
|
||||
});
|
||||
|
||||
AssertFileExists(result, "src/notify_plugin.cpp");
|
||||
AssertFileMissing(result, "src/notify_plugin.h");
|
||||
AssertFileMissing(result, "src/main.cpp");
|
||||
|
||||
var source = ReadFile(result, "src/notify_plugin.cpp");
|
||||
|
||||
// The loader reaches these through crtprx.o's _init, which resolves them by
|
||||
// their unmangled names -- without extern "C" the C++ compiler would mangle
|
||||
// them and the plugin would link but never load.
|
||||
Assert.Contains("extern \"C\"", source);
|
||||
Assert.Contains("int module_start(", source);
|
||||
Assert.Contains("int module_stop(", source);
|
||||
Assert.Contains("sceKernelSendNotificationRequest", source);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(OrbisTargetType.Exe, "TYPE EXE")]
|
||||
[InlineData(OrbisTargetType.Prx, "TYPE PRX")]
|
||||
[InlineData(OrbisTargetType.Sprx, "TYPE SPRX")]
|
||||
[InlineData(OrbisTargetType.GoldHenPlugin, "TYPE GHPLUGIN")]
|
||||
public void Generate_CMakeLists_DeclaresCorrectType(OrbisTargetType type, string expectedSnippet)
|
||||
{
|
||||
var result = ProjectGenerator.Generate(new ProjectOptions
|
||||
@@ -222,9 +249,12 @@ public class ProjectGeneratorTests : IDisposable
|
||||
// JsonDocument.Parse throws on malformed JSON -- that's the assertion.
|
||||
using var tasks = JsonDocument.Parse(ReadFile(result, ".vscode/tasks.json"));
|
||||
using var cpp = JsonDocument.Parse(ReadFile(result, ".vscode/c_cpp_properties.json"));
|
||||
using var settings = JsonDocument.Parse(ReadFile(result, ".vscode/settings.json"));
|
||||
|
||||
Assert.True(tasks.RootElement.TryGetProperty("tasks", out _));
|
||||
Assert.True(cpp.RootElement.TryGetProperty("configurations", out _));
|
||||
Assert.True(settings.RootElement.TryGetProperty("cmake.configureOnOpen", out var configureOnOpen));
|
||||
Assert.False(configureOnOpen.GetBoolean());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in new issue
Block a user