diff --git a/PKHeX.Core/Saves/SAV1.cs b/PKHeX.Core/Saves/SAV1.cs index e12967e46..6c1c50e2d 100644 --- a/PKHeX.Core/Saves/SAV1.cs +++ b/PKHeX.Core/Saves/SAV1.cs @@ -267,8 +267,8 @@ namespace PKHeX.Core public int PikaBeachScore { - get => BigEndian.BCDToInt32_LE(Data, Offsets.PikaBeachScore, 2); - set => SetData(BigEndian.Int32ToBCD_LE(Math.Min(9999, value), 2), Offsets.PikaBeachScore); + get => BinaryCodedDecimal.ToInt32LE(Data, Offsets.PikaBeachScore, 2); + set => BinaryCodedDecimal.WriteBytesLE(Data.AsSpan(Offsets.PikaBeachScore, 2), Math.Min(9999, value)); } public override string PlayTimeString => !PlayedMaximum ? base.PlayTimeString : $"{base.PlayTimeString} {Checksums.CRC16_CCITT(Data):X4}"; @@ -353,21 +353,21 @@ namespace PKHeX.Core public override uint Money { - get => (uint)BigEndian.BCDToInt32(Data, Offsets.Money, 3); + get => (uint)BinaryCodedDecimal.ToInt32BE(Data, Offsets.Money, 3); set { value = (uint)Math.Min(value, MaxMoney); - BigEndian.Int32ToBCD((int)value, 3).CopyTo(Data, Offsets.Money); + BinaryCodedDecimal.WriteBytesBE(Data.AsSpan(Offsets.Money, 3), (int)value); } } public uint Coin { - get => (uint)BigEndian.BCDToInt32(Data, Offsets.Coin, 2); + get => (uint)BinaryCodedDecimal.ToInt32BE(Data, Offsets.Coin, 2); set { value = (ushort)Math.Min(value, MaxCoins); - BigEndian.Int32ToBCD((int)value, 2).CopyTo(Data, Offsets.Coin); + BinaryCodedDecimal.WriteBytesBE(Data.AsSpan(Offsets.Coin, 2), (int)value); } } diff --git a/PKHeX.Core/Util/BigEndian.cs b/PKHeX.Core/Util/BigEndian.cs index 8cc557f60..a57e8f4f2 100644 --- a/PKHeX.Core/Util/BigEndian.cs +++ b/PKHeX.Core/Util/BigEndian.cs @@ -128,79 +128,5 @@ namespace PKHeX.Core data[2 + i] = tmp1; } } - - /// - /// Returns a 32-bit signed integer converted from bytes in a Binary Coded Decimal format byte array. - /// - /// Input byte array to read from. - /// Offset to start reading at. - /// Length of array to read. - public static int BCDToInt32(byte[] input, int offset, int length) - { - int result = 0; - for (int i = offset; i < offset + length; i++) - { - byte p = input[i]; - result *= 100; - result += 10 * (p >> 4); - result += p & 0xf; - } - return result; - } - - /// - /// Returns the specified 32-bit signed integer value as an array of Binary Coded Decimal format bytes. - /// - /// 32-bit signed integer to convert. - /// Desired size of returned array. - public static byte[] Int32ToBCD(int input, int size) - { - byte[] result = new byte[size]; - for (int i = 0; i < size; i++) - { - int p = input%100; - input /= 100; - result[size - i - 1] = (byte)(p/10 << 4 | p%10); - } - return result; - } - - /// - /// Returns a 16-bit signed integer converted from bytes in a Binary Coded Decimal format byte array. - /// - /// Little Endian instead of Big Endian - /// Input byte array to read from. - /// Offset to start reading at. - /// Length of array to read. - public static int BCDToInt32_LE(byte[] input, int offset, int length) - { - int result = 0; - for (int i = offset + length - 1; i >= offset; i--) - { - byte p = input[i]; - result *= 100; - result += 10 * (p >> 4); - result += p & 0xf; - } - return result; - } - - /// - /// Returns the specified 32-bit signed integer value as an array of Binary Coded Decimal format bytes. - /// - /// Little Endian instead of Big Endian - /// 32-bit signed integer to convert. - /// Desired size of returned array. - public static byte[] Int32ToBCD_LE(int input, int size) - { - byte[] result = new byte[size]; - for (int i = size - 1; i >= 0; i--) - { - int p = input % 100; - input /= 100; - result[size - i - 1] = (byte)(p / 10 << 4 | p % 10); - } - return result; - } } } diff --git a/PKHeX.Core/Util/BinaryCodedDecimal.cs b/PKHeX.Core/Util/BinaryCodedDecimal.cs new file mode 100644 index 000000000..99a82e026 --- /dev/null +++ b/PKHeX.Core/Util/BinaryCodedDecimal.cs @@ -0,0 +1,96 @@ +using System; +using System.Runtime.CompilerServices; + +namespace PKHeX.Core +{ + /// + /// 4-bit decimal encoding used by some Generation 1 save file values. + /// + public static class BinaryCodedDecimal + { + /// + /// Returns a 32-bit signed integer converted from bytes in a Binary Coded Decimal format byte array. + /// + /// Input byte array to read from. + /// Offset to start reading at. + /// Length of array to read. + public static int ToInt32BE(byte[] input, int offset, int length) + { + var span = new ReadOnlySpan(input, offset, length); + return ToInt32BE(span); + } + + /// + public static int ToInt32BE(ReadOnlySpan input) + { + int result = 0; + foreach (var b in input) + PushDigits(ref result, b); + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void PushDigits(ref int result, byte b) => result = (result * 100) + (10 * (b >> 4)) + (b & 0xf); + + /// + /// Returns the specified 32-bit signed integer value as an array of Binary Coded Decimal format bytes. + /// + /// 32-bit signed integer to convert. + /// Desired size of returned array. + public static byte[] GetBytesBE(int value, int size) + { + byte[] data = new byte[size]; + WriteBytesBE(data, value); + return data; + } + + public static void WriteBytesBE(Span data, int value) + { + for (int i = 0; i < data.Length; i++) + { + int p = value % 100; + value /= 100; + data[^(1+i)] = (byte) (p / 10 << 4 | p % 10); + } + } + + /// + /// Big Endian instead of Little Endian + public static int ToInt32LE(byte[] data, int offset, int length) + { + var span = new ReadOnlySpan(data, offset, length); + return ToInt32LE(span); + } + + /// + public static int ToInt32LE(ReadOnlySpan input) + { + int result = 0; + for (int i = input.Length - 1; i >= 0; i--) + PushDigits(ref result, input[i]); + return result; + } + + /// + /// Little Endian instead of Big Endian + public static byte[] GetBytesLE(int value, int size) + { + byte[] data = new byte[size]; + WriteBytesLE(data, value); + return data; + } + + /// + /// Writes the to the buffer. + /// + public static void WriteBytesLE(Span data, int value) + { + for (int i = 0; i < data.Length; i++) + { + int p = value % 100; + value /= 100; + data[i] = (byte) (p / 10 << 4 | p % 10); + } + } + } +} diff --git a/Tests/PKHeX.Core.Tests/Util/ConvertUtilTests.cs b/Tests/PKHeX.Core.Tests/Util/ConvertUtilTests.cs index 142538fb1..886c613ae 100644 --- a/Tests/PKHeX.Core.Tests/Util/ConvertUtilTests.cs +++ b/Tests/PKHeX.Core.Tests/Util/ConvertUtilTests.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using FluentAssertions; using Xunit; @@ -47,5 +48,29 @@ namespace PKHeX.Tests.Util var remake = Core.Util.GetHexStringFromBytes(convert, 0, convert.Length); remake.Should().Be(v); } + + [Theory] + [InlineData(0x12345678, 12345678)] + public void CheckConvertBCD_Little(uint raw, int expect) + { + var data = BitConverter.GetBytes(raw); + var result = Core.BinaryCodedDecimal.ToInt32LE(data); + result.Should().Be(expect); + + var newData = Core.BinaryCodedDecimal.GetBytesLE(result, 4); + data.SequenceEqual(newData).Should().BeTrue(); + } + + [Theory] + [InlineData(0x78563412, 12345678)] + public void CheckConvertBCD_Big(uint raw, int expect) + { + var data = BitConverter.GetBytes(raw); + var result = Core.BinaryCodedDecimal.ToInt32BE(data); + result.Should().Be(expect); + + var newData = Core.BinaryCodedDecimal.GetBytesBE(result, 4); + data.SequenceEqual(newData).Should().BeTrue(); + } } }