Following the DirectPackageInstaller Tool to preview / send PS4 PKGs, recently PlayStation 4 Scene developer @marcussacana (DdtankAndr on Twitter) made available a PS4 OpenOrbis Mono sample entrypoint with SDL2 working to aid in the creation of C# PS4 homebrew without requiring Unity or the PlayStation DevKit leaks.
Download: IV0000-MONO00001_00-MONOSAMPLE000000.pkg (45.3 MB - Include as example the classic DVD logo screensaver) / GIT
Those interested in supporting his continued work can do so directly on Marcussacana's Ko-Fi Page and this comes proceeding the PS4 Mono UI Research by OSM-Made (OldSchoolModz) / Orbis Suite 3.0 Beta (WIP) for PS4 Developers with some related articles alongside further details below:
A Sample Mono Entrypoint for PS4 homebrews with SDL2 working
If you want, you can use the pre-built binaries and just rebuild the "main" C# project, and by replacing the main.exe inside the pkg and/or adding any new assemblies, in the PKG, you will be able to create your HB even without deal with the OpenOrbis ***.
How to build
The PS4 default mono runtime always try load the modules from sprx files, in this example, at /PS4-OpenOrbis-Mono/trampoline.c a hook in the mono runtime is installed in the function that try open the sprx assembly, and instead, load the .dll or .exe assembly file and call the mono runtime export mono_image_open_from_data_with_name, because it was made for load assembly direct from memory, that function don't expect a sprx file, but a common assembly file instead, allowing we load our mono libraries.
Since it depends of hooks, we need or ship the mono runtime in the pkg with a known offset, or instead detect the firmware version and find all possible offsets for all FWs. While the first method is easy and safest and works with different FWs, you will share a sony binary, and that can be a license issue.
In the second method you don't need to ship the PS4 mono runtime library, but instead you will need to add offests for each FWs and be sure, when a new JB came you should need update the offsets as well. This second method can works with different FWs as well, but that if you make the code detect the FW, and use the correct hook offset.
The good news about the second method is how is just a single hook, then you just need found one offset, and the given function is the only one that references the string %s.sprx, this make the job to find the correct function easy.
A third way and correct way, should be create a tool to pack the assemblies as fake sprx, I didn't tried that.
Really, I should try do that before create a hook, lol.
Tested in FW 6.72 and 9.00 only
Special Thanks:
Lightning Mods, AlAzif, Bucanero, Flatz, sleirsgoevy, OSM-Made and of course, the OpenOrbis team.
OrbisGL Testing in Real PS4
Download: IV0000-MONO00001_00-MONOSAMPLE000000.pkg (45.3 MB - Include as example the classic DVD logo screensaver) / GIT
Those interested in supporting his continued work can do so directly on Marcussacana's Ko-Fi Page and this comes proceeding the PS4 Mono UI Research by OSM-Made (OldSchoolModz) / Orbis Suite 3.0 Beta (WIP) for PS4 Developers with some related articles alongside further details below:
- Lua Script Debugger
- LibHomebrew Library with Lua Script Support
- Simple DirectMedia Layer v2.0 (SDL2) for LibOrbis
- LibLuaPS4 & LibHB Updates
- OpenOrbis PS4 Toolchain
- SDL2 GLES2 Platform Renderer Samples
- Abuse SDL2 WIP Port PS4 Homebrew Fork
- RunSquare: PS4 Edition Lua Homebrew Game
- PS4Load: PS4 SELF File Loader PKG for Homebrew Developers
A Sample Mono Entrypoint for PS4 homebrews with SDL2 working
If you want, you can use the pre-built binaries and just rebuild the "main" C# project, and by replacing the main.exe inside the pkg and/or adding any new assemblies, in the PKG, you will be able to create your HB even without deal with the OpenOrbis ***.
How to build
- Install the OpenOrbis ***
- Install the Mono ***
- Run make
The PS4 default mono runtime always try load the modules from sprx files, in this example, at /PS4-OpenOrbis-Mono/trampoline.c a hook in the mono runtime is installed in the function that try open the sprx assembly, and instead, load the .dll or .exe assembly file and call the mono runtime export mono_image_open_from_data_with_name, because it was made for load assembly direct from memory, that function don't expect a sprx file, but a common assembly file instead, allowing we load our mono libraries.
Since it depends of hooks, we need or ship the mono runtime in the pkg with a known offset, or instead detect the firmware version and find all possible offsets for all FWs. While the first method is easy and safest and works with different FWs, you will share a sony binary, and that can be a license issue.
In the second method you don't need to ship the PS4 mono runtime library, but instead you will need to add offests for each FWs and be sure, when a new JB came you should need update the offsets as well. This second method can works with different FWs as well, but that if you make the code detect the FW, and use the correct hook offset.
The good news about the second method is how is just a single hook, then you just need found one offset, and the given function is the only one that references the string %s.sprx, this make the job to find the correct function easy.
Really, I should try do that before create a hook, lol.
Tested in FW 6.72 and 9.00 only
Special Thanks:
Lightning Mods, AlAzif, Bucanero, Flatz, sleirsgoevy, OSM-Made and of course, the OpenOrbis team.
Code:
using OrbisGL.GL;
using System;
using System.Numerics;
using OrbisGL.Input;
using OrbisGL.Controls;
using OrbisGL;
namespace Orbis
{
internal class MainDisplay : Application
{
public MainDisplay() : base(1920, 1080, 60)
{
#if ORBIS
EnableKeyboard();
EnableDualshock(new DualshockSettings()
{
LeftAnalogAsPad = true,
PadAsSelector = true,
Mouse = VirtualMouse.Touchpad
});
#endif
InitializeComponents();
}
private void InitializeComponents()
{
var BG = new Panel(1920, 1080);
BG.Size = new Vector2(Width, Height);
var View = new RowView(300, 600);
for (int i = 0; i < 30; i++)
{
View.AddChild(new Checkbox(28)
{
Text = $"Checkbox {i}"
});
}
var ButtonA = new Button(1, 1, 28);
ButtonA.Text = "Button A";
var ButtonB = new Button(1, 1, 28);
ButtonB.Text = "Button B";
ButtonA.Position = new Vector2(10, 10);
View.Position = new Vector2(10, 80);
ButtonB.Position = new Vector2(10, 80 + View.Size.Y + 60);
ButtonA.Links.Down = View;
ButtonB.Links.Up = View;
View.Links.Up = ButtonA;
View.Links.Down = ButtonB;
BG.AddChild(ButtonA);
BG.AddChild(View);
BG.AddChild(ButtonB);
Objects.Add(BG);
}
}
}