Files
sharpemu/src/SharpEmu.Libs/Font/FontExports.cs
T
Berk 5864328e35 Merge media decoding into one FFmpeg bridge (#706)
* [media] merge bink into shared ffmpeg bridge

* [avplayer] decode in process and fix stream info size

* [font] add glyph and teardown exports

* [build] bump ffmpeg runtime to 3b502d4

* [font] add glyph and teardown exports

* [build] bump ffmpeg runtime to 3b502d4

* [avplayer] decode in process

* [font] add glyph and teardown exports

* [build] bump ffmpeg runtime to 3b502d4
2026-07-31 01:31:14 +03:00

555 lines
19 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using SharpEmu.HLE;
namespace SharpEmu.Libs.Font;
public static class FontExports
{
private const ushort GlyphMagic = 0x0F03;
private const int GlyphSize = 0x100;
private const int GlyphMetricsSize = 8 * sizeof(float);
private const int RenderOutputSize = 0x40;
private static readonly object AllocationGate = new();
private static readonly Stack<ulong> FreeGlyphs = new();
private static ulong _librarySelectionAddress;
private static ulong _rendererSelectionAddress;
[SysAbiExport(
Nid = "whrS4oksXc4",
ExportName = "sceFontMemoryInit",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int MemoryInit(CpuContext ctx)
{
var descriptorAddress = ctx[CpuRegister.Rdi];
var regionAddress = ctx[CpuRegister.Rsi];
var regionSize = (uint)ctx[CpuRegister.Rdx];
var interfaceAddress = ctx[CpuRegister.Rcx];
var mspaceAddress = ctx[CpuRegister.R8];
var destroyCallback = ctx[CpuRegister.R9];
if (descriptorAddress == 0 ||
!TryWriteUInt32(ctx, descriptorAddress, 0x00000F00) ||
!TryWriteUInt32(ctx, descriptorAddress + 0x04, regionSize) ||
!ctx.TryWriteUInt64(descriptorAddress + 0x08, regionAddress) ||
!ctx.TryWriteUInt64(descriptorAddress + 0x10, mspaceAddress) ||
!ctx.TryWriteUInt64(descriptorAddress + 0x18, interfaceAddress) ||
!ctx.TryWriteUInt64(descriptorAddress + 0x20, destroyCallback) ||
!ctx.TryWriteUInt64(descriptorAddress + 0x28, 0) ||
!ctx.TryWriteUInt64(descriptorAddress + 0x30, 0) ||
!ctx.TryWriteUInt64(descriptorAddress + 0x38, mspaceAddress))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
return SetSuccess(ctx);
}
[SysAbiExport(
Nid = "oM+XCzVG3oM",
ExportName = "sceFontSelectLibraryFt",
Target = Generation.Gen5,
LibraryName = "libSceFontFt")]
public static int SelectLibraryFt(CpuContext ctx) =>
ReturnSelection(ctx, ref _librarySelectionAddress, 0x38);
[SysAbiExport(
Nid = "Xx974EW-QFY",
ExportName = "sceFontSelectRendererFt",
Target = Generation.Gen5,
LibraryName = "libSceFontFt")]
public static int SelectRendererFt(CpuContext ctx) =>
ReturnSelection(ctx, ref _rendererSelectionAddress, 0x100);
[SysAbiExport(
Nid = "n590hj5Oe-k",
ExportName = "sceFontCreateLibraryWithEdition",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int CreateLibraryWithEdition(CpuContext ctx) =>
CreateOpaqueHandle(ctx, ctx[CpuRegister.Rcx], 0x100, magic: 0x0F01);
[SysAbiExport(
Nid = "WaSFJoRWXaI",
ExportName = "sceFontCreateRendererWithEdition",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int CreateRendererWithEdition(CpuContext ctx) =>
CreateOpaqueHandle(ctx, ctx[CpuRegister.Rcx], 0x100, magic: 0x0F07);
[SysAbiExport(
Nid = "3OdRkSjOcog",
ExportName = "sceFontBindRenderer",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int BindRenderer(CpuContext ctx) => SetSuccess(ctx);
[SysAbiExport(
Nid = "N1EBMeGhf7E",
ExportName = "sceFontSetScalePixel",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int SetScalePixel(CpuContext ctx) => SetSuccess(ctx);
[SysAbiExport(
Nid = "TMtqoFQjjbA",
ExportName = "sceFontSetEffectSlant",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int SetEffectSlant(CpuContext ctx) => SetSuccess(ctx);
[SysAbiExport(
Nid = "v0phZwa4R5o",
ExportName = "sceFontSetEffectWeight",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int SetEffectWeight(CpuContext ctx) => SetSuccess(ctx);
[SysAbiExport(
Nid = "6vGCkkQJOcI",
ExportName = "sceFontSetupRenderScalePixel",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int SetupRenderScalePixel(CpuContext ctx) => SetSuccess(ctx);
[SysAbiExport(
Nid = "lz9y9UFO2UU",
ExportName = "sceFontSetupRenderEffectSlant",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int SetupRenderEffectSlant(CpuContext ctx) => SetSuccess(ctx);
[SysAbiExport(
Nid = "XIGorvLusDQ",
ExportName = "sceFontSetupRenderEffectWeight",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int SetupRenderEffectWeight(CpuContext ctx) => SetSuccess(ctx);
[SysAbiExport(
Nid = "imxVx8lm+KM",
ExportName = "sceFontGetHorizontalLayout",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int GetHorizontalLayout(CpuContext ctx)
{
var layoutAddress = ctx[CpuRegister.Rsi];
if (layoutAddress == 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
// Baseline, line advance, decoration extent: the same invented geometry
// as GetRenderCharGlyphMetrics.
var values = new[] { 12.0f, 16.0f, 0.0f };
for (var index = 0; index < values.Length; index++)
{
if (!TryWriteUInt32(
ctx,
layoutAddress + (ulong)(index * sizeof(float)),
BitConverter.SingleToUInt32Bits(values[index])))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
}
return SetSuccess(ctx);
}
[SysAbiExport(
Nid = "3BrWWFU+4ts",
ExportName = "sceFontGetVerticalLayout",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int GetVerticalLayout(CpuContext ctx)
{
var layoutAddress = ctx[CpuRegister.Rsi];
if (layoutAddress == 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
// Baseline (horizontal offset), line advance, decoration extent.
// Mirrors the same three-float layout as GetHorizontalLayout, but
// interpreted for vertical writing (e.g. CJK text rendered top-to-bottom).
var values = new[] { 8.0f, 16.0f, 0.0f };
for (var index = 0; index < values.Length; index++)
{
if (!TryWriteUInt32(
ctx,
layoutAddress + (ulong)(index * sizeof(float)),
BitConverter.SingleToUInt32Bits(values[index])))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
}
return SetSuccess(ctx);
}
[SysAbiExport(
Nid = "cKYtVmeSTcw",
ExportName = "sceFontOpenFontSet",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int OpenFontSet(CpuContext ctx) =>
CreateOpaqueHandle(ctx, ctx[CpuRegister.R8], 0x100, magic: 0x0F02);
[SysAbiExport(
Nid = "KXUpebrFk1U",
ExportName = "sceFontOpenFontMemory",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int OpenFontMemory(CpuContext ctx) =>
CreateOpaqueHandle(ctx, ctx[CpuRegister.R8], 0x100, magic: 0x0F02);
[SysAbiExport(
Nid = "JzCH3SCFnAU",
ExportName = "sceFontOpenFontInstance",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int OpenFontInstance(CpuContext ctx)
{
var sourceHandle = ctx[CpuRegister.Rdi];
var setupHandle = ctx[CpuRegister.Rsi];
var outputAddress = ctx[CpuRegister.Rdx];
if (outputAddress == 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (setupHandle != 0)
{
return ctx.TryWriteUInt64(outputAddress, setupHandle)
? SetSuccess(ctx)
: SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (!TryAllocateOpaque(ctx, 0x100, out var handle))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (sourceHandle != 0)
{
Span<byte> source = stackalloc byte[0x100];
if (ctx.Memory.TryRead(sourceHandle, source))
{
_ = ctx.Memory.TryWrite(handle, source);
}
}
_ = TryWriteUInt16(ctx, handle, 0x0F02);
return ctx.TryWriteUInt64(outputAddress, handle)
? SetSuccess(ctx)
: SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
[SysAbiExport(
Nid = "SsRbbCiWoGw",
ExportName = "sceFontSupportSystemFonts",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int SupportSystemFonts(CpuContext ctx) => SetSuccess(ctx);
[SysAbiExport(
Nid = "mz2iTY0MK4A",
ExportName = "sceFontSupportExternalFonts",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int SupportExternalFonts(CpuContext ctx) => SetSuccess(ctx);
[SysAbiExport(
Nid = "CUKn5pX-NVY",
ExportName = "sceFontAttachDeviceCacheBuffer",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int AttachDeviceCacheBuffer(CpuContext ctx) => SetSuccess(ctx);
[SysAbiExport(
Nid = "IQtleGLL5pQ",
ExportName = "sceFontGetRenderCharGlyphMetrics",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int GetRenderCharGlyphMetrics(CpuContext ctx)
{
var metricsAddress = ctx[CpuRegister.Rdx];
if (metricsAddress == 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
var values = new[] { 8.0f, 16.0f, 0.0f, 12.0f, 8.0f, 0.0f, 0.0f, 16.0f };
for (var index = 0; index < values.Length; index++)
{
if (!TryWriteUInt32(
ctx,
metricsAddress + (ulong)(index * sizeof(float)),
BitConverter.SingleToUInt32Bits(values[index])))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
}
return SetSuccess(ctx);
}
[SysAbiExport(
Nid = "gdUCnU0gHdI",
ExportName = "sceFontRenderSurfaceInit",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int RenderSurfaceInit(CpuContext ctx)
{
var surfaceAddress = ctx[CpuRegister.Rdi];
var bufferAddress = ctx[CpuRegister.Rsi];
var widthBytes = (uint)ctx[CpuRegister.Rdx];
var pixelBytes = (uint)ctx[CpuRegister.Rcx] & 0xFF;
var width = (uint)ctx[CpuRegister.R8];
var height = (uint)ctx[CpuRegister.R9];
if (surfaceAddress == 0 ||
!ctx.TryWriteUInt64(surfaceAddress, bufferAddress) ||
!TryWriteUInt32(ctx, surfaceAddress + 0x08, widthBytes) ||
!TryWriteUInt32(ctx, surfaceAddress + 0x0C, pixelBytes) ||
!TryWriteUInt32(ctx, surfaceAddress + 0x10, width) ||
!TryWriteUInt32(ctx, surfaceAddress + 0x14, height) ||
!TryWriteUInt32(ctx, surfaceAddress + 0x18, 0) ||
!TryWriteUInt32(ctx, surfaceAddress + 0x1C, 0) ||
!TryWriteUInt32(ctx, surfaceAddress + 0x20, width) ||
!TryWriteUInt32(ctx, surfaceAddress + 0x24, height))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
return SetSuccess(ctx);
}
[SysAbiExport(
Nid = "C-4Qw5Srlyw",
ExportName = "sceFontGenerateCharGlyph",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int GenerateCharGlyph(CpuContext ctx)
{
var outputAddress = ctx[CpuRegister.Rcx];
if (outputAddress == 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!TryRentGlyph(ctx, out var glyph) ||
!ctx.TryWriteUInt64(outputAddress, glyph))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
return SetSuccess(ctx);
}
[SysAbiExport(
Nid = "8-zmgsxkBek",
ExportName = "sceFontGlyphDefineAttribute",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int GlyphDefineAttribute(CpuContext ctx) => SetSuccess(ctx);
[SysAbiExport(
Nid = "LHDoRWVFGqk",
ExportName = "sceFontDeleteGlyph",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int DeleteGlyph(CpuContext ctx)
{
var glyphPointerAddress = ctx[CpuRegister.Rsi];
if (glyphPointerAddress == 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!ctx.TryReadUInt64(glyphPointerAddress, out var glyph))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (glyph != 0)
{
lock (AllocationGate)
{
FreeGlyphs.Push(glyph);
}
}
return ctx.TryWriteUInt64(glyphPointerAddress, 0)
? SetSuccess(ctx)
: SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
[SysAbiExport(
Nid = "kAenWy1Zw5o",
ExportName = "sceFontRenderCharGlyphImageHorizontal",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int RenderCharGlyphImageHorizontal(CpuContext ctx)
{
var metricsAddress = ctx[CpuRegister.Rcx];
var resultAddress = ctx[CpuRegister.R8];
if (metricsAddress != 0)
{
var values = new[] { 8.0f, 16.0f, 0.0f, 12.0f, 8.0f, 0.0f, 0.0f, 16.0f };
for (var index = 0; index < values.Length; index++)
{
if (!TryWriteUInt32(
ctx,
metricsAddress + (ulong)(index * sizeof(float)),
BitConverter.SingleToUInt32Bits(values[index])))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
}
}
if (resultAddress != 0)
{
Span<byte> cleared = stackalloc byte[RenderOutputSize];
cleared.Clear();
if (!ctx.Memory.TryWrite(resultAddress, cleared))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
}
return SetSuccess(ctx);
}
[SysAbiExport(
Nid = "vzHs3C8lWJk",
ExportName = "sceFontCloseFont",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int CloseFont(CpuContext ctx) => SetSuccess(ctx);
[SysAbiExport(
Nid = "1QjhKxrsOB8",
ExportName = "sceFontUnbindRenderer",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int UnbindRenderer(CpuContext ctx) => SetSuccess(ctx);
[SysAbiExport(
Nid = "exAxkyVLt0s",
ExportName = "sceFontDestroyRenderer",
Target = Generation.Gen5,
LibraryName = "libSceFont")]
public static int DestroyRenderer(CpuContext ctx)
{
var rendererPointerAddress = ctx[CpuRegister.Rdi];
if (rendererPointerAddress == 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
return ctx.TryWriteUInt64(rendererPointerAddress, 0)
? SetSuccess(ctx)
: SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
private static bool TryRentGlyph(CpuContext ctx, out ulong glyph)
{
lock (AllocationGate)
{
if (FreeGlyphs.Count > 0)
{
glyph = FreeGlyphs.Pop();
return TryWriteUInt16(ctx, glyph, GlyphMagic);
}
}
return TryAllocateOpaque(ctx, GlyphSize, out glyph) &&
TryWriteUInt16(ctx, glyph, GlyphMagic);
}
private static int ReturnSelection(CpuContext ctx, ref ulong selectionAddress, uint objectSize)
{
if (ctx[CpuRegister.Rdi] != 0)
{
ctx[CpuRegister.Rax] = 0;
return 0;
}
lock (AllocationGate)
{
if (selectionAddress == 0)
{
if (!TryAllocateOpaque(ctx, 0x20, out selectionAddress) ||
!TryWriteUInt32(ctx, selectionAddress, 0) ||
!TryWriteUInt32(ctx, selectionAddress + 4, objectSize))
{
selectionAddress = 0;
}
}
}
ctx[CpuRegister.Rax] = selectionAddress;
return 0;
}
private static int CreateOpaqueHandle(CpuContext ctx, ulong outputAddress, int size, ushort magic)
{
if (outputAddress == 0 || !TryAllocateOpaque(ctx, size, out var handle))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (!TryWriteUInt16(ctx, handle, magic) || !ctx.TryWriteUInt64(outputAddress, handle))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
return SetSuccess(ctx);
}
private static bool TryAllocateOpaque(CpuContext ctx, int size, out ulong address)
{
address = 0;
if (ctx.Memory is not IGuestMemoryAllocator allocator ||
!allocator.TryAllocateGuestMemory((ulong)size, 0x10, out address))
{
return false;
}
Span<byte> bytes = stackalloc byte[size];
bytes.Clear();
return ctx.Memory.TryWrite(address, bytes);
}
private static bool TryWriteUInt16(CpuContext ctx, ulong address, ushort value)
{
Span<byte> bytes = stackalloc byte[sizeof(ushort)];
BinaryPrimitives.WriteUInt16LittleEndian(bytes, value);
return ctx.Memory.TryWrite(address, bytes);
}
private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
{
Span<byte> bytes = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(bytes, value);
return ctx.Memory.TryWrite(address, bytes);
}
private static int SetSuccess(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 0;
return 0;
}
private static int SetReturn(CpuContext ctx, OrbisGen2Result result)
{
ctx[CpuRegister.Rax] = unchecked((ulong)(int)result);
return (int)result;
}
}