You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
87 lines
4.0 KiB
87 lines
4.0 KiB
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;
|
|
}
|
|
}
|
|
|