mirror of
https://github.com/kwsch/PKHeX
synced 2025-02-16 13:28:35 +00:00
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:
parent
4aacffb99b
commit
7632230edf
11 changed files with 370 additions and 100 deletions
|
@ -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
|
||||
|
|
|
@ -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, '.', '/', 'ォ', '♀', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', // F0-FF
|
||||
MNY, '×', '.', '/', 'ォ', '♀', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', // F0-FF
|
||||
];
|
||||
|
||||
#endregion
|
||||
|
|
|
@ -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 = '4'; // 's
|
||||
private const char LI5 = '5'; // 't
|
||||
private const char LI6 = '6'; // 'v
|
||||
private const char LI7 = '7'; // 'd
|
||||
private const char LI8 = '8'; // 'l
|
||||
private const char LI9 = '9'; // 'm
|
||||
private const char LIA = 'A'; // 'r
|
||||
private const char LIB = 'B'; // 's
|
||||
private const char LI7 = '7';
|
||||
private const char LI8 = '8';
|
||||
private const char LI9 = '9';
|
||||
private const char LIA = 'A';
|
||||
private const char LIB = 'B';
|
||||
|
||||
/// <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, '.', '/', 'ォ', '♀', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', // F0-FF
|
||||
MNY, '×', '.', '/', 'ォ', '♀', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', // 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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() { }
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = "0123456789ABCDEF";
|
||||
//
|
||||
// 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 = "0123456789ABCDEF";
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue