Support G2 mail in all languages (#4359)

* Support G2 mail in all languages

- Handle Japanese/Korean mail
- Handle both NPC and user-entered mail (#3470)
- Display European mail based on mail language (#4027)

* Fix shift

* Add support for Stad2 mail
This commit is contained in:
abcboy101 2024-10-01 01:02:37 -04:00 committed by GitHub
parent 4aacffb99b
commit 7632230edf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 370 additions and 100 deletions

View file

@ -78,7 +78,7 @@ public sealed class SK2 : GBPKM, ICaughtData2
}
}
// 0x1F
public byte HeldMailID { get => Data[0x1F]; set => Data[0x1F] = value; }
public byte PokerusState { get => Data[0x20]; set => Data[0x20] = value; }
// Crystal only Caught Data

View file

@ -209,7 +209,7 @@ public static class StringConverter1
internal const char MNY = '¥'; // Yen
internal const char LPO = '@'; // Po
internal const char LKE = '#'; // Ke
internal const char LEA = '%'; // é for Box
internal const char LEA = '%'; // é for Box/Mail
public const char DOT = ''; // . for MR.MIME (U+2024, not U+002E)
internal const char SPF = ' '; // Full-width space (U+3000)
public const char SPH = ' '; // Half-width space
@ -229,7 +229,7 @@ public static class StringConverter1
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', // A0-AF
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'à', 'è', 'é', 'ù', 'À', 'Á', // B0-BF
'Ä', 'Ö', 'Ü', 'ä', 'ö', 'ü', 'È', 'É', 'Ì', 'Í', 'Ñ', 'Ò', 'Ó', 'Ù', 'Ú', 'á', // C0-CF
'ì', 'í', 'ñ', 'ò', 'ó', 'ú', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, '←', '\'', // D0-DF
'ì', 'í', 'ñ', 'ò', 'ó', 'ú', 'º', NUL, NUL, NUL, NUL, NUL, NUL, NUL, '←', '\'', // D0-DF
'', LPK, LMN, '-', NUL, NUL, '?', '!', DOT, '&', LEA, '→', '▷', '▶', '▼', '♂', // E0-EF
MNY, '×', '.', '/', ',', '♀', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', // F0-FF
];
@ -242,8 +242,8 @@ public static class StringConverter1
'だ', 'ぢ', 'づ', 'で', 'ど', NUL, NUL, NUL, NUL, NUL, 'ば', 'び', 'ぶ', 'ベ', 'ぼ', NUL, // 30-3F
'パ', 'ピ', 'プ', 'ポ', 'ぱ', 'ぴ', 'ぷ', 'ペ', 'ぽ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 40-4F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, TOT, NUL, NUL, // 50-5F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 60-6F
'「', '」', '『', '』', '・', '⋯', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, SPF, // 70-7F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, 'ぃ', 'ぅ', // 60-6F
'「', '」', '『', '』', '・', '⋯', 'ぁ', 'ぇ', 'ぉ', NUL, NUL, NUL, NUL, NUL, NUL, SPF, // 70-7F
'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', 'ス', 'セ', 'ソ', 'タ', // 80-8F
'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ', 'ヌ', 'ネ', '', 'ハ', 'ヒ', 'フ', 'ホ', 'マ', 'ミ', 'ム', // 90-9F
'メ', 'モ', 'ヤ', 'ユ', 'ヨ', 'ラ', 'ル', 'レ', 'ロ', 'ワ', 'ヲ', 'ン', 'ッ', 'ャ', 'ュ', 'ョ', // A0-AF
@ -251,7 +251,7 @@ public static class StringConverter1
'た', 'ち', 'つ', 'て', 'と', 'な', 'に', 'ぬ', 'ね', 'の', 'は', 'ひ', 'ふ', 'ヘ', 'ほ', 'ま', // C0-CF
'み', 'む', 'め', 'も', 'や', 'ゆ', 'よ', 'ら', 'リ', 'る', 'れ', 'ろ', 'わ', 'を', 'ん', 'っ', // D0-DF
'ゃ', 'ゅ', 'ょ', 'ー', '゚', '゙', '', '', '。', 'ァ', 'ゥ', 'ェ', NUL, NUL, NUL, '♂', // E0-EF
MNY, NUL, '', '', 'ォ', '♀', '', '', '', '', '', '', '', '', '', '', // F0-FF
MNY, '×', '', '', 'ォ', '♀', '', '', '', '', '', '', '', '', '', '', // F0-FF
];
#endregion

View file

