Browse Source
Added a custom marshaler to decode screen texts received from Quake, which uses single-byte characters and a non-standard character set. Fixes string conversion errors and a fatal exception whenever the "ö" character is printed in the Zerstörer mod.
console
Added a custom marshaler to decode screen texts received from Quake, which uses single-byte characters and a non-standard character set. Fixes string conversion errors and a fatal exception whenever the "ö" character is printed in the Zerstörer mod.
console
4 changed files with 103 additions and 11 deletions
-
2Assets/Scripts/Data/QExtensions.cs
-
22Assets/Scripts/Modules/SystemModule.Interop.cs
-
87Assets/Scripts/QuakeTextMarshaler.cs
-
3Assets/Scripts/QuakeTextMarshaler.cs.meta
@ -0,0 +1,87 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using System.Text; |
|||
|
|||
/// <summary>
|
|||
/// Quake uses a character set for its strings that is partially ASCII, but also partially nothing standard at all.
|
|||
/// Inside the main PAK file is a character atlas texture and in order to print text, each char value inside of a
|
|||
/// string is used to index a glyph in this atlas, which is then drawn to the screen.
|
|||
///
|
|||
/// Most strings received from Quake will work fine with the default ANSI charset marshaling, however some mods will
|
|||
/// define their own character sets and use byte values >127 to print special characters. This is where the ANSI
|
|||
/// marshaler breaks down, as these bytes are interpreted to signify multi-byte characters and result in a String
|
|||
/// conversion error. To remedy this, we define our own single-byte character set that reflects the default one from
|
|||
/// Quake and decode strings received from the engine by ourselves.
|
|||
/// </summary>
|
|||
public class QuakeTextMarshaler: ICustomMarshaler |
|||
{ |
|||
private static QuakeTextMarshaler instance = null; |
|||
|
|||
private static readonly char[] CharacterTable = |
|||
{ |
|||
'·', ' ', ' ', ' ', ' ', '·', ' ', ' ', ' ', ' ', '\n', ' ', ' ', ' ', '·', '·', |
|||
'[', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '·', ' ', ' ', ' ', |
|||
' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', |
|||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', |
|||
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', |
|||
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', |
|||
'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', |
|||
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '←', |
|||
'·', ' ', ' ', ' ', ' ', '·', ' ', ' ', ' ', ' ', '\n', ' ', ' ', ' ', '·', '·', |
|||
'[', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '·', ' ', ' ', ' ', |
|||
' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', |
|||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', |
|||
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', |
|||
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', |
|||
'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', |
|||
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '←', |
|||
}; |
|||
|
|||
public IntPtr MarshalManagedToNative(object managedObj) |
|||
{ |
|||
if (managedObj == null) |
|||
return IntPtr.Zero; |
|||
|
|||
if (!(managedObj is string str)) |
|||
throw new MarshalDirectiveException("QuakeTextMarshaler must be used on a string."); |
|||
|
|||
throw new NotImplementedException("QuakeTextMarshaler can only be used to decode text from Quake."); |
|||
} |
|||
|
|||
public object MarshalNativeToManaged(IntPtr pNativeData) |
|||
{ |
|||
byte b; |
|||
|
|||
var sb = new StringBuilder(); |
|||
while ((b = Marshal.ReadByte(pNativeData)) != 0) |
|||
{ |
|||
sb.Append(CharacterTable[b]); |
|||
pNativeData = IntPtr.Add(pNativeData, 1); |
|||
} |
|||
|
|||
return sb.ToString(); |
|||
} |
|||
|
|||
public void CleanUpManagedData(object managedObj) |
|||
{ |
|||
} |
|||
|
|||
public void CleanUpNativeData(IntPtr pNativeData) |
|||
{ |
|||
// Free HGlobal memory if we decide to implement MarshalManagedToNative
|
|||
} |
|||
|
|||
public int GetNativeDataSize() |
|||
{ |
|||
return -1; |
|||
} |
|||
|
|||
public static ICustomMarshaler GetInstance(string cookie) |
|||
{ |
|||
if (instance == null) |
|||
return instance = new QuakeTextMarshaler(); |
|||
|
|||
return instance; |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,3 @@ |
|||
fileFormatVersion: 2 |
|||
guid: 23e2ec4793084710924ba442860bf2d6 |
|||
timeCreated: 1618677615 |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue