From d40bfa63c7e81d36a58bcff8a05ce743dfb4a510 Mon Sep 17 00:00:00 2001 From: Kurt Date: Sat, 2 Apr 2022 23:36:56 -0700 Subject: [PATCH] Refactor gen2 mail data structure Now reads/writes correctly in the GUI. Closes #3470 Not going to handle slot swaps not matching the data, as this becomes problematic when cloning slots. Previous commit added the ability to manually move mail slots. --- PKHeX.Core/Saves/Substructures/Mail/Mail2.cs | 187 ++++++++++++------- 1 file changed, 123 insertions(+), 64 deletions(-) diff --git a/PKHeX.Core/Saves/Substructures/Mail/Mail2.cs b/PKHeX.Core/Saves/Substructures/Mail/Mail2.cs index b45e785c4..fbdbc54d6 100644 --- a/PKHeX.Core/Saves/Substructures/Mail/Mail2.cs +++ b/PKHeX.Core/Saves/Substructures/Mail/Mail2.cs @@ -1,69 +1,128 @@ using System; +using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +// warning: international only +public sealed class Mail2 : Mail { - public sealed class Mail2 : Mail + private readonly bool US; + private bool Japanese => !US; + private bool Korean => !US && !Japanese; + + // 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 const int COUNT_PARTY = 6; + private const int COUNT_MAILBOX = 10; + + private const int AUTHOR_LENGTH = 7; + + public Mail2(SAV2 sav, int index) : base(sav.GetData(GetMailOffset(index), 0x2F), GetMailOffset(index)) { - private readonly bool US; - - public Mail2(SAV2 sav, int index) : base(sav.GetData(GetMailOffset(index), 0x2F), GetMailOffset(index)) - { - US = !sav.Japanese && !sav.Korean; - } - - private static int GetMailOffset(int index) - { - return index < 6 ? (index * 0x2F) + 0x600 : ((index - 6) * 0x2F) + 0x835; - } - - public override string GetMessage(bool isLastLine) => US ? StringConverter12.GetString(Data.AsSpan(isLastLine ? 0x11 : 0, 0x10), false) : string.Empty; - - public override void SetMessage(string line1, string line2) - { - if (US) - { - StringConverter12.SetString(Data.AsSpan(0x11, 0x10), line2.AsSpan(), 0x10, false, StringConverterOption.Clear50); - bool hasLine2 = Data[0x11] != StringConverter12.G1TerminatorCode; - var padChar = !hasLine2 ? StringConverterOption.Clear50 : StringConverterOption.Clear7F; // space - StringConverter12.SetString(Data.AsSpan(0, 0x10), line1.AsSpan(), 0x10, false, padChar); - Data[0x10] = 0x4E; - } - } - - public override string AuthorName - { - get => US ? StringConverter12.GetString(Data.AsSpan(0x21, 8), false) : string.Empty; - set - { - if (US) - { - StringConverter12.SetString(Data.AsSpan(0x21, 8), value.AsSpan(), 7, false, StringConverterOption.Clear50); - Data[0x29] = Data[0x2A] = 0; - } - } - } - - public override ushort AuthorTID - { - get => (ushort)(Data[0x2B] << 8 | Data[0x2C]); - set - { - Data[0x2B] = (byte)(value >> 8); - Data[0x2C] = (byte)(value & 0xFF); - } - } - - public override int AppearPKM { get => Data[0x2D]; set => Data[0x2D] = (byte)value; } - public override int MailType { get => Data[0x2E]; set => Data[0x2E] = (byte)value; } - - public override bool? IsEmpty => MailType switch - { - 0 => true, - 0x9E => false, - >= 0xB5 and <= 0xBD => false, - _ => null, - }; - - public override void SetBlank() => (new byte[0x2F]).CopyTo(Data, 0); + US = !sav.Japanese && !sav.Korean; } -} \ No newline at end of file + + private static int GetMailOffset(int index) + { + if (index < COUNT_PARTY) + return GetPartyMailOffset(index); + return GetMailboxMailOffset(index - COUNT_PARTY); + } + + private static int GetPartyMailOffset(int index) + { + if ((uint)index >= COUNT_PARTY) + throw new ArgumentOutOfRangeException(nameof(index)); + return (index * SIZE) + 0x600; + } + + private static int GetMailboxMailOffset(int index) + { + if ((uint)index >= COUNT_MAILBOX) + throw new ArgumentOutOfRangeException(nameof(index)); + return (index * SIZE) + 0x835; + } + + private string GetString(Span asSpan) + { + if (Korean) + return StringConverter2KOR.GetString(asSpan); + return StringConverter12.GetString(asSpan, Japanese); + } + + private void SetString(Span asSpan, ReadOnlySpan value, int maxLength) + { + if (Korean) + StringConverter2KOR.SetString(asSpan, value, maxLength); + else + StringConverter12.SetString(asSpan, value, maxLength, Japanese); + } + + public string Line1 + { + get => GetString(Data.AsSpan(0, LINE_LENGTH - 1)); + set + { + var span = Data.AsSpan(0, LINE_LENGTH); + SetString(span[..^1], value.AsSpan(), LINE_LENGTH - 1); + span[^1] = 0x4E; + } + } + + public string Line2 + { + get => GetString(Data.AsSpan(LINE_LENGTH, LINE_LENGTH - 1)); + set + { + var span = Data.AsSpan(LINE_LENGTH, LINE_LENGTH); + SetString(span[..^1], value.AsSpan(), LINE_LENGTH - 1); + span[^1] = 0x4E; + } + } + + public override string GetMessage(bool isLastLine) => isLastLine ? Line2 : Line1; + public override void SetMessage(string line1, string line2) => (Line1, Line2) = (line1, line2); + + public override string AuthorName + { + get => GetString(Data.AsSpan(OFS_AUTHOR, AUTHOR_LENGTH + 1)); + set + { + SetString(Data.AsSpan(OFS_AUTHOR, 8), value.AsSpan(), AUTHOR_LENGTH); + Nationality = 0; // ?? + } + } + + public ushort Nationality + { + get => ReadUInt16BigEndian(Data.AsSpan(OFS_AUTHOR_NATION, 2)); + set => WriteUInt16LittleEndian(Data.AsSpan(OFS_AUTHOR_NATION, 2), value); + } + + public override ushort AuthorTID + { + get => ReadUInt16BigEndian(Data.AsSpan(OFS_AUTHOR_ID + 2)); + set => WriteUInt16BigEndian(Data.AsSpan(OFS_AUTHOR_ID, 2), value); + } + + public override int AppearPKM { get => Data[OFS_APPEAR]; set => Data[OFS_APPEAR] = (byte)value; } + public override int MailType { get => Data[OFS_TYPE]; set => Data[OFS_TYPE] = (byte)value; } + + public override bool? IsEmpty => MailType switch + { + 0 => true, + 0x9E => false, + >= 0xB5 and <= 0xBD => false, + _ => null, + }; + + public override void SetBlank() => Data.AsSpan(0, SIZE).Clear(); +}