@ -8,9 +8,11 @@ public static class StringConverter2
public const byte TerminatorZero = StringConverter1.TerminatorZero;
public const byte TradeOTCode = StringConverter1.TradeOTCode;
public const byte SpaceCode = StringConverter1.SpaceCode;
public const byte LineBreakCode = 0x4E; // Mail
public const char Terminator = StringConverter1.Terminator;
public const char TradeOT = StringConverter1.TradeOT;
public const char LineBreak = '⏎'; // Mail
public static bool GetIsJapanese(ReadOnlySpan<char> str) => AllJapanese(str);
@ -175,10 +177,11 @@ public static class StringConverter2
private const char MNY = StringConverter1.MNY; // Yen
private const char LPO = StringConverter1.LPO; // Po
private const char LKE = StringConverter1.LKE; // Ke
private const char LEA = StringConverter1.LEA; // é for Box
private const char LEA = StringConverter1.LEA; // é for Box/Mail
private const char DOT = StringConverter1.DOT; // . for MR.MIME (U+2024, not U+002E)
private const char SPF = StringConverter1.SPF; // Full-width space (U+3000)
private const char SPH = StringConverter1.SPH; // Half-width space
private const char RET = LineBreak; // Line break for Mail
private const char LAP = ''; // Apostrophe
@ -197,11 +200,11 @@ public static class StringConverter2
private const char LI4 = ''; // 's
private const char LI5 = ''; // 't
private const char LI6 = ''; // 'v
private const char LI7 = ''; // 'd
private const char LI8 = ''; // 'l
private const char LI9 = ''; // 'm
private const char LIA = ''; // 'r
private const char LIB = ''; // 's
private const char LI7 = '';
private const char LI8 = '';
private const char LI9 = '';
private const char LIA = '';
private const char LIB = '';
/// <summary>
/// English encoding table with unused indexes merged in from other languages that use them.
@ -213,7 +216,7 @@ public static class StringConverter2
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 10-1F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 20-2F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 30-3F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 40-4F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, RET, NUL, // 40-4F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, TOT, NUL, NUL, // 50-5F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 60-6F
LPO, LKE, '“', '”', NUL, '…', NUL, NUL, NUL, '┌', '─', '┐', '│', '└', '┘', SPH, // 70-7F
@ -233,7 +236,7 @@ public static class StringConverter2
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 10-1F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 20-2F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 30-3F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 40-4F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, RET, NUL, // 40-4F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, TOT, NUL, NUL, // 50-5F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 60-6F
LPO, LKE, '“', '”', NUL, '…', NUL, NUL, NUL, '┌', '─', '┐', '│', '└', '┘', SPH, // 70-7F
@ -241,7 +244,7 @@ public static class StringConverter2
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '(', ')', ':', ';', '[', ']', // 90-9F
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', // A0-AF
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'à', 'è', 'é', 'ù', 'ß', 'ç', // B0-BF
'Ä', 'Ö', 'Ü', 'ä', 'ö', 'ü', 'È', 'É', 'Ì', 'Í', 'Ñ', 'Ò', ', 'Ù', 'Ú', 'á', // C0-CF
'Ä', 'Ö', 'Ü', 'ä', 'ö', 'ü', 'ë', 'ï', 'â', 'ô', 'û', 'ê', ', 'Ù', 'Ú', 'á', // C0-CF
NUL, NUL, NUL, NUL, LI0, LI1, LI2, LI3, LI4, LI5, LI6, LI7, LI8, LI9, LIA, LIB, // D0-DF
LAP, LPK, LMN, '-', '+', NUL, '?', '!', DOT, '&', LEA, '→', '▷', '▶', '▼', '♂', // E0-EF
MNY, '×', '.', '/', ',', '♀', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', // F0-FF
@ -253,7 +256,7 @@ public static class StringConverter2
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 10-1F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 20-2F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 30-3F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 40-4F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, RET, NUL, // 40-4F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, TOT, NUL, NUL, // 50-5F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 60-6F
LPO, LKE, '“', '”', NUL, '…', NUL, NUL, NUL, '┌', '─', '┐', '│', '└', '┘', SPH, // 70-7F
@ -262,7 +265,7 @@ public static class StringConverter2
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', // A0-AF
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'à', 'è', 'é', 'ù', 'À', 'Á', // B0-BF
'Ä', 'Ö', 'Ü', 'ä', 'ö', 'ü', 'È', 'É', 'Ì', 'Í', 'Ñ', 'Ò', 'Ó', 'Ù', 'Ú', 'á', // C0-CF
'ì', 'í', 'ñ', 'ò', 'ó', 'ú', NUL, NUL, LI0, LI1, LI2, LI3, LI4, LI5, LI6, NUL, // D0-DF
'ì', 'í', 'ñ', 'ò', 'ó', 'ú', 'º', NUL, LI0, LI1, LI2, LI3, LI4, LI5, LI6, NUL, // D0-DF
LAP, LPK, LMN, '-', '¿', '¡', '?', '!', DOT, '&', LEA, '→', '▷', '▶', '▼', '♂', // E0-EF
MNY, '×', '.', '/', ',', '♀', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', // F0-FF
];
@ -281,16 +284,20 @@ public static class StringConverter2
}
}
// Control codes used in Randy's mail
private const char NIS = '㋥'; // "に " (particle ni)
private const char NOS = '㋨'; // "の " (particle no)
public static ReadOnlySpan<char> TableJP =>
[
NUL, NUL, NUL, NUL, NUL, 'ガ', 'ギ', 'グ', 'ゲ', 'ゴ', 'ザ', 'ジ', 'ズ', 'ゼ', 'ゾ', 'ダ', // 00-0F
'ヂ', 'ヅ', 'デ', 'ド', NUL, NUL, NUL, NUL, NUL, 'バ', 'ビ', 'ブ', 'ボ', NUL, NUL, NUL, // 10-1F
NUL, NUL, NUL, NUL, NUL, NUL, 'が', 'ぎ', 'ぐ', 'げ', 'ご', 'ざ', 'じ', 'ず', 'ぜ', 'ぞ', // 20-2F
'ヂ', 'ヅ', 'デ', 'ド', NUL, NUL, NUL, NUL, NUL, 'バ', 'ビ', 'ブ', 'ボ', NIS, NUL, NUL, // 10-1F
NUL, NUL, NUL, NUL, NUL, NOS, 'が', 'ぎ', 'ぐ', 'げ', 'ご', 'ざ', 'じ', 'ず', 'ぜ', 'ぞ', // 20-2F
'だ', 'ぢ', 'づ', 'で', 'ど', NUL, NUL, NUL, NUL, NUL, 'ば', 'び', 'ぶ', 'ベ', 'ぼ', NUL, // 30-3F
'パ', 'ピ', 'プ', 'ポ', 'ぱ', 'ぴ', 'ぷ', 'ペ', 'ぽ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 40-4F
'パ', 'ピ', 'プ', 'ポ', 'ぱ', 'ぴ', 'ぷ', 'ペ', 'ぽ', NUL, NUL, NUL, NUL, NUL, RET, NUL, // 40-4F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, TOT, NUL, NUL, // 50-5F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, // 60-6F
'「', '」', '『', '』', '・', '⋯', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, SPF, // 70-7F
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, 'ぃ', 'ぅ', // 60-6F
'「', '」', '『', '』', '・', '⋯', 'ぁ', 'ぇ', 'ぉ', NUL, NUL, NUL, NUL, NUL, NUL, SPF, // 70-7F
'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', 'ス', 'セ', 'ソ', 'タ', // 80-8F
'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ', 'ヌ', 'ネ', '', 'ハ', 'ヒ', 'フ', 'ホ', 'マ', 'ミ', 'ム', // 90-9F
'メ', 'モ', 'ヤ', 'ユ', 'ヨ', 'ラ', 'ル', 'レ', 'ロ', 'ワ', 'ヲ', 'ン', 'ッ', 'ャ', 'ュ', 'ョ', // A0-AF
@ -298,7 +305,7 @@ public static class StringConverter2
'た', 'ち', 'つ', 'て', 'と', 'な', 'に', 'ぬ', 'ね', 'の', 'は', 'ひ', 'ふ', 'ヘ', 'ほ', 'ま', // C0-CF
'み', 'む', 'め', 'も', 'や', 'ゆ', 'よ', 'ら', 'リ', 'る', 'れ', 'ろ', 'わ', 'を', 'ん', 'っ', // D0-DF
'ゃ', 'ゅ', 'ょ', 'ー', '゚', '゙', '', '', '。', 'ァ', 'ゥ', 'ェ', NUL, NUL, NUL, '♂', // E0-EF
MNY, NUL, '', '', 'ォ', '♀', '', '', '', '', '', '', '', '', '', '', // F0-FF
MNY, '×', '', '', 'ォ', '♀', '', '', '', '', '', '', '', '', '', '', // F0-FF
];
#endregion
@ -330,7 +337,7 @@ public static class StringConverter2
}
private static bool TryGetLigatureIndex(char c, out int index) => -1 != (index = LigatureList.IndexOf(c));
private static ReadOnlySpan<char> LigatureList => [LI0, LI1, LI2, LI3, LI4, LI5, LI6, LI7, LI8, LI8, LI9, LIA, LIB];
private static ReadOnlySpan<char> LigatureList => [LI0, LI1, LI2, LI3, LI4, LI5, LI6, LI7, LI8, LI9, LIA, LIB];
private static char GetLigature(int ligatureIndex) => LigatureList[ligatureIndex];
public static int DeflateLigatures(ReadOnlySpan<char> value, Span<char> result, int language)
@ -384,4 +391,69 @@ public static class StringConverter2
}
return index;
}
/// <summary>
/// Converts foreign Mail from the language-unaware encoding used for English Gold/Silver back to its original, language-aware encoding.
/// </summary>
/// <param name="data">Encoded data.</param>
/// <param name="language">Mail language.</param>
public static void DecodeMailEnglishGS(Span<byte> data, int language)
{
if (language is (int)LanguageID.French or (int)LanguageID.German)
DecodeMailFG(data);
else if (language is (int)LanguageID.Italian or (int)LanguageID.Spanish)
RemapMailIS(data);
}
/// <summary>
/// Converts foreign Mail from its original, language-aware encoding to the language-unaware encoding used for English Gold/Silver.
/// </summary>
/// <param name="data">Decoded data.</param>
/// <param name="language">Mail language.</param>
public static void EncodeMailEnglishGS(Span<byte> data, int language)
{
if (language is (int)LanguageID.French or (int)LanguageID.German)
EncodeMailFG(data);
else if (language is (int)LanguageID.Italian or (int)LanguageID.Spanish)
RemapMailIS(data);
}
// Remap 's, swap c' d' j' with unused spaces
// - English: 0xCD-CF (unused spaces), 0xD4-D6 ('s 't 'v), 0xDC (unused space)
// - French/German: 0xCD-CF (unused spaces), 0xD4-D6 (c' d' j'), 0xDC ('s)
private static void DecodeMailFG(Span<byte> data)
{
for (int i = 0; i < data.Length; i++)
{
var b = data[i];
if (b == 0xD4)
data[i] = 0xDC; // 's
else if (b is >= 0xCD and <= 0xCF)
data[i] += 0xD4 - 0xCD; // c' d' j' (shift up)
}
}
private static void EncodeMailFG(Span<byte> data)
{
for (int i = 0; i < data.Length; i++)
{
var b = data[i];
if (b == 0xDC)
data[i] = 0xD4; // 's
else if (b is >= 0xD4 and <= 0xD6)
data[i] -= 0xD4 - 0xCD; // c' d' j' (shift down)
}
}
// Swap upper/lower halves of 0xD0-DF
// - English: 0xD0-D7 (ligatures), 0xD8-DF (unused spaces)
// - Italian/Spanish: 0xD0-D7 (accented letters), 0xD8-DF (ligatures)
private static void RemapMailIS(Span<byte> data)
{
for (int i = 0; i < data.Length; i++)
{
if ((data[i] & 0xF0) == 0xD0)
data[i] ^= 0x08;
}
}
}

View file

@ -8,6 +8,10 @@ namespace PKHeX.Core;
/// </summary>
public static class StringConverter2KOR
{
// Mail
public const byte LineBreakCode = 0x59;
public const char LineBreak = StringConverter2.LineBreak;
/// <summary>
/// Checks if any of the characters inside <see cref="str"/> are from the special Korean codepoint pages.
/// </summary>
@ -453,6 +457,7 @@ public static class StringConverter2KOR
// In transporter's code, none of these glyphs are legitimately accessible via keyboard.
private const char NUL = NULL;
private const char RET = LineBreak; // Mail, NUL in Transporter
private static ReadOnlySpan<char> Table0 =>
[
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
@ -460,7 +465,7 @@ public static class StringConverter2KOR
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, RET, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, ' ',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',

View file

@ -70,6 +70,12 @@ public sealed class SAV2Stadium : SAV_STADIUM, IBoxDetailName
ClearBoxes();
}
protected sealed override void SetChecksums()
{
base.SetChecksums();
SetMailChecksums();
}
protected override bool GetIsBoxChecksumValid(int box)
{
var boxOfs = GetBoxOffset(box) - ListHeaderSizeBox;
@ -200,6 +206,27 @@ public sealed class SAV2Stadium : SAV_STADIUM, IBoxDetailName
private static bool IsStadiumJ(ReadOnlySpan<byte> data) => StadiumUtil.IsMagicPresentAbsolute(data, BoxStart + BoxSizeJ - ListFooterSize, MAGIC_FOOTER) != StadiumSaveType.None;
private static bool IsStadiumU(ReadOnlySpan<byte> data) => StadiumUtil.IsMagicPresentAbsolute(data, BoxStart + BoxSizeU - ListFooterSize, MAGIC_FOOTER) != StadiumSaveType.None;
#region Mail Box
public static int MailboxBlockOffset(int language) => language == (int)LanguageID.Japanese ? 0x6530 : 0x62D8;
public static int MailboxHeldBlockOffset(int language) => language == (int)LanguageID.Japanese ? 0x6D6C : 0x6C0E;
public int MailboxBlockSize => 2 + (MailboxMailCount * Mail2.GetMailSize(Language)) + ListFooterSize;
public int MailboxHeldBlockSize => 2 + (MailboxHeldMailCount * Mail2.GetMailSize(Language)) + ListFooterSize;
public const int MailboxMailCount = 50;
public const int MailboxHeldMailCount = 30;
private void SetMailChecksums()
{
var ofs = MailboxBlockOffset(Language);
var size = MailboxBlockSize - 2;
var chk = Checksums.CheckSum16(new ReadOnlySpan<byte>(Data, ofs, size));
WriteUInt16BigEndian(Data.AsSpan(ofs + size), chk);
var ofsHeld = MailboxHeldBlockOffset(Language);
var sizeHeld = MailboxHeldBlockSize - 2;
var chkHeld = Checksums.CheckSum16(new ReadOnlySpan<byte>(Data, ofsHeld, sizeHeld));
WriteUInt16BigEndian(Data.AsSpan(ofsHeld + sizeHeld), chkHeld);
}
#endregion
private static bool GetIsSwap(ReadOnlySpan<byte> data, bool japanese)
{
var teamSwap = StadiumUtil.IsMagicPresentSwap(data, TeamSize, MAGIC_FOOTER, 1);

View file

@ -53,7 +53,7 @@ public abstract class SAV_STADIUM : SaveFile, ILangDeviantSave
public sealed override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid";
protected abstract void SetBoxChecksum(int box);
protected abstract bool GetIsBoxChecksumValid(int box);
protected sealed override void SetChecksums() => SetBoxChecksums();
protected override void SetChecksums() => SetBoxChecksums();
protected abstract void SetBoxMetadata(int box);
protected void SetBoxChecksums()

View file

@ -3,120 +3,221 @@ using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
// warning: international only
public sealed class Mail2 : MailDetail
{
private readonly int Language;
private bool Japanese => Language == 1;
private bool Korean => Language == (int)LanguageID.Korean;
private readonly bool EnglishGS;
private readonly bool Japanese;
private readonly bool Korean;
// structure:
private const int LINE_LENGTH = 0x10;
private const int MESSAGE_LENGTH = LINE_LENGTH + LINE_LENGTH + 1; // each line has a single char at end
private const int OFS_AUTHOR = MESSAGE_LENGTH;
private const int OFS_AUTHOR_NATION = OFS_AUTHOR + AUTHOR_LENGTH + 1;
private const int OFS_AUTHOR_ID = OFS_AUTHOR_NATION + 2;
private const int OFS_APPEAR = OFS_AUTHOR_ID + 2;
private const int OFS_TYPE = OFS_APPEAR + 1;
private const int SIZE = OFS_TYPE + 1;
private int LINE_LENGTH => Korean ? 0x20 : 0x10;
private int MESSAGE_LENGTH => LINE_LENGTH + 1 + LINE_LENGTH; // line break in the middle
private int OFS_AUTHOR => MESSAGE_LENGTH;
private int OFS_AUTHOR_NATION => OFS_AUTHOR + AUTHOR_LENGTH; // not in Japanese/Korean
private int OFS_AUTHOR_ID => OFS_AUTHOR_NATION + ((Japanese || Korean) ? 0 : 2);
private int OFS_APPEAR => OFS_AUTHOR_ID + 2;
private int OFS_TYPE => OFS_APPEAR + 1;
private int SIZE => OFS_TYPE + 1;
private const int SIZE_J = 0x2A;
private const int SIZE_U = 0x2F;
private const int SIZE_K = 0x4F;
private const int COUNT_PARTY = 6;
private const int COUNT_MAILBOX = 10;
private const int COUNT_MAILBOX_STADIUM2 = SAV2Stadium.MailboxMailCount; // 50
private const int COUNT_PARTY_STADIUM2 = SAV2Stadium.MailboxHeldMailCount; // 30
private const int AUTHOR_LENGTH = 7;
private int AUTHOR_LENGTH => Japanese ? 5 : (Korean ? 10 : 8);
public Mail2(SAV2 sav, int index) : base(sav.Data.AsSpan(GetMailOffset(index), 0x2F).ToArray(), GetMailOffset(index))
private byte LineBreakCode => Korean ? StringConverter2KOR.LineBreakCode : StringConverter2.LineBreakCode;
private char LineBreak => Korean ? StringConverter2KOR.LineBreak : StringConverter2.LineBreak;
public Mail2(SAV2 sav, int index) : base(sav.Data.AsSpan(GetMailOffset(index, GetMailSize(sav.Language)), GetMailSize(sav.Language)).ToArray(), GetMailOffset(index, GetMailSize(sav.Language)))
{
Language = sav.Language;
EnglishGS = sav.Version != GameVersion.C && sav.Language == (int)LanguageID.English;
Japanese = sav.Japanese;
Korean = sav.Korean;
}
private static int GetMailOffset(int index)
public Mail2(SAV2Stadium sav, int index) : base(sav.Data.AsSpan(GetMailOffsetStadium2(index, GetMailSize(sav.Language)), GetMailSize(sav.Language)).ToArray(), GetMailOffsetStadium2(index, GetMailSize(sav.Language)))
{
EnglishGS = false;
Japanese = sav.Japanese;
Korean = sav.Korean;
}
public static int GetMailSize(int language) => language switch
{
(int)LanguageID.Japanese => SIZE_J,
(int)LanguageID.Korean => SIZE_K,
_ => SIZE_U,
};
#region Offsets
public static int GetMailboxOffset(int language) => 0x600 + (COUNT_PARTY * 2) * GetMailSize(language);
private static int GetMailOffset(int index, int size)
{
if (index < COUNT_PARTY)
return GetPartyMailOffset(index);
return GetMailboxMailOffset(index - COUNT_PARTY);
return GetPartyMailOffset(index, size);
return GetMailboxMailOffset(index - COUNT_PARTY, size);
}
private static int GetPartyMailOffset(int index)
private static int GetPartyMailOffset(int index, int size)
{
if ((uint)index >= COUNT_PARTY)
throw new ArgumentOutOfRangeException(nameof(index));
return (index * SIZE) + 0x600;
return (index * size) + 0x600;
}
private static int GetMailboxMailOffset(int index)
private static int GetMailboxMailOffset(int index, int size)
{
if ((uint)index >= COUNT_MAILBOX)
throw new ArgumentOutOfRangeException(nameof(index));
return (index * SIZE) + 0x835;
return (index * size) + (0x600 + (COUNT_PARTY * 2) * size + 1);
}
public static int GetMailboxOffsetStadium2(int language) => SAV2Stadium.MailboxBlockOffset(language) + 1;
private static int GetMailOffsetStadium2(int index, int size)
{
if (index < COUNT_PARTY_STADIUM2)
return GetHeldMailOffsetStadium2(index, size);
return GetMailboxMailOffsetStadium2(index - COUNT_PARTY_STADIUM2, size);
}
private static int GetMailboxMailOffsetStadium2(int index, int size)
{
if ((uint)index >= COUNT_MAILBOX_STADIUM2)
throw new ArgumentOutOfRangeException(nameof(index));
return (index * size) + SAV2Stadium.MailboxBlockOffset(size == SIZE_J ? (int)LanguageID.Japanese : (int)LanguageID.English) + 2;
}
private static int GetHeldMailOffsetStadium2(int index, int size)
{
if ((uint)index >= COUNT_PARTY_STADIUM2)
throw new ArgumentOutOfRangeException(nameof(index));
return (index * size) + SAV2Stadium.MailboxHeldBlockOffset(size == SIZE_J ? (int)LanguageID.Japanese : (int)LanguageID.English) + 2;
}
#endregion
private string GetString(Span<byte> span)
{
if (Korean)
return StringConverter2KOR.GetString(span);
var result = StringConverter2.GetString(span, Language);
if (EnglishGS)
StringConverter2.DecodeMailEnglishGS(span, AuthorLanguage);
var result = StringConverter2.GetString(span, AuthorLanguage);
if (!Korean)
result = StringConverter2.InflateLigatures(result, Language);
result = StringConverter2.InflateLigatures(result, AuthorLanguage);
return result;
}
private void SetString(Span<byte> span, ReadOnlySpan<char> value, int maxLength)
private void SetString(Span<byte> span, ReadOnlySpan<char> value, int maxLength, StringConverterOption option = StringConverterOption.Clear50)
{
if (Korean)
{
StringConverter2KOR.SetString(span, value, maxLength);
StringConverter2KOR.SetString(span, value, maxLength, option);
return;
}
Span<char> deflated = stackalloc char[maxLength];
int len = StringConverter2.DeflateLigatures(value, deflated, Language);
StringConverter2.SetString(span, deflated[..len], maxLength, Language);
int len = StringConverter2.DeflateLigatures(value, deflated, AuthorLanguage);
StringConverter2.SetString(span, deflated[..len], maxLength, AuthorLanguage, option);
if (EnglishGS)
StringConverter2.EncodeMailEnglishGS(span, AuthorLanguage);
}
public string Line1
public override string GetMessage(bool isLastLine)
{
get => GetString(Data.AsSpan(0, LINE_LENGTH - 1));
set
{
var span = Data.AsSpan(0, LINE_LENGTH);
SetString(span[..^1], value, LINE_LENGTH - 1);
span[^1] = 0x4E;
}
var span = Data.AsSpan(0, MESSAGE_LENGTH);
var index = span.IndexOf(LineBreakCode);
return index == -1 ? string.Empty : GetString(isLastLine ? span[(index + 1)..] : span[..index]);
}
public string Line2
public override void SetMessage(string line1, string line2, bool userEntered)
{
get => GetString(Data.AsSpan(LINE_LENGTH, LINE_LENGTH - 1));
set
if (IsEmpty == true && line1 == string.Empty && line2 == string.Empty)
{
var span = Data.AsSpan(LINE_LENGTH, LINE_LENGTH);
SetString(span[..^1], value, LINE_LENGTH - 1);
span[^1] = 0x4E;
Data.AsSpan(0, MESSAGE_LENGTH).Clear();
return;
}
}
public override string GetMessage(bool isLastLine) => isLastLine ? Line2 : Line1;
public override void SetMessage(string line1, string line2) => (Line1, Line2) = (line1, line2);
if (Korean || !userEntered)
{
// Japanese/international Randy's mail has a line break in different place.
// Korean always puts a line break after the first line, even if it's not full.
var span = Data.AsSpan(0, MESSAGE_LENGTH);
var message = string.Join(LineBreak, line1, line2);
SetString(span, message, MESSAGE_LENGTH,
// Randy's mail can have trash bytes after the end of the message, so don't clear it.
userEntered ? StringConverterOption.Clear50 : StringConverterOption.None);
return;
}
// Japanese/international user-entered mail always has a line break at index 0x10
var span1 = Data.AsSpan(0, LINE_LENGTH);
SetString(span1, line1, LINE_LENGTH);
if (line2 != string.Empty) // Pad the first line with spaces if needed
span1.Replace<byte>(0x50, 0x7F);
Data[LINE_LENGTH] = LineBreakCode;
var span2 = Data.AsSpan(LINE_LENGTH + 1, LINE_LENGTH);
SetString(span2, line2, LINE_LENGTH);
}
public override string AuthorName
{
get => GetString(Data.AsSpan(OFS_AUTHOR, AUTHOR_LENGTH + 1));
get => GetString(Data.AsSpan(OFS_AUTHOR, AUTHOR_LENGTH));
set => SetString(Data.AsSpan(OFS_AUTHOR, AUTHOR_LENGTH), value,
// Japanese/Korean don't have an extra byte for the terminator.
AUTHOR_LENGTH - ((Japanese || Korean) ? 0 : 1),
// Randy's mail can have trash bytes after the end of the OT, so don't clear it.
UserEntered ? StringConverterOption.Clear50 : StringConverterOption.None);
}
public override byte AuthorLanguage
{
get
{
if (Japanese)
return (byte)LanguageID.Japanese;
if (Korean)
return (byte)LanguageID.Korean;
return (byte)(Nationality switch
{
0x0000 => LanguageID.English,
0x8485 => LanguageID.French, // "EF"
0x8486 => LanguageID.German, // "EG"
0x8488 => LanguageID.Italian, // "EI"
0x8492 => LanguageID.Spanish, // "ES"
_ => LanguageID.English,
});
}
set
{
SetString(Data.AsSpan(OFS_AUTHOR, 8), value, AUTHOR_LENGTH);
Nationality = 0; // ??
if (Japanese || Korean)
return;
Nationality = (LanguageID)value switch
{
LanguageID.English => 0x0000,
LanguageID.French => 0x8485, // "EF"
LanguageID.German => 0x8486, // "EG"
LanguageID.Italian => 0x8488, // "EI"
LanguageID.Spanish => 0x8492, // "ES"
_ => Nationality, // Invalid, don't change.
};
}
}
public ushort Nationality
{
get => ReadUInt16BigEndian(Data.AsSpan(OFS_AUTHOR_NATION, 2));
set => WriteUInt16LittleEndian(Data.AsSpan(OFS_AUTHOR_NATION, 2), value);
set => WriteUInt16BigEndian(Data.AsSpan(OFS_AUTHOR_NATION, 2), value);
}
public override ushort AuthorTID
{
get => ReadUInt16BigEndian(Data.AsSpan(OFS_AUTHOR_ID + 2));
get => ReadUInt16BigEndian(Data.AsSpan(OFS_AUTHOR_ID, 2));
set => WriteUInt16BigEndian(Data.AsSpan(OFS_AUTHOR_ID, 2), value);
}
@ -131,5 +232,27 @@ public sealed class Mail2 : MailDetail
_ => null,
};
public override bool UserEntered
{
get
{
// Blank mail, prepare for writing
if (MailType == 0)
return true;
// Japanese/international user-entered mail always has a line break at index 0x10
// Randy's mail instead has it at index 0x0D (Japanese) or 0x0F (international)
if (!Korean)
return Data[LINE_LENGTH] == LineBreakCode;
// Korean mail can have a line break anywhere, so look at trash bytes instead
// User-entered mail always fills the message buffer with 0x50
// Randy's mail has trash bytes after the terminator, so check for any trash bytes
var span = Data.AsSpan(0, MESSAGE_LENGTH);
var terminator = span.IndexOf<byte>(0x50);
return terminator == -1 || !span[terminator..].ContainsAnyExcept<byte>(0x50);
}
}
public override void SetBlank() => Data.AsSpan(0, SIZE).Clear();
}

View file

@ -16,7 +16,7 @@ public abstract class MailDetail
public virtual void CopyTo(PK5 pk5) { }
public virtual string GetMessage(bool isLastLine) => string.Empty;
public virtual ushort GetMessage(int index1, int index2) => 0;
public virtual void SetMessage(string line1, string line2) { }
public virtual void SetMessage(string line1, string line2, bool userEntered) { }
public virtual void SetMessage(int index1, int index2, ushort value) { }
public virtual string AuthorName { get; set; } = string.Empty;
public virtual ushort AuthorTID { get; set; }
@ -27,5 +27,6 @@ public abstract class MailDetail
public virtual ushort AppearPKM { get; set; }
public virtual int MailType { get; set; }
public abstract bool? IsEmpty { get; } // true: empty, false: legal mail, null: illegal mail
public virtual bool UserEntered { get; }
public virtual void SetBlank() { }
}

