2016-09-26 23:14:11 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2022-01-03 05:35:59 +00:00
|
|
|
|
using static System.Buffers.Binary.BinaryPrimitives;
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
2017-01-08 07:54:09 +00:00
|
|
|
|
namespace PKHeX.Core
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2017-10-24 06:12:58 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Generation 4 <see cref="SaveFile"/> object for Pokémon Battle Revolution saves.
|
|
|
|
|
/// </summary>
|
2016-09-26 23:14:11 +00:00
|
|
|
|
public sealed class SAV4BR : SaveFile
|
|
|
|
|
{
|
2020-12-05 13:36:23 +00:00
|
|
|
|
protected internal override string ShortSummary => $"{Version} #{SaveCount:0000}";
|
2019-02-08 05:40:20 +00:00
|
|
|
|
public override string Extension => string.Empty;
|
PKHeX.Core Nullable cleanup (#2401)
* Handle some nullable cases
Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data)
Make some classes have explicit constructors instead of { } initialization
* Handle bits more obviously without null
* Make SaveFile.BAK explicitly readonly again
* merge constructor methods to have readonly fields
* Inline some properties
* More nullable handling
* Rearrange box actions
define straightforward classes to not have any null properties
* Make extrabyte reference array immutable
* Move tooltip creation to designer
* Rearrange some logic to reduce nesting
* Cache generated fonts
* Split mystery gift album purpose
* Handle more tooltips
* Disallow null setters
* Don't capture RNG object, only type enum
* Unify learnset objects
Now have readonly properties which are never null
don't new() empty learnsets (>800 Learnset objects no longer created,
total of 2400 objects since we also new() a move & level array)
optimize g1/2 reader for early abort case
* Access rewrite
Initialize blocks in a separate object, and get via that object
removes a couple hundred "might be null" warnings since blocks are now readonly getters
some block references have been relocated, but interfaces should expose all that's needed
put HoF6 controls in a groupbox, and disable
* Readonly personal data
* IVs non nullable for mystery gift
* Explicitly initialize forced encounter moves
* Make shadow objects readonly & non-null
Put murkrow fix in binary data resource, instead of on startup
* Assign dex form fetch on constructor
Fixes legality parsing edge cases
also handle cxd parse for valid; exit before exception is thrown in FrameGenerator
* Remove unnecessary null checks
* Keep empty value until init
SetPouch sets the value to an actual one during load, but whatever
* Readonly team lock data
* Readonly locks
Put locked encounters at bottom (favor unlocked)
* Mail readonly data / offset
Rearrange some call flow and pass defaults
Add fake classes for SaveDataEditor mocking
Always party size, no need to check twice in stat editor
use a fake save file as initial data for savedata editor, and for
gamedata (wow i found a usage)
constrain eventwork editor to struct variable types (uint, int, etc),
thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
|
|
|
|
public override PersonalTable Personal => PersonalTable.DP;
|
|
|
|
|
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_DP;
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
2016-09-26 23:43:52 +00:00
|
|
|
|
private const int SAVE_COUNT = 4;
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2019-06-09 02:56:11 +00:00
|
|
|
|
public SAV4BR() : base(SaveUtil.SIZE_G4BR)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2019-06-09 02:56:11 +00:00
|
|
|
|
ClearBoxes();
|
|
|
|
|
}
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
2019-06-09 02:56:11 +00:00
|
|
|
|
public SAV4BR(byte[] data) : base(data)
|
|
|
|
|
{
|
|
|
|
|
InitializeData(data);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-03 05:35:59 +00:00
|
|
|
|
private void InitializeData(ReadOnlySpan<byte> data)
|
2019-06-09 02:56:11 +00:00
|
|
|
|
{
|
2016-09-26 23:14:11 +00:00
|
|
|
|
Data = DecryptPBRSaveData(data);
|
|
|
|
|
|
|
|
|
|
// Detect active save
|
2022-01-03 05:35:59 +00:00
|
|
|
|
var first = ReadUInt32BigEndian(Data.AsSpan(0x00004C));
|
|
|
|
|
var second = ReadUInt32BigEndian(Data.AsSpan(0x1C004C));
|
2021-03-17 01:49:27 +00:00
|
|
|
|
SaveCount = Math.Max(second, first);
|
|
|
|
|
if (second > first)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2021-03-17 01:49:27 +00:00
|
|
|
|
// swap halves
|
2016-09-26 23:14:11 +00:00
|
|
|
|
byte[] tempData = new byte[0x1C0000];
|
|
|
|
|
Array.Copy(Data, 0, tempData, 0, 0x1C0000);
|
|
|
|
|
Array.Copy(Data, 0x1C0000, Data, 0, 0x1C0000);
|
|
|
|
|
tempData.CopyTo(Data, 0x1C0000);
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-21 04:29:26 +00:00
|
|
|
|
var names = (string[]) SaveNames;
|
2016-09-26 23:43:52 +00:00
|
|
|
|
for (int i = 0; i < SAVE_COUNT; i++)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2021-01-21 04:29:26 +00:00
|
|
|
|
var name = GetOTName(i);
|
|
|
|
|
if (string.IsNullOrWhiteSpace(name))
|
|
|
|
|
name = $"Empty {i + 1}";
|
|
|
|
|
else if (_currentSlot == -1)
|
|
|
|
|
_currentSlot = i;
|
|
|
|
|
names[i] = name;
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-21 04:29:26 +00:00
|
|
|
|
if (_currentSlot == -1)
|
|
|
|
|
_currentSlot = 0;
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
2021-01-21 04:29:26 +00:00
|
|
|
|
CurrentSlot = _currentSlot;
|
2018-05-23 02:02:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-17 01:49:27 +00:00
|
|
|
|
/// <summary> Amount of times the primary save has been saved </summary>
|
2019-06-09 02:56:11 +00:00
|
|
|
|
private uint SaveCount;
|
2017-06-18 01:37:19 +00:00
|
|
|
|
|
2019-02-19 05:59:57 +00:00
|
|
|
|
protected override byte[] GetFinalData()
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2017-06-18 01:37:19 +00:00
|
|
|
|
SetChecksums();
|
2016-09-26 23:14:11 +00:00
|
|
|
|
return EncryptPBRSaveData(Data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Configuration
|
2020-12-05 13:36:23 +00:00
|
|
|
|
protected override SaveFile CloneInternal() => new SAV4BR(Write());
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
2021-01-21 04:29:26 +00:00
|
|
|
|
public readonly IReadOnlyList<string> SaveNames = new string[SAVE_COUNT];
|
2018-02-16 01:05:45 +00:00
|
|
|
|
|
2021-01-21 04:29:26 +00:00
|
|
|
|
private int _currentSlot = -1;
|
2021-01-23 06:55:55 +00:00
|
|
|
|
private const int SIZE_SLOT = 0x6FF00;
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2018-02-16 01:05:45 +00:00
|
|
|
|
public int CurrentSlot
|
|
|
|
|
{
|
2021-01-21 04:29:26 +00:00
|
|
|
|
get => _currentSlot;
|
2018-02-16 01:05:45 +00:00
|
|
|
|
// 4 save slots, data reading depends on current slot
|
2018-05-19 21:42:21 +00:00
|
|
|
|
set
|
|
|
|
|
{
|
2021-01-21 04:29:26 +00:00
|
|
|
|
_currentSlot = value;
|
2021-01-23 06:55:55 +00:00
|
|
|
|
var ofs = SIZE_SLOT * _currentSlot;
|
2018-05-19 21:42:21 +00:00
|
|
|
|
Box = ofs + 0x978;
|
2018-05-23 02:02:56 +00:00
|
|
|
|
Party = ofs + 0x13A54; // first team slot after boxes
|
|
|
|
|
BoxName = ofs + 0x58674;
|
2018-05-19 21:42:21 +00:00
|
|
|
|
}
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-07 23:16:10 +00:00
|
|
|
|
protected override int SIZE_STORED => PokeCrypto.SIZE_4STORED;
|
2020-01-04 22:48:39 +00:00
|
|
|
|
protected override int SIZE_PARTY => PokeCrypto.SIZE_4STORED + 4;
|
2016-09-26 23:14:11 +00:00
|
|
|
|
public override PKM BlankPKM => new BK4();
|
|
|
|
|
public override Type PKMType => typeof(BK4);
|
|
|
|
|
|
|
|
|
|
public override int MaxMoveID => 467;
|
2016-10-24 04:59:27 +00:00
|
|
|
|
public override int MaxSpeciesID => Legal.MaxSpeciesID_4;
|
2017-12-28 00:36:24 +00:00
|
|
|
|
public override int MaxAbilityID => Legal.MaxAbilityID_4;
|
|
|
|
|
public override int MaxItemID => Legal.MaxItemID_4_HGSS;
|
|
|
|
|
public override int MaxBallID => Legal.MaxBallID_4;
|
|
|
|
|
public override int MaxGameID => Legal.MaxGameID_4;
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
|
|
|
|
public override int MaxEV => 255;
|
|
|
|
|
public override int Generation => 4;
|
|
|
|
|
protected override int GiftCountMax => 1;
|
2017-04-10 04:53:53 +00:00
|
|
|
|
public override int OTLength => 7;
|
2016-09-26 23:14:11 +00:00
|
|
|
|
public override int NickLength => 10;
|
|
|
|
|
public override int MaxMoney => 999999;
|
2018-06-17 16:27:14 +00:00
|
|
|
|
public override int Language => (int)LanguageID.English; // prevent KOR from inhabiting
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
|
|
|
|
public override int BoxCount => 18;
|
2018-05-19 21:42:21 +00:00
|
|
|
|
|
|
|
|
|
public override int PartyCount
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
int ctr = 0;
|
|
|
|
|
for (int i = 0; i < 6; i++)
|
2018-09-15 05:37:47 +00:00
|
|
|
|
{
|
2018-05-19 21:42:21 +00:00
|
|
|
|
if (Data[GetPartyOffset(i) + 4] != 0) // sanity
|
2018-05-19 23:33:06 +00:00
|
|
|
|
ctr++;
|
2018-09-15 05:37:47 +00:00
|
|
|
|
}
|
2018-05-19 21:42:21 +00:00
|
|
|
|
return ctr;
|
|
|
|
|
}
|
2019-02-02 07:08:03 +00:00
|
|
|
|
protected set
|
|
|
|
|
{
|
|
|
|
|
// Ignore, value is calculated
|
|
|
|
|
}
|
2018-05-19 21:42:21 +00:00
|
|
|
|
}
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
|
|
|
|
// Checksums
|
2017-06-18 01:37:19 +00:00
|
|
|
|
protected override void SetChecksums()
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
|
|
|
|
SetChecksum(Data, 0, 0x100, 8);
|
|
|
|
|
SetChecksum(Data, 0, 0x1C0000, 0x1BFF80);
|
|
|
|
|
SetChecksum(Data, 0x1C0000, 0x100, 0x1C0008);
|
|
|
|
|
SetChecksum(Data, 0x1C0000, 0x1C0000, 0x1BFF80 + 0x1C0000);
|
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2017-10-18 06:19:34 +00:00
|
|
|
|
public override bool ChecksumsValid => IsChecksumsValid(Data);
|
|
|
|
|
public override string ChecksumInfo => $"Checksums valid: {ChecksumsValid}.";
|
|
|
|
|
|
2022-01-03 05:35:59 +00:00
|
|
|
|
public static bool IsChecksumsValid(Span<byte> sav)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2017-10-18 06:19:34 +00:00
|
|
|
|
return VerifyChecksum(sav, 0x000000, 0x1C0000, 0x1BFF80)
|
|
|
|
|
&& VerifyChecksum(sav, 0x000000, 0x000100, 0x000008)
|
|
|
|
|
&& VerifyChecksum(sav, 0x1C0000, 0x1C0000, 0x1BFF80 + 0x1C0000)
|
|
|
|
|
&& VerifyChecksum(sav, 0x1C0000, 0x000100, 0x1C0008);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Trainer Info
|
2017-05-13 03:32:36 +00:00
|
|
|
|
public override GameVersion Version { get => GameVersion.BATREV; protected set { } }
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2019-02-21 05:52:22 +00:00
|
|
|
|
private string GetOTName(int slot)
|
2018-05-23 02:02:56 +00:00
|
|
|
|
{
|
2019-02-21 05:52:22 +00:00
|
|
|
|
var ofs = 0x390 + (0x6FF00 * slot);
|
2022-01-03 05:35:59 +00:00
|
|
|
|
var span = Data.AsSpan(ofs, 16);
|
|
|
|
|
return GetString(span);
|
2018-05-23 02:02:56 +00:00
|
|
|
|
}
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
2019-02-21 05:52:22 +00:00
|
|
|
|
private void SetOTName(int slot, string name)
|
|
|
|
|
{
|
|
|
|
|
var ofs = 0x390 + (0x6FF00 * slot);
|
2022-01-03 05:35:59 +00:00
|
|
|
|
var span = Data.AsSpan(ofs, 16);
|
|
|
|
|
SetString(span, name.AsSpan(), 7, StringConverterOption.ClearZero);
|
2019-02-21 05:52:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string CurrentOT { get => GetOTName(_currentSlot); set => SetOTName(_currentSlot, value); }
|
|
|
|
|
|
2016-09-26 23:14:11 +00:00
|
|
|
|
// Storage
|
2019-02-17 18:42:43 +00:00
|
|
|
|
public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot);
|
|
|
|
|
public override int GetBoxOffset(int box) => Box + (SIZE_STORED * box * 30);
|
2016-09-26 23:43:52 +00:00
|
|
|
|
|
2021-01-23 06:55:55 +00:00
|
|
|
|
public override int TID
|
|
|
|
|
{
|
|
|
|
|
get => (Data[(_currentSlot * SIZE_SLOT) + 0x12867] << 8) | Data[(_currentSlot * SIZE_SLOT) + 0x12860];
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
Data[(_currentSlot * SIZE_SLOT) + 0x12867] = (byte)(value >> 8);
|
|
|
|
|
Data[(_currentSlot * SIZE_SLOT) + 0x12860] = (byte)(value & 0xFF);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override int SID
|
|
|
|
|
{
|
|
|
|
|
get => (Data[(_currentSlot * SIZE_SLOT) + 0x12865] << 8) | Data[(_currentSlot * SIZE_SLOT) + 0x12866];
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
Data[(_currentSlot * SIZE_SLOT) + 0x12865] = (byte)(value >> 8);
|
|
|
|
|
Data[(_currentSlot * SIZE_SLOT) + 0x12866] = (byte)(value & 0xFF);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-26 23:43:52 +00:00
|
|
|
|
// Save file does not have Box Name / Wallpaper info
|
2018-05-23 02:02:56 +00:00
|
|
|
|
private int BoxName = -1;
|
|
|
|
|
private const int BoxNameLength = 0x28;
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2018-05-23 02:02:56 +00:00
|
|
|
|
public override string GetBoxName(int box)
|
|
|
|
|
{
|
|
|
|
|
if (BoxName < 0)
|
|
|
|
|
return $"BOX {box + 1}";
|
|
|
|
|
|
2021-01-15 06:50:13 +00:00
|
|
|
|
int ofs = BoxName + (box * BoxNameLength);
|
2022-01-03 05:35:59 +00:00
|
|
|
|
var span = Data.AsSpan(ofs, BoxNameLength);
|
|
|
|
|
if (span.Count((byte)0) == span.Length)
|
2018-05-23 02:02:56 +00:00
|
|
|
|
return $"BOX {box + 1}";
|
2022-01-03 05:35:59 +00:00
|
|
|
|
return GetString(ofs, BoxNameLength);
|
2018-05-23 02:02:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-23 23:30:22 +00:00
|
|
|
|
public override void SetBoxName(int box, string value)
|
|
|
|
|
{
|
|
|
|
|
if (BoxName < 0)
|
|
|
|
|
return;
|
|
|
|
|
|
2018-09-15 05:37:47 +00:00
|
|
|
|
int ofs = BoxName + (box * BoxNameLength);
|
2022-01-03 05:35:59 +00:00
|
|
|
|
var span = Data.AsSpan(ofs, BoxNameLength);
|
|
|
|
|
if (span.Count((byte)0) == span.Length)
|
2018-05-23 23:30:22 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2022-01-03 05:35:59 +00:00
|
|
|
|
SetString(span, value.AsSpan(), BoxNameLength / 2, StringConverterOption.ClearZero);
|
2018-05-23 23:30:22 +00:00
|
|
|
|
}
|
2016-09-26 23:43:52 +00:00
|
|
|
|
|
2019-03-30 02:43:33 +00:00
|
|
|
|
protected override PKM GetPKM(byte[] data)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2019-02-17 18:42:43 +00:00
|
|
|
|
if (data.Length != SIZE_STORED)
|
|
|
|
|
Array.Resize(ref data, SIZE_STORED);
|
2020-03-19 06:34:53 +00:00
|
|
|
|
return BK4.ReadUnshuffle(data);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2019-03-30 02:43:33 +00:00
|
|
|
|
protected override byte[] DecryptPKM(byte[] data) => data;
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
2019-02-17 18:42:43 +00:00
|
|
|
|
protected override void SetDex(PKM pkm) { /* There's no PokéDex */ }
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2021-02-09 04:26:53 +00:00
|
|
|
|
protected override void SetPKM(PKM pkm, bool isParty = false)
|
2018-03-22 04:10:23 +00:00
|
|
|
|
{
|
|
|
|
|
var pk4 = (BK4)pkm;
|
|
|
|
|
// Apply to this Save File
|
|
|
|
|
DateTime Date = DateTime.Now;
|
|
|
|
|
if (pk4.Trade(OT, TID, SID, Gender, Date.Day, Date.Month, Date.Year))
|
|
|
|
|
pkm.RefreshChecksum();
|
|
|
|
|
}
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
2018-05-19 21:42:21 +00:00
|
|
|
|
protected override void SetPartyValues(PKM pkm, bool isParty)
|
|
|
|
|
{
|
2022-01-03 05:35:59 +00:00
|
|
|
|
if (pkm is G4PKM g4)
|
|
|
|
|
g4.Sanity = isParty ? (ushort)0xC000 : (ushort)0x4000;
|
2018-05-19 21:42:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-01-03 05:35:59 +00:00
|
|
|
|
public static byte[] DecryptPBRSaveData(ReadOnlySpan<byte> input)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
|
|
|
|
byte[] output = new byte[input.Length];
|
2022-01-03 05:35:59 +00:00
|
|
|
|
Span<ushort> keys = stackalloc ushort[4];
|
2019-03-31 04:28:32 +00:00
|
|
|
|
for (int i = 0; i < SaveUtil.SIZE_G4BR; i += 0x1C0000)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2022-01-03 05:35:59 +00:00
|
|
|
|
ReadKeys(input, i, keys);
|
|
|
|
|
input.Slice(i, 8).CopyTo(output.AsSpan(i, 8));
|
2021-01-08 02:05:21 +00:00
|
|
|
|
GeniusCrypto.Decrypt(input, i + 8, i + 0x1C0000, keys, output);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-03 05:35:59 +00:00
|
|
|
|
private static byte[] EncryptPBRSaveData(ReadOnlySpan<byte> input)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
|
|
|
|
byte[] output = new byte[input.Length];
|
2022-01-03 05:35:59 +00:00
|
|
|
|
Span<ushort> keys = stackalloc ushort[4];
|
2019-03-31 04:28:32 +00:00
|
|
|
|
for (int i = 0; i < SaveUtil.SIZE_G4BR; i += 0x1C0000)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2022-01-03 05:35:59 +00:00
|
|
|
|
ReadKeys(input, i, keys);
|
|
|
|
|
input.Slice(i, 8).CopyTo(output.AsSpan(i, 8));
|
2021-01-08 02:05:21 +00:00
|
|
|
|
GeniusCrypto.Encrypt(input, i + 8, i + 0x1C0000, keys, output);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-03 05:35:59 +00:00
|
|
|
|
private static void ReadKeys(ReadOnlySpan<byte> input, int ofs, Span<ushort> keys)
|
2019-03-31 04:28:32 +00:00
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < keys.Length; i++)
|
2022-01-03 05:35:59 +00:00
|
|
|
|
keys[i] = ReadUInt16BigEndian(input[(ofs + (i * 2))..]);
|
2019-03-31 04:28:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-01-03 05:35:59 +00:00
|
|
|
|
public static bool VerifyChecksum(Span<byte> input, int offset, int len, int checksum_offset)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2022-01-03 05:35:59 +00:00
|
|
|
|
Span<uint> originalChecksums = stackalloc uint[16];
|
|
|
|
|
for (int i = 0; i < originalChecksums.Length; i++)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2022-01-03 05:35:59 +00:00
|
|
|
|
var chk = input.Slice(checksum_offset + (i * 4), 4);
|
|
|
|
|
originalChecksums[i] = ReadUInt32BigEndian(chk);
|
|
|
|
|
chk.Clear();
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-01-03 05:35:59 +00:00
|
|
|
|
Span<uint> checksums = stackalloc uint[16];
|
|
|
|
|
var span = input.Slice(offset, len);
|
|
|
|
|
for (int i = 0; i < span.Length; i += 2)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2022-01-03 05:35:59 +00:00
|
|
|
|
uint val = ReadUInt16BigEndian(span[i..]);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
for (int j = 0; j < 16; j++)
|
2019-02-21 05:52:22 +00:00
|
|
|
|
checksums[j] += ((val >> j) & 1);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-08-06 05:39:38 +00:00
|
|
|
|
// Restore original checksums
|
2022-01-03 05:35:59 +00:00
|
|
|
|
for (int i = 0; i < originalChecksums.Length; i++)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2022-01-03 05:35:59 +00:00
|
|
|
|
var chk = originalChecksums[i];
|
|
|
|
|
var dest = input[(checksum_offset + (i * 4))..];
|
|
|
|
|
WriteUInt32BigEndian(dest, chk);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-08-06 05:39:38 +00:00
|
|
|
|
// Check if they match
|
2022-01-03 05:35:59 +00:00
|
|
|
|
for (int i = 0; i < originalChecksums.Length; i++)
|
2021-08-06 05:39:38 +00:00
|
|
|
|
{
|
2022-01-03 05:35:59 +00:00
|
|
|
|
if (originalChecksums[i] != checksums[i])
|
2021-08-06 05:39:38 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-01-03 05:35:59 +00:00
|
|
|
|
private static void SetChecksum(Span<byte> input, int offset, int len, int checksum_offset)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2022-01-03 05:35:59 +00:00
|
|
|
|
// Wipe Checksum region.
|
|
|
|
|
input.Slice(checksum_offset, 4 * 16).Clear();
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
2022-01-03 05:35:59 +00:00
|
|
|
|
Span<uint> checksums = stackalloc uint[16];
|
|
|
|
|
var span = input.Slice(offset, len);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
for (int i = 0; i < len; i += 2)
|
|
|
|
|
{
|
2022-01-03 05:35:59 +00:00
|
|
|
|
uint val = ReadUInt16BigEndian(span[i..]);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
for (int j = 0; j < 16; j++)
|
2019-02-21 05:52:22 +00:00
|
|
|
|
checksums[j] += ((val >> j) & 1);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < checksums.Length; i++)
|
|
|
|
|
{
|
2022-01-03 05:35:59 +00:00
|
|
|
|
var chk = checksums[i];
|
|
|
|
|
var dest = input[(checksum_offset + (i * 4))..];
|
|
|
|
|
WriteUInt32BigEndian(dest, chk);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-09 21:06:50 +00:00
|
|
|
|
|
2022-01-03 05:35:59 +00:00
|
|
|
|
public override string GetString(ReadOnlySpan<byte> data) => StringConverter4GC.GetStringUnicode(data);
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2022-01-03 05:35:59 +00:00
|
|
|
|
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option) => StringConverter4GC.SetStringUnicode(value, destBuffer, maxLength, option);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
}
|