From 9be6f85ef02712901f3ff0bd27a979dae3c15c19 Mon Sep 17 00:00:00 2001 From: h4sht Date: Tue, 21 Jul 2026 17:18:16 +0200 Subject: [PATCH] [Font] Implement sceFontGetVerticalLayout (#492) Add sceFontGetVerticalLayout (NID: 3BrWWFU+4ts) to the Font module, completing the vertical-text counterpart to the existing GetHorizontalLayout. The SceFontVerticalLayout structure is three floats (baseline, lineAdvance, decorationExtent) interpreted for vertical writing such as CJK text rendered top-to-bottom. - Write baseline=8.0f, lineAdvance=16.0f, decorationExtent=0.0f - Validate output pointer and return INVALID_ARGUMENT on null - Return MEMORY_FAULT when guest writes fail Tests: - GetVerticalLayout_WritesExactlyThreeFloats with sentinel guard - GetVerticalLayout_NullBuffer_ReturnsInvalidArgument NID sourced via: python scripts/aerolib_catalog.py lookup sceFontGetVerticalLayout Co-authored-by: tru3 --- src/SharpEmu.Libs/Font/FontExports.cs | 31 +++++++++++++++++++ .../Font/FontExportsTests.cs | 28 +++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/SharpEmu.Libs/Font/FontExports.cs b/src/SharpEmu.Libs/Font/FontExports.cs index f5f00fdc..fab39708 100644 --- a/src/SharpEmu.Libs/Font/FontExports.cs +++ b/src/SharpEmu.Libs/Font/FontExports.cs @@ -153,6 +153,37 @@ public static class FontExports 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", diff --git a/tests/SharpEmu.Libs.Tests/Font/FontExportsTests.cs b/tests/SharpEmu.Libs.Tests/Font/FontExportsTests.cs index 8720a0e6..849f7a66 100644 --- a/tests/SharpEmu.Libs.Tests/Font/FontExportsTests.cs +++ b/tests/SharpEmu.Libs.Tests/Font/FontExportsTests.cs @@ -41,4 +41,32 @@ public sealed class FontExportsTests Assert.Equal(0.0f, BinaryPrimitives.ReadSingleLittleEndian(layout[8..])); Assert.Equal(Sentinel, BinaryPrimitives.ReadUInt32LittleEndian(layout[12..])); } + + [Fact] + public void GetVerticalLayout_WritesExactlyThreeFloats() + { + const uint Sentinel = 0xDEADBEEF; + Span sentinelBytes = stackalloc byte[sizeof(uint)]; + BinaryPrimitives.WriteUInt32LittleEndian(sentinelBytes, Sentinel); + Assert.True(_ctx.Memory.TryWrite(LayoutAddress + 12, sentinelBytes)); + + _ctx[CpuRegister.Rsi] = LayoutAddress; + Assert.Equal(0, FontExports.GetVerticalLayout(_ctx)); + + Span layout = stackalloc byte[16]; + Assert.True(_ctx.Memory.TryRead(LayoutAddress, layout)); + Assert.Equal(8.0f, BinaryPrimitives.ReadSingleLittleEndian(layout)); + Assert.Equal(16.0f, BinaryPrimitives.ReadSingleLittleEndian(layout[4..])); + Assert.Equal(0.0f, BinaryPrimitives.ReadSingleLittleEndian(layout[8..])); + Assert.Equal(Sentinel, BinaryPrimitives.ReadUInt32LittleEndian(layout[12..])); + } + + [Fact] + public void GetVerticalLayout_NullBuffer_ReturnsInvalidArgument() + { + _ctx[CpuRegister.Rsi] = 0; + Assert.Equal( + (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT, + FontExports.GetVerticalLayout(_ctx)); + } }