2021-01-15 06:50:13 +00:00
|
|
|
|
using System;
|
2019-03-21 04:50:44 +00:00
|
|
|
|
using System.Text;
|
2021-04-19 01:29:02 +00:00
|
|
|
|
using static PKHeX.Core.StringConverter7ZH;
|
2019-03-21 04:50:44 +00:00
|
|
|
|
|
|
|
|
|
namespace PKHeX.Core
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Logic for converting a <see cref="string"/> between the various generation specific encoding formats.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static class StringConverter
|
|
|
|
|
{
|
2021-03-10 05:31:53 +00:00
|
|
|
|
private const char TerminatorNull = (char)0;
|
|
|
|
|
private const char TerminatorFFFF = (char)0xFFFF;
|
|
|
|
|
|
2019-03-21 04:50:44 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Converts bytes to a string according to the input parameters.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="data">Encoded data</param>
|
|
|
|
|
/// <param name="generation">Generation string format</param>
|
|
|
|
|
/// <param name="jp">Encoding is Japanese</param>
|
2020-06-17 02:46:22 +00:00
|
|
|
|
/// <param name="isBigEndian">Encoding is Big Endian</param>
|
2019-03-21 04:50:44 +00:00
|
|
|
|
/// <param name="count">Length of data to read.</param>
|
|
|
|
|
/// <param name="offset">Offset to read from</param>
|
|
|
|
|
/// <returns>Decoded string.</returns>
|
2020-06-17 02:46:22 +00:00
|
|
|
|
public static string GetString(byte[] data, int generation, bool jp, bool isBigEndian, int count, int offset = 0)
|
2019-03-21 04:50:44 +00:00
|
|
|
|
{
|
2020-06-17 02:46:22 +00:00
|
|
|
|
if (isBigEndian)
|
2019-03-21 04:50:44 +00:00
|
|
|
|
return generation == 3 ? StringConverter3.GetBEString3(data, offset, count) : StringConverter4.GetBEString4(data, offset, count);
|
|
|
|
|
|
2020-12-25 01:12:08 +00:00
|
|
|
|
return generation switch
|
2019-03-21 04:50:44 +00:00
|
|
|
|
{
|
2020-12-25 01:12:08 +00:00
|
|
|
|
1 or 2 => StringConverter12.GetString1(data, offset, count, jp),
|
|
|
|
|
3 => StringConverter3.GetString3(data, offset, count, jp),
|
|
|
|
|
4 => StringConverter4.GetString4(data, offset, count),
|
|
|
|
|
5 => GetString5(data, offset, count),
|
|
|
|
|
6 => GetString6(data, offset, count),
|
2021-01-31 21:10:03 +00:00
|
|
|
|
7 => GetString7(data, offset, count),
|
2021-04-19 01:29:02 +00:00
|
|
|
|
_ => GetString7b(data, offset, count),
|
2020-12-25 01:12:08 +00:00
|
|
|
|
};
|
2019-03-21 04:50:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the bytes for a Generation specific string according to the input parameters.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="value">Decoded string.</param>
|
|
|
|
|
/// <param name="generation">Generation string format</param>
|
|
|
|
|
/// <param name="jp">Encoding is Japanese</param>
|
2020-06-17 02:46:22 +00:00
|
|
|
|
/// <param name="isBigEndian">Encoding is Big Endian</param>
|
2021-01-17 01:31:05 +00:00
|
|
|
|
/// <param name="maxLength">Maximum length of the input <see cref="value"/></param>
|
|
|
|
|
/// <param name="language">Language specific conversion (Chinese)</param>
|
|
|
|
|
/// <param name="padTo">Pad the input <see cref="value"/> to given length</param>
|
|
|
|
|
/// <param name="padWith">Pad the input <see cref="value"/> with this character value</param>
|
2019-03-21 04:50:44 +00:00
|
|
|
|
/// <returns>Encoded data.</returns>
|
2020-06-17 02:46:22 +00:00
|
|
|
|
public static byte[] SetString(string value, int generation, bool jp, bool isBigEndian, int maxLength, int language = 0, int padTo = 0, ushort padWith = 0)
|
2019-03-21 04:50:44 +00:00
|
|
|
|
{
|
2020-06-17 02:46:22 +00:00
|
|
|
|
if (isBigEndian)
|
2019-03-21 04:50:44 +00:00
|
|
|
|
return generation == 3 ? StringConverter3.SetBEString3(value, maxLength, padTo, padWith) : StringConverter4.SetBEString4(value, maxLength, padTo, padWith);
|
|
|
|
|
|
2020-12-25 01:12:08 +00:00
|
|
|
|
return generation switch
|
2019-03-21 04:50:44 +00:00
|
|
|
|
{
|
2020-12-25 01:12:08 +00:00
|
|
|
|
1 or 2 => StringConverter12.SetString1(value, maxLength, jp, padTo, padWith),
|
|
|
|
|
3 => StringConverter3.SetString3(value, maxLength, jp, padTo, padWith),
|
|
|
|
|
4 => StringConverter4.SetString4(value, maxLength, padTo, padWith),
|
|
|
|
|
5 => SetString5(value, maxLength, padTo, padWith),
|
|
|
|
|
6 => SetString6(value, maxLength, padTo, padWith),
|
2021-01-31 21:10:03 +00:00
|
|
|
|
7 => SetString7(value, maxLength, language, padTo, padWith),
|
2021-04-19 01:29:02 +00:00
|
|
|
|
_ => SetString7b(value, maxLength, padTo, padWith),
|
2020-12-25 01:12:08 +00:00
|
|
|
|
};
|
2019-03-21 04:50:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Converts Generation 5 encoded data to decoded string.</summary>
|
|
|
|
|
/// <param name="data">Encoded data</param>
|
|
|
|
|
/// <param name="offset">Offset to read from</param>
|
|
|
|
|
/// <param name="count">Length of data to read.</param>
|
|
|
|
|
/// <returns>Decoded string.</returns>
|
|
|
|
|
public static string GetString5(byte[] data, int offset, int count)
|
|
|
|
|
{
|
2021-01-15 06:50:13 +00:00
|
|
|
|
var raw = Encoding.Unicode.GetString(data, offset, count);
|
|
|
|
|
var sb = new StringBuilder(raw);
|
2021-03-10 05:31:53 +00:00
|
|
|
|
Util.TrimFromFirst(sb, TerminatorFFFF);
|
2021-01-15 06:50:13 +00:00
|
|
|
|
SanitizeString(sb);
|
|
|
|
|
return sb.ToString();
|
2019-03-21 04:50:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Gets the bytes for a Generation 5 string.</summary>
|
|
|
|
|
/// <param name="value">Decoded string.</param>
|
2021-01-17 01:31:05 +00:00
|
|
|
|
/// <param name="maxLength">Maximum length of the input <see cref="value"/></param>
|
|
|
|
|
/// <param name="padTo">Pad the input <see cref="value"/> to given length</param>
|
|
|
|
|
/// <param name="padWith">Pad the input <see cref="value"/> with this character value</param>
|
2019-03-21 04:50:44 +00:00
|
|
|
|
/// <returns>Encoded data.</returns>
|
|
|
|
|
public static byte[] SetString5(string value, int maxLength, int padTo = 0, ushort padWith = 0)
|
|
|
|
|
{
|
2021-01-15 06:50:13 +00:00
|
|
|
|
var sb = new StringBuilder(value, Math.Max(maxLength, padTo));
|
|
|
|
|
var delta = sb.Length - maxLength;
|
|
|
|
|
if (delta > 0)
|
|
|
|
|
sb.Remove(maxLength, delta);
|
|
|
|
|
|
|
|
|
|
// Replace Special Characters and add Terminator
|
|
|
|
|
UnSanitizeString(sb, 5);
|
2021-03-10 05:31:53 +00:00
|
|
|
|
sb.Append(TerminatorFFFF);
|
2021-01-15 06:50:13 +00:00
|
|
|
|
var d2 = padTo - sb.Length;
|
|
|
|
|
if (d2 > 0)
|
|
|
|
|
sb.Append((char)padWith, d2);
|
|
|
|
|
|
|
|
|
|
return Encoding.Unicode.GetBytes(sb.ToString());
|
2019-03-21 04:50:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Converts Generation 6 encoded data to decoded string.</summary>
|
|
|
|
|
/// <param name="data">Encoded data</param>
|
|
|
|
|
/// <param name="offset">Offset to read from</param>
|
|
|
|
|
/// <param name="count">Length of data to read.</param>
|
|
|
|
|
/// <returns>Decoded string.</returns>
|
|
|
|
|
public static string GetString6(byte[] data, int offset, int count)
|
|
|
|
|
{
|
2021-01-15 06:50:13 +00:00
|
|
|
|
var raw = Encoding.Unicode.GetString(data, offset, count);
|
|
|
|
|
var sb = new StringBuilder(raw);
|
2021-03-10 05:31:53 +00:00
|
|
|
|
Util.TrimFromFirst(sb, TerminatorNull);
|
2021-01-15 06:50:13 +00:00
|
|
|
|
SanitizeString(sb);
|
|
|
|
|
return sb.ToString();
|
2019-03-21 04:50:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Gets the bytes for a Generation 6 string.</summary>
|
|
|
|
|
/// <param name="value">Decoded string.</param>
|
2021-01-17 01:31:05 +00:00
|
|
|
|
/// <param name="maxLength">Maximum length of the input <see cref="value"/></param>
|
|
|
|
|
/// <param name="padTo">Pad the input <see cref="value"/> to given length</param>
|
|
|
|
|
/// <param name="padWith">Pad the input <see cref="value"/> with this character value</param>
|
2019-03-21 04:50:44 +00:00
|
|
|
|
/// <returns>Encoded data.</returns>
|
|
|
|
|
public static byte[] SetString6(string value, int maxLength, int padTo = 0, ushort padWith = 0)
|
|
|
|
|
{
|
2021-01-15 06:50:13 +00:00
|
|
|
|
var sb = new StringBuilder(value);
|
|
|
|
|
var delta = sb.Length - maxLength;
|
|
|
|
|
if (delta > 0)
|
|
|
|
|
sb.Remove(maxLength, delta);
|
|
|
|
|
|
|
|
|
|
// Replace Special Characters and add Terminator
|
|
|
|
|
UnSanitizeString(sb, 6);
|
2021-03-10 05:31:53 +00:00
|
|
|
|
sb.Append(TerminatorNull);
|
2021-01-15 06:50:13 +00:00
|
|
|
|
var d2 = padTo - sb.Length;
|
|
|
|
|
if (d2 > 0)
|
|
|
|
|
sb.Append((char)padWith, d2);
|
|
|
|
|
|
|
|
|
|
return Encoding.Unicode.GetBytes(sb.ToString());
|
2019-03-21 04:50:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Converts Generation 7 encoded data to decoded string.</summary>
|
|
|
|
|
/// <param name="data">Encoded data</param>
|
|
|
|
|
/// <param name="offset">Offset to read from</param>
|
|
|
|
|
/// <param name="count">Length of data to read.</param>
|
|
|
|
|
/// <returns>Decoded string.</returns>
|
|
|
|
|
public static string GetString7(byte[] data, int offset, int count)
|
|
|
|
|
{
|
2021-01-15 06:50:13 +00:00
|
|
|
|
var raw = Encoding.Unicode.GetString(data, offset, count);
|
|
|
|
|
var sb = new StringBuilder(raw);
|
2021-03-10 05:31:53 +00:00
|
|
|
|
Util.TrimFromFirst(sb, TerminatorNull);
|
2021-01-15 06:50:13 +00:00
|
|
|
|
SanitizeString(sb);
|
|
|
|
|
RemapChineseGlyphsBin2String(sb);
|
|
|
|
|
return sb.ToString();
|
2019-03-21 04:50:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-19 01:29:02 +00:00
|
|
|
|
/// <summary>Converts Generation 7-Beluga encoded data to decoded string.</summary>
|
|
|
|
|
/// <param name="data">Encoded data</param>
|
|
|
|
|
/// <param name="offset">Offset to read from</param>
|
|
|
|
|
/// <param name="count">Length of data to read.</param>
|
|
|
|
|
/// <returns>Decoded string.</returns>
|
|
|
|
|
public static string GetString7b(byte[] data, int offset, int count)
|
|
|
|
|
{
|
|
|
|
|
var raw = Encoding.Unicode.GetString(data, offset, count);
|
|
|
|
|
var sb = new StringBuilder(raw);
|
|
|
|
|
Util.TrimFromFirst(sb, TerminatorNull);
|
|
|
|
|
SanitizeString(sb);
|
|
|
|
|
//RemapChineseGlyphsBin2String(sb);
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-21 04:50:44 +00:00
|
|
|
|
/// <summary>Gets the bytes for a Generation 7 string.</summary>
|
|
|
|
|
/// <param name="value">Decoded string.</param>
|
2021-01-17 01:31:05 +00:00
|
|
|
|
/// <param name="maxLength">Maximum length of the input <see cref="value"/></param>
|
2019-03-21 04:50:44 +00:00
|
|
|
|
/// <param name="language">Language specific conversion (Chinese)</param>
|
2021-01-17 01:31:05 +00:00
|
|
|
|
/// <param name="padTo">Pad the input <see cref="value"/> to given length</param>
|
|
|
|
|
/// <param name="padWith">Pad the input <see cref="value"/> with this character value</param>
|
2019-03-21 04:50:44 +00:00
|
|
|
|
/// <param name="chinese">Chinese string remapping should be attempted</param>
|
|
|
|
|
/// <returns>Encoded data.</returns>
|
|
|
|
|
public static byte[] SetString7(string value, int maxLength, int language, int padTo = 0, ushort padWith = 0, bool chinese = false)
|
|
|
|
|
{
|
2021-01-15 06:50:13 +00:00
|
|
|
|
var sb = new StringBuilder(value);
|
|
|
|
|
var delta = sb.Length - maxLength;
|
|
|
|
|
if (delta > 0)
|
|
|
|
|
sb.Remove(maxLength, delta);
|
2019-03-21 04:50:44 +00:00
|
|
|
|
if (chinese)
|
2021-01-15 06:50:13 +00:00
|
|
|
|
ConvertString2BinG7_zh(sb, language);
|
|
|
|
|
|
|
|
|
|
// Replace Special Characters and add Terminator
|
|
|
|
|
UnSanitizeString(sb, 7);
|
2021-03-10 05:31:53 +00:00
|
|
|
|
sb.Append(TerminatorNull);
|
2021-01-15 06:50:13 +00:00
|
|
|
|
var d2 = padTo - sb.Length;
|
|
|
|
|
if (d2 > 0)
|
|
|
|
|
sb.Append((char)padWith, d2);
|
|
|
|
|
|
|
|
|
|
return Encoding.Unicode.GetBytes(sb.ToString());
|
2019-03-21 04:50:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-19 01:29:02 +00:00
|
|
|
|
/// <summary>Gets the bytes for a Generation 7-Beluga string.</summary>
|
2019-03-21 04:50:44 +00:00
|
|
|
|
/// <param name="value">Decoded string.</param>
|
2021-01-17 01:31:05 +00:00
|
|
|
|
/// <param name="maxLength">Maximum length of the input <see cref="value"/></param>
|
|
|
|
|
/// <param name="padTo">Pad the input <see cref="value"/> to given length</param>
|
|
|
|
|
/// <param name="padWith">Pad the input <see cref="value"/> with this character value</param>
|
2019-03-21 04:50:44 +00:00
|
|
|
|
/// <returns>Encoded data.</returns>
|
2021-04-19 01:29:02 +00:00
|
|
|
|
public static byte[] SetString7b(string value, int maxLength, int padTo = 0, ushort padWith = 0)
|
2019-03-21 04:50:44 +00:00
|
|
|
|
{
|
2021-01-15 06:50:13 +00:00
|
|
|
|
var sb = new StringBuilder(value);
|
|
|
|
|
var delta = sb.Length - maxLength;
|
|
|
|
|
if (delta > 0)
|
|
|
|
|
sb.Remove(maxLength, delta);
|
|
|
|
|
|
|
|
|
|
// Replace Special Characters and add Terminator
|
2021-04-19 01:29:02 +00:00
|
|
|
|
// No special characters!
|
2021-03-10 05:31:53 +00:00
|
|
|
|
sb.Append(TerminatorNull);
|
2021-01-15 06:50:13 +00:00
|
|
|
|
var d2 = padTo - sb.Length;
|
|
|
|
|
if (d2 > 0)
|
|
|
|
|
sb.Append((char)padWith, d2);
|
|
|
|
|
|
|
|
|
|
return Encoding.Unicode.GetBytes(sb.ToString());
|
2019-03-21 04:50:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Converts full width to single width
|
|
|
|
|
/// </summary>
|
2021-01-15 06:50:13 +00:00
|
|
|
|
/// <param name="s">Input string to sanitize.</param>
|
2019-03-21 04:50:44 +00:00
|
|
|
|
/// <returns></returns>
|
2021-01-15 06:50:13 +00:00
|
|
|
|
internal static void SanitizeString(StringBuilder s)
|
2019-03-21 04:50:44 +00:00
|
|
|
|
{
|
2021-01-15 06:50:13 +00:00
|
|
|
|
if (s.Length == 0)
|
|
|
|
|
return;
|
2019-03-21 04:50:44 +00:00
|
|
|
|
|
|
|
|
|
// remap custom glyphs to unicode
|
2021-01-15 06:50:13 +00:00
|
|
|
|
s.Replace('\uE08F', '♀'); // ♀ (gen6+)
|
|
|
|
|
s.Replace('\uE08E', '♂'); // ♂ (gen6+)
|
|
|
|
|
s.Replace('\u246E', '♀'); // ♀ (gen5)
|
|
|
|
|
s.Replace('\u246D', '♂'); // ♂ (gen5)
|
2019-03-21 04:50:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Converts full width to half width when appropriate
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="str">Input string to set.</param>
|
|
|
|
|
/// <param name="generation">Generation specific context</param>
|
|
|
|
|
/// <returns></returns>
|
2021-01-15 06:50:13 +00:00
|
|
|
|
internal static void UnSanitizeString(StringBuilder str, int generation)
|
2019-03-21 04:50:44 +00:00
|
|
|
|
{
|
|
|
|
|
if (generation <= 5)
|
|
|
|
|
{
|
2021-01-15 06:50:13 +00:00
|
|
|
|
str.Replace('\u2640', '\u246E'); // ♀
|
|
|
|
|
str.Replace('\u2642', '\u246D'); // ♂
|
|
|
|
|
return;
|
2019-03-21 04:50:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-19 01:29:02 +00:00
|
|
|
|
bool fullwidth = GetIsFullWidthString(str);
|
2019-03-21 04:50:44 +00:00
|
|
|
|
if (fullwidth) // jp/ko/zh strings
|
2021-01-15 06:50:13 +00:00
|
|
|
|
return; // keep as full width
|
2019-03-21 04:50:44 +00:00
|
|
|
|
|
|
|
|
|
// Convert back to half width glyphs
|
2021-01-15 06:50:13 +00:00
|
|
|
|
str.Replace('\u2640', '\uE08F'); // ♀
|
|
|
|
|
str.Replace('\u2642', '\uE08E'); // ♂
|
2019-03-21 04:50:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-19 01:29:02 +00:00
|
|
|
|
private static bool GetIsFullWidthString(StringBuilder str)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < str.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
var c = str[i];
|
|
|
|
|
if (c >> 12 is (0 or 0xE))
|
|
|
|
|
continue;
|
|
|
|
|
if (c is '\u2640' or '\u2642') // ♀♂
|
|
|
|
|
continue;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-03-21 04:50:44 +00:00
|
|
|
|
|
2021-04-19 01:29:02 +00:00
|
|
|
|
public static bool HasEastAsianScriptCharacters(string str)
|
|
|
|
|
{
|
|
|
|
|
foreach (var c in str)
|
|
|
|
|
{
|
|
|
|
|
if (c is >= '\u4E00' and <= '\u9FFF')
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-03-21 04:50:44 +00:00
|
|
|
|
}
|
|
|
|
|
}
|