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)); + } }