mumain/ClientLibrary/GenerateExtensionsDotNet.xslt
Mosch0512 1a4d25c01a Run and connect the client natively on Linux (issue #462 Phase 4)
The engine now boots into the login scene, renders via SDL/OpenGL, and
connects to a server on Linux. Brings up the remaining runtime pieces and
fixes the bugs that surfaced once the game actually ran.

Networking (.NET client library on Linux):
- Build MUnique.Client.Library as a native linux-x64 Native AOT shared
  object and dlopen the .so instead of the Windows .dll.
- Fix the native-to-managed string marshalling: the client passes wchar_t*,
  which is 4-byte UTF-32 on Linux but the library decoded it as UTF-16,
  garbling the server address into a bogus IP so connect() hung forever on
  the main thread (the apparent "black screen"). Decode by the platform's
  real wchar_t width. Updates the XSLT template so regeneration stays correct.

Runtime fixes uncovered by running:
- Config was never read on Linux: the exe-dir split looked for a backslash
  only, so config.ini was never found and every setting fell back to its
  default. Accept either path separator.
- UI piled into the top-left: CInput::Create bailed on the null SDL window
  handle before setting the screen size, so centered windows computed
  negative positions. Set the size regardless of the handle off Windows.
- LP64 width bugs reading binary assets (size fields are 32-bit on disk, but
  long/size_t are 8 bytes on Linux x64): pet.bmd speed array, BMD encrypted
  size, camera walk-script waypoint count.
- Double fclose() of the same handle (filter file, object files): tolerated
  on the old CRT, a double-free on glibc.
- mu_swprintf bounds the buffer by its real array size to stop a fortify
  abort, and the GL frame can be dumped via MU_CAPTURE_FRAME for headless
  verification.

Portable subsystems:
- GDI text backend backed by stb_truetype (CreateFont/CreateDIBSection/
  TextOut), so UI text renders without Win32 GDI.
- Case-correcting path resolver so Windows-style asset paths resolve on a
  case-sensitive filesystem, wired through every file-open shim.
- GameShop compiles on Linux: the UI and list managers build, only the
  WinINet/urlmon downloader is excluded, with the download entry points
  failing gracefully.
- Portable shims extended: StrSafe, Sleep/GetLastError, file attribute and
  directory helpers, system-info for the crash log, and the Linux entry point
  now calls the shared WinMain bootstrap.
2026-06-13 18:22:03 +02:00

289 lines
10 KiB
HTML

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
xmlns:pd="http://www.munique.net/OpenMU/PacketDefinitions"
>
<xsl:param name="subNamespace" />
<xsl:output method="text" indent="yes" />
<xsl:include href="Common.xslt" />
<xsl:template match="pd:PacketDefinitions">
<xsl:text>// &lt;copyright file="ConnectionManager.cs" company="MUnique"&gt;
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// &lt;/copyright&gt;
//------------------------------------------------------------------------------
// &lt;auto-generated&gt;
// This source code was auto-generated by an XSL transformation.
// Do not change this file. Instead, change the XML data which contains
// the packet definitions and re-run the transformation (publish/rebuild the
// client library project).
// &lt;/auto-generated&gt;
//------------------------------------------------------------------------------
// ReSharper disable RedundantVerbatimPrefix
// ReSharper disable AssignmentIsFullyDiscarded
// ReSharper disable UnusedMember.Global
// ReSharper disable UseObjectOrCollectionInitializer
#nullable enable
namespace MUnique.Client.Library;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using MUnique.OpenMU.Network;
using MUnique.OpenMU.Network.Packets;
using MUnique.OpenMU.Network.Packets.</xsl:text><xsl:value-of select="$subNamespace"/><xsl:text>;
/// &lt;summary&gt;
/// Extension methods to start writing messages of this namespace on a &lt;see cref="IConnection"/&gt;.
/// &lt;/summary&gt;
public unsafe partial class ConnectionManager
{</xsl:text>
<xsl:apply-templates select="pd:Packets/pd:Packet" mode="ext2" />
<xsl:text>}</xsl:text>
</xsl:template>
<xsl:template match="pd:Packet[not(pd:Fields/pd:Field/pd:Type = 'Structure[]')]" mode="ext2">
<xsl:value-of select="$newline" />
<xsl:text>
/// &lt;summary&gt;
/// Sends a &lt;see cref="</xsl:text>
<xsl:apply-templates select="pd:Name" />
<xsl:text>" /&gt; to this connection.
/// &lt;/summary&gt;
/// &lt;param name="handle"&gt;The handle of the connection.&lt;/param&gt;</xsl:text>
<xsl:apply-templates select="pd:Fields/pd:Field" mode="paramdoc">
<xsl:sort select="pd:DefaultValue"/>
</xsl:apply-templates>
<xsl:call-template name="WriteRemarks" />
<xsl:text> [UnmanagedCallersOnly(EntryPoint = "Send</xsl:text>
<xsl:apply-templates select="pd:Name" />
<xsl:text>")]
public static void </xsl:text>
<xsl:text>Send</xsl:text>
<xsl:apply-templates select="pd:Name" />
<xsl:text>(int handle</xsl:text>
<xsl:if test="count(pd:Fields/pd:Field) > 0">
<xsl:text>, </xsl:text>
</xsl:if>
<xsl:apply-templates select="pd:Fields/pd:Field" mode="params">
<xsl:sort select="pd:DefaultValue"/>
</xsl:apply-templates>
<xsl:text>)</xsl:text>
<xsl:value-of select="$newline"/>
<xsl:text> {
if (!Connections.TryGetValue(handle, out var connection))
{
return;
}
try</xsl:text>
<xsl:text>
{
connection.CreateAndSend(pipeWriter =&gt;
{
var length = </xsl:text>
<xsl:choose>
<xsl:when test="pd:Length">
<xsl:apply-templates select="pd:Name" />
<xsl:text>Ref.Length</xsl:text>
</xsl:when>
<xsl:when test="not(pd:Length)">
<xsl:apply-templates select="pd:Name" />
<xsl:text>Ref.GetRequiredSize(</xsl:text>
<xsl:apply-templates select="pd:Fields/pd:Field[(pd:Type = 'Binary' or pd:Type = 'String') and not(pd:Length)]" mode="length"/>
<xsl:text>)</xsl:text>
</xsl:when>
<xsl:otherwise>
throw new NotImplementedException()
</xsl:otherwise>
</xsl:choose>
<xsl:text>;
var packet = new </xsl:text>
<xsl:apply-templates select="pd:Name" />
<xsl:text>Ref(pipeWriter.GetSpan(length)[..length]);</xsl:text>
<xsl:if test="pd:Fields/pd:Field">
<xsl:value-of select="$newline"/>
<xsl:apply-templates select="pd:Fields/pd:Field" mode="assignment" />
</xsl:if>
<xsl:text>
return length;
});
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}</xsl:text>
</xsl:template>
<xsl:template match="pd:Field[(pd:Type = 'Binary')]" mode="length">
<xsl:text>(int)</xsl:text>
<xsl:call-template name="LowerCaseName" />
<xsl:text>ByteLength</xsl:text>
</xsl:template>
<xsl:template match="pd:Field[(pd:Type = 'String')]" mode="length">
<xsl:text>Encoding.UTF8.GetByteCount(NativeInterop.PtrToWideString(@</xsl:text>
<xsl:call-template name="LowerCaseName" />
<xsl:text>)!)</xsl:text>
</xsl:template>
<xsl:template match="pd:Field" mode="paramdoc">
<xsl:value-of select="$newline"/>
<xsl:text> /// &lt;param name="</xsl:text>
<xsl:call-template name="LowerCaseName" />
<xsl:text>"&gt;</xsl:text>
<xsl:choose>
<xsl:when test="pd:Description">
<xsl:value-of select="pd:Description"/>
</xsl:when>
<xsl:otherwise>
<xsl:text>The</xsl:text>
<xsl:call-template name="splitName">
<xsl:with-param name="name" select="pd:Name" />
</xsl:call-template>
<xsl:text>.</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text>&lt;/param&gt;</xsl:text>
<xsl:if test="(pd:Type = 'Binary')">
<xsl:value-of select="$newline"/>
<xsl:text> /// &lt;param name="</xsl:text>
<xsl:call-template name="LowerCaseName" />
<xsl:text>ByteLength</xsl:text>
<xsl:text>"&gt;The length of &lt;paramref name="</xsl:text>
<xsl:call-template name="LowerCaseName" />
<xsl:text>"/&gt;.</xsl:text>
<xsl:text>&lt;/param&gt;</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="pd:Type[. = 'Binary']" mode="type">Memory&lt;byte&gt;</xsl:template>
<xsl:template match="pd:DefaultValue" mode="params">
<xsl:choose>
<xsl:when test=". = 'true'">1</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="pd:Field" mode="params">
<xsl:if test="position() > 1">
<xsl:text>, </xsl:text>
</xsl:if>
<xsl:apply-templates select="pd:Type" mode="blittabletype"/>
<xsl:text> @</xsl:text>
<xsl:call-template name="LowerCaseName" />
<xsl:if test="pd:DefaultValue">
<xsl:text> = </xsl:text>
<xsl:apply-templates select="pd:DefaultValue" mode="params"/>
</xsl:if>
<xsl:if test="(pd:Type = 'Binary')">
<xsl:text>, uint </xsl:text>
<xsl:call-template name="LowerCaseName" />
<xsl:text>ByteLength</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="pd:Field" mode="listparams">
<xsl:if test="position() > 1">
<xsl:text>, </xsl:text>
</xsl:if>
<xsl:text>@</xsl:text>
<xsl:call-template name="LowerCaseName" />
</xsl:template>
<xsl:template match="pd:Field" mode="assignment">
<xsl:choose>
<xsl:when test="pd:Type='Binary'">
<xsl:text> new Span&lt;byte&gt;(@</xsl:text>
<xsl:call-template name="LowerCaseName" />
<xsl:text>, (int)</xsl:text>
<xsl:call-template name="LowerCaseName" />
<xsl:text>ByteLength).CopyTo(packet.</xsl:text>
<xsl:value-of select="pd:Name"/>
<xsl:text>);</xsl:text>
</xsl:when>
<xsl:when test="pd:Type='Boolean'">
<xsl:text> packet.</xsl:text>
<xsl:value-of select="pd:Name"/>
<xsl:text> = @</xsl:text>
<xsl:call-template name="LowerCaseName" />
<xsl:text> == 1;</xsl:text>
</xsl:when>
<xsl:when test="pd:Type='String'">
<xsl:text> packet.</xsl:text>
<xsl:value-of select="pd:Name"/>
<xsl:text> = NativeInterop.PtrToWideString(@</xsl:text>
<xsl:call-template name="LowerCaseName" />
<xsl:text>);</xsl:text>
</xsl:when>
<xsl:when test="pd:Type='Binary'">
<xsl:text> packet.</xsl:text>
<xsl:value-of select="pd:Name"/>
<xsl:text> = new Span&lt;byte&gt;(@</xsl:text>
<xsl:call-template name="LowerCaseName" />
<xsl:text>, (int)</xsl:text>
<xsl:call-template name="LowerCaseName" />
<xsl:text>ByteLength);</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text> packet.</xsl:text>
<xsl:value-of select="pd:Name"/>
<xsl:text> = @</xsl:text>
<xsl:call-template name="LowerCaseName" />
<xsl:text>;</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="$newline"/>
</xsl:template>
<xsl:template name="WriteRemarks">
<xsl:if test="pd:SentWhen or pd:CausedReaction">
<xsl:text>
/// &lt;remarks&gt;
/// Is sent </xsl:text>
<xsl:choose>
<xsl:when test="pd:Direction = 'ClientToServer'">
<xsl:text>by the client when: </xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>by the server when: </xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="pd:SentWhen"/>
<xsl:text>
/// Causes reaction </xsl:text>
<xsl:choose>
<xsl:when test="pd:Direction = 'ClientToServer'">
<xsl:text>on server side: </xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>on client side: </xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="pd:CausedReaction"/>
<xsl:text>
/// &lt;/remarks&gt;</xsl:text>
<xsl:value-of select="$newline" />
</xsl:if>
</xsl:template>
<xsl:template match="text()" mode="params"></xsl:template>
<xsl:template match="text()" mode="listparams"></xsl:template>
<xsl:template match="text()" mode="paramdoc"></xsl:template>
<xsl:template match="text()" mode="assignment"></xsl:template>
<xsl:template match="text()" mode="ext2"></xsl:template>
<xsl:template match="text()" mode="length"></xsl:template>
<xsl:template match="text()" mode="writer"></xsl:template>
<xsl:template match="text()"></xsl:template>
</xsl:stylesheet>