View file

@ -1233,8 +1233,8 @@ public partial class SAVEditor : UserControl, ISlotViewer<PictureBox>, ISaveFile
B_OpenChatterEditor.Visible = sav is SAV4 or SAV5;
B_OpenSealStickers.Visible = B_Poffins.Visible = sav is SAV8BS;
B_OpenApricorn.Visible = sav is SAV4HGSS;
B_OpenRTCEditor.Visible = sav.Generation == 2 || sav is IGen3Hoenn;
B_MailBox.Visible = sav is SAV2 or SAV3 or SAV4 or SAV5;
B_OpenRTCEditor.Visible = (sav.Generation == 2 && sav is not SAV2Stadium) || sav is IGen3Hoenn;
B_MailBox.Visible = sav is SAV2 or SAV2Stadium or SAV3 or SAV4 or SAV5;
B_Raids.Visible = sav is SAV8SWSH or SAV9SV;
B_RaidsSevenStar.Visible = sav is SAV9SV;

View file

@ -58,6 +58,7 @@ namespace PKHeX.WinForms
NUD_Message01 = new System.Windows.Forms.NumericUpDown();
NUD_Message00 = new System.Windows.Forms.NumericUpDown();
GB_Author = new System.Windows.Forms.GroupBox();
CHK_UserEntered = new System.Windows.Forms.CheckBox();
CB_AuthorVersion = new System.Windows.Forms.ComboBox();
CB_AuthorLang = new System.Windows.Forms.ComboBox();
Label_OTGender = new System.Windows.Forms.Label();
@ -200,21 +201,21 @@ namespace PKHeX.WinForms
//
TB_MessageBody21.Location = new System.Drawing.Point(10, 27);
TB_MessageBody21.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
TB_MessageBody21.MaxLength = 16;
TB_MessageBody21.MaxLength = 32;
TB_MessageBody21.Name = "TB_MessageBody21";
TB_MessageBody21.Size = new System.Drawing.Size(186, 23);
TB_MessageBody21.Size = new System.Drawing.Size(216, 23);
TB_MessageBody21.TabIndex = 7;
TB_MessageBody21.Text = "MMMMMMMMMMMMMMMM";
TB_MessageBody21.Text = "";
//
// TB_MessageBody22
//
TB_MessageBody22.Location = new System.Drawing.Point(10, 61);
TB_MessageBody22.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
TB_MessageBody22.MaxLength = 16;
TB_MessageBody22.MaxLength = 32;
TB_MessageBody22.Name = "TB_MessageBody22";
TB_MessageBody22.Size = new System.Drawing.Size(186, 23);
TB_MessageBody22.Size = new System.Drawing.Size(216, 23);
TB_MessageBody22.TabIndex = 8;
TB_MessageBody22.Text = "MMMMMMMMMMMMMMMM";
TB_MessageBody22.Text = "";
//
// TB_AuthorName
//
@ -271,11 +272,11 @@ namespace PKHeX.WinForms
NUD_BoxSize.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
NUD_BoxSize.Location = new System.Drawing.Point(105, 464);
NUD_BoxSize.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
NUD_BoxSize.Maximum = new decimal(new int[] { 10, 0, 0, 0 });
NUD_BoxSize.Maximum = new decimal(new int[] { 50, 0, 0, 0 });
NUD_BoxSize.Name = "NUD_BoxSize";
NUD_BoxSize.Size = new System.Drawing.Size(43, 23);
NUD_BoxSize.TabIndex = 14;
NUD_BoxSize.Value = new decimal(new int[] { 10, 0, 0, 0 });
NUD_BoxSize.Value = new decimal(new int[] { 50, 0, 0, 0 });
NUD_BoxSize.ValueChanged += NUD_BoxSize_ValueChanged;
//
// GB_MessageTB
@ -286,7 +287,7 @@ namespace PKHeX.WinForms
GB_MessageTB.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
GB_MessageTB.Name = "GB_MessageTB";
GB_MessageTB.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
GB_MessageTB.Size = new System.Drawing.Size(208, 96);
GB_MessageTB.Size = new System.Drawing.Size(238, 96);
GB_MessageTB.TabIndex = 15;
GB_MessageTB.TabStop = false;
GB_MessageTB.Text = "Message";
@ -447,6 +448,7 @@ namespace PKHeX.WinForms
//
// GB_Author
//
GB_Author.Controls.Add(CHK_UserEntered);
GB_Author.Controls.Add(CB_AuthorVersion);
GB_Author.Controls.Add(CB_AuthorLang);
GB_Author.Controls.Add(Label_OTGender);
@ -462,6 +464,16 @@ namespace PKHeX.WinForms
GB_Author.TabStop = false;
GB_Author.Text = "Author";
//
// CHK_UserEntered
//
CHK_UserEntered.AutoSize = true;
CHK_UserEntered.Location = new System.Drawing.Point(159, 62);
CHK_UserEntered.Name = "CHK_UserEntered";
CHK_UserEntered.Size = new System.Drawing.Size(94, 19);
CHK_UserEntered.TabIndex = 61;
CHK_UserEntered.Text = "User-Entered";
CHK_UserEntered.UseVisualStyleBackColor = true;
//
// CB_AuthorVersion
//
CB_AuthorVersion.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
@ -1002,5 +1014,6 @@ namespace PKHeX.WinForms
private System.Windows.Forms.Button B_PartyDown;
private System.Windows.Forms.Button B_BoxDown;
private System.Windows.Forms.Button B_BoxUp;
private System.Windows.Forms.CheckBox CHK_UserEntered;
}
}

