diff --git a/SabreTools.Serialization/Deserializers/CFB.cs b/SabreTools.Serialization/Deserializers/CFB.cs index 42c9dd4e..ce5ba0a5 100644 --- a/SabreTools.Serialization/Deserializers/CFB.cs +++ b/SabreTools.Serialization/Deserializers/CFB.cs @@ -244,7 +244,7 @@ namespace SabreTools.Serialization.Deserializers var obj = new DirectoryEntry(); byte[] name = data.ReadBytes(64); - obj.Name = Encoding.Unicode.GetString(name); + obj.Name = Encoding.Unicode.GetString(name).DecodeStreamName(); obj.NameLength = data.ReadUInt16LittleEndian(); obj.ObjectType = (ObjectType)data.ReadByteValue(); obj.ColorFlag = (ColorFlag)data.ReadByteValue(); diff --git a/SabreTools.Serialization/Extensions.CFB.cs b/SabreTools.Serialization/Extensions.CFB.cs new file mode 100644 index 00000000..dca66b71 --- /dev/null +++ b/SabreTools.Serialization/Extensions.CFB.cs @@ -0,0 +1,99 @@ +using System.Text; + +namespace SabreTools.Serialization +{ + public static partial class Extensions + { + /// + /// Decode a MIME-encoded stream name stored as a byte array + /// + /// Byte array to decode + /// Decoded string on success, null otherwise + /// Adapted from LibMSI + public static string? DecodeStreamName(this byte[]? input) + { + // Ignore invalid inputs + if (input == null) + return null; + else if (input.Length == 0) + return string.Empty; + + int count = 0; + int p = 0; // inputBytes[0] + + byte[] output = new byte[input.Length + 1]; + int q = 0; // output[0] + while (p < input.Length && input[p] != 0) + { + byte ch = input[p]; + if ((ch == 0xe3 && input[p + 1] >= 0xa0) || (ch == 0xe4 && input[p + 1] < 0xa0)) + { + // UTF-8 encoding of 0x3800..0x47ff. + output[q++] = (byte)Mime2Utf(input[p + 2] & 0x7f); + output[q++] = (byte)Mime2Utf(input[p + 1] ^ 0xa0); + p += 3; + count += 2; + continue; + } + + if (ch == 0xe4 && input[p + 1] == 0xa0) + { + // UTF-8 encoding of 0x4800..0x483f. + output[q++] = (byte)Mime2Utf(input[p + 2] & 0x7f); + p += 3; + count++; + continue; + } + + output[q++] = input[p++]; + if (ch >= 0xc1) + output[q++] = input[p++]; + if (ch >= 0xe0) + output[q++] = input[p++]; + if (ch >= 0xf0) + output[q++] = input[p++]; + + count++; + } + + output[q] = 0; + return Encoding.ASCII.GetString(output); + } + + /// + /// Decode a MIME-encoded stream name stored as a string + /// + /// String to decode + /// Decoded string on success, null otherwise + /// Adapted from LibMSI + public static string? DecodeStreamName(this string? input) + { + // Ignore invalid inputs + if (input == null) + return null; + else if (input.Length == 0) + return string.Empty; + + byte[] inputBytes = Encoding.UTF8.GetBytes(input); + return inputBytes.DecodeStreamName(); + } + + /// + /// Decode a single MIME-encoded byte + /// + /// Byte to decode + /// Decoded byte on success, `_` otherwise + /// Adapted from LibMSI + private static int Mime2Utf(int b) + { + return b switch + { + < 10 => b + '0', + < 10 + 26 => b - 10 + 'A', + < 10 + 26 + 26 => b - 10 - 26 + 'a', + 10 + 26 + 26 => '.', + _ => '_' + }; + } + } +} \ No newline at end of file