View file

@ -47,16 +47,18 @@ public partial class SAV_MailBox : Form
AppearPKMs = [CB_AppearPKM1, CB_AppearPKM2, CB_AppearPKM3];
Miscs = [NUD_Misc1, NUD_Misc2, NUD_Misc3];
NUD_BoxSize.Visible = L_BoxSize.Visible = Generation == 2;
NUD_BoxSize.Visible = L_BoxSize.Visible = CHK_UserEntered.Visible = Generation == 2;
GB_MessageTB.Visible = Generation == 2;
GB_MessageNUD.Visible = Generation != 2;
Messages[0][3].Visible = Messages[1][3].Visible = Messages[2][3].Visible = Generation is 4 or 5;
NUD_AuthorSID.Visible = Generation != 2;
Label_OTGender.Visible = CB_AuthorLang.Visible = CB_AuthorVersion.Visible = Generation is 4 or 5;
Label_OTGender.Visible = CB_AuthorVersion.Visible = Generation is 4 or 5;
CB_AuthorLang.Visible = Generation is 2 or 4 or 5;
L_AppearPKM.Visible = AppearPKMs[0].Visible = Generation != 5;
AppearPKMs[1].Visible = AppearPKMs[2].Visible = Generation == 4;
NUD_MessageEnding.Visible = Generation == 5;
L_MiscValue.Visible = NUD_Misc1.Visible = NUD_Misc2.Visible = NUD_Misc3.Visible = Generation == 5;
GB_PKM.Visible = B_PartyUp.Enabled = B_PartyDown.Enabled = SAV is not SAV2Stadium;
for (int i = p.Count; i < 6; i++)
PKMNUDs[i].Visible = PKMLabels[i].Visible = PKMHeldItems[i].Visible = false;
@ -76,9 +78,20 @@ public partial class SAV_MailBox : Form
for (int i = 0; i < m.Length; i++)
m[i] = new Mail2(sav2, i);
NUD_BoxSize.Value = SAV.Data[0x834];
NUD_BoxSize.Value = SAV.Data[Mail2.GetMailboxOffset(SAV.Language)];
MailItemID = [0x9E, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD];
PartyBoxCount = 6;
NUD_BoxSize.Maximum = 10;
break;
case SAV2Stadium sav2Stadium:
m = new Mail2[SAV2Stadium.MailboxHeldMailCount + SAV2Stadium.MailboxMailCount];
for (int i = 0; i < m.Length; i++)
m[i] = new Mail2(sav2Stadium, i);
NUD_BoxSize.Value = SAV.Data[Mail2.GetMailboxOffsetStadium2(SAV.Language)];
MailItemID = [0x9E, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD];
PartyBoxCount = SAV2Stadium.MailboxHeldMailCount;
NUD_BoxSize.Maximum = SAV2Stadium.MailboxMailCount;
break;
case SAV3 sav3:
m = new Mail3[6 + 10];
@ -138,7 +151,10 @@ public partial class SAV_MailBox : Form
CB_AuthorVersion.Items.Clear();
CB_AuthorVersion.InitializeBinding();
CB_AuthorVersion.DataSource = new BindingSource(vers, null);
}
if (Generation is 2 or 4 or 5)
{
CB_AuthorLang.Items.Clear();
CB_AuthorLang.InitializeBinding();
CB_AuthorLang.DataSource = new BindingSource(GameInfo.LanguageDataSource(SAV.Generation), null);
@ -217,14 +233,22 @@ public partial class SAV_MailBox : Form
{
case 2:
foreach (var n in m) n.CopyTo(SAV);
// duplicate
int ofs = 0x600;
int len = 0x2F * 6;
Array.Copy(SAV.Data, ofs, SAV.Data, ofs + len, len);
ofs += len << 1;
SAV.Data[ofs] = (byte)NUD_BoxSize.Value;
len = (0x2F * 10) + 1;
Array.Copy(SAV.Data, ofs, SAV.Data, ofs + len, len);
if (SAV is SAV2)
{
// duplicate
int ofs = 0x600;
int len = Mail2.GetMailSize(SAV.Language) * 6;
Array.Copy(SAV.Data, ofs, SAV.Data, ofs + len, len);
ofs += len << 1;
SAV.Data[ofs] = (byte)NUD_BoxSize.Value;
len = (Mail2.GetMailSize(SAV.Language) * 10) + 1;
Array.Copy(SAV.Data, ofs, SAV.Data, ofs + len, len);
}
else if (SAV is SAV2Stadium)
{
int ofs = Mail2.GetMailboxOffsetStadium2(SAV.Language);
SAV.Data[ofs] = (byte)NUD_BoxSize.Value;
}
break;
case 3:
foreach (var n in m) n.CopyTo(SAV);
@ -257,7 +281,9 @@ public partial class SAV_MailBox : Form
if (Generation == 2)
{
mail.AppearPKM = species;
mail.SetMessage(TB_MessageBody21.Text, TB_MessageBody22.Text);
mail.SetMessage(TB_MessageBody21.Text, TB_MessageBody22.Text, CHK_UserEntered.Checked);
// ReSharper disable once ConstantNullCoalescingCondition
mail.AuthorLanguage = (byte)((int?)CB_AuthorLang.SelectedValue ?? (int)LanguageID.English);
return;
}
mail.AuthorSID = (ushort)NUD_AuthorSID.Value;
@ -498,6 +524,9 @@ public partial class SAV_MailBox : Form
AppearPKMs[0].SelectedValue = (int)species;
TB_MessageBody21.Text = mail.GetMessage(false);
TB_MessageBody22.Text = mail.GetMessage(true);
CB_AuthorLang.SelectedValue = (int)mail.AuthorLanguage;
CB_AuthorLang.Enabled = CB_AuthorLang.SelectedValue is not (int)LanguageID.Japanese and not (int)LanguageID.Korean;
CHK_UserEntered.Checked = mail.UserEntered;
editing = false;
return;
}