2016-06-20 04:22:43 +00:00
|
|
|
|
using System;
|
2017-09-30 05:58:25 +00:00
|
|
|
|
using System.Collections.Generic;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2017-01-08 07:54:09 +00:00
|
|
|
|
namespace PKHeX.Core
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2017-10-24 06:12:58 +00:00
|
|
|
|
/// <summary>
|
2019-09-03 02:30:58 +00:00
|
|
|
|
/// Generation 4 abstract <see cref="SaveFile"/> object.
|
2017-10-24 06:12:58 +00:00
|
|
|
|
/// </summary>
|
2019-09-14 18:39:48 +00:00
|
|
|
|
/// <remarks>
|
|
|
|
|
/// Storage data is stored in one contiguous block, and the remaining data is stored in another block.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
public abstract class SAV4 : SaveFile
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2020-12-05 13:36:23 +00:00
|
|
|
|
protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
|
2021-07-27 06:33:56 +00:00
|
|
|
|
public sealed override string Extension => ".sav";
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2019-09-03 02:30:58 +00:00
|
|
|
|
// Blocks & Offsets
|
|
|
|
|
private readonly int GeneralBlockPosition; // Small Block
|
|
|
|
|
private readonly int StorageBlockPosition; // Big Block
|
2020-03-12 04:09:36 +00:00
|
|
|
|
private const int PartitionSize = 0x40000;
|
2019-09-03 02:30:58 +00:00
|
|
|
|
|
2019-09-14 18:39:48 +00:00
|
|
|
|
// SaveData is chunked into two pieces.
|
|
|
|
|
protected readonly byte[] Storage;
|
|
|
|
|
public readonly byte[] General;
|
2021-07-27 06:33:56 +00:00
|
|
|
|
protected sealed override byte[] BoxBuffer => Storage;
|
|
|
|
|
protected sealed override byte[] PartyBuffer => General;
|
2019-09-13 02:01:06 +00:00
|
|
|
|
|
2019-09-14 18:39:48 +00:00
|
|
|
|
protected abstract int StorageStart { get; }
|
2021-02-21 17:59:10 +00:00
|
|
|
|
public abstract Zukan4 Dex { get; }
|
2019-09-14 18:39:48 +00:00
|
|
|
|
|
2021-07-27 06:33:56 +00:00
|
|
|
|
public sealed override bool GetFlag(int offset, int bitIndex) => FlagUtil.GetFlag(General, offset, bitIndex);
|
|
|
|
|
public sealed override void SetFlag(int offset, int bitIndex, bool value) => FlagUtil.SetFlag(General, offset, bitIndex, value);
|
2019-09-14 18:39:48 +00:00
|
|
|
|
|
2021-05-29 22:31:47 +00:00
|
|
|
|
protected SAV4(int gSize, int sSize)
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2021-05-29 22:31:47 +00:00
|
|
|
|
General = new byte[gSize];
|
|
|
|
|
Storage = new byte[sSize];
|
2019-06-09 02:56:11 +00:00
|
|
|
|
ClearBoxes();
|
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2021-05-29 22:31:47 +00:00
|
|
|
|
protected SAV4(byte[] data, int gSize, int sSize, int sStart) : base(data)
|
2019-06-09 02:56:11 +00:00
|
|
|
|
{
|
2021-05-29 22:31:47 +00:00
|
|
|
|
GeneralBlockPosition = GetActiveBlock(data, 0, gSize);
|
|
|
|
|
StorageBlockPosition = GetActiveBlock(data, sStart, sSize);
|
2020-03-12 04:09:36 +00:00
|
|
|
|
|
|
|
|
|
var gbo = (GeneralBlockPosition == 0 ? 0 : PartitionSize);
|
|
|
|
|
var sbo = (StorageBlockPosition == 0 ? 0 : PartitionSize) + sStart;
|
|
|
|
|
General = GetData(gbo, gSize);
|
|
|
|
|
Storage = GetData(sbo, sSize);
|
2019-06-09 02:56:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-03 02:30:58 +00:00
|
|
|
|
// Configuration
|
2021-07-27 06:33:56 +00:00
|
|
|
|
protected sealed override SaveFile CloneInternal()
|
2019-06-09 02:56:11 +00:00
|
|
|
|
{
|
2020-12-05 13:36:23 +00:00
|
|
|
|
var sav = CloneInternal4();
|
2019-09-03 02:30:58 +00:00
|
|
|
|
SetData(sav.General, General, 0);
|
|
|
|
|
SetData(sav.Storage, Storage, 0);
|
|
|
|
|
return sav;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-12-05 13:36:23 +00:00
|
|
|
|
protected abstract SAV4 CloneInternal4();
|
2019-09-03 02:30:58 +00:00
|
|
|
|
|
2021-07-27 06:33:56 +00:00
|
|
|
|
public sealed override void CopyChangesFrom(SaveFile sav)
|
2019-09-03 02:30:58 +00:00
|
|
|
|
{
|
|
|
|
|
SetData(sav.Data, 0);
|
|
|
|
|
var s4 = (SAV4)sav;
|
|
|
|
|
SetData(General, s4.General, 0);
|
|
|
|
|
SetData(Storage, s4.Storage, 0);
|
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2021-07-27 06:33:56 +00:00
|
|
|
|
protected sealed override int SIZE_STORED => PokeCrypto.SIZE_4STORED;
|
|
|
|
|
protected sealed override int SIZE_PARTY => PokeCrypto.SIZE_4PARTY;
|
|
|
|
|
public sealed override PKM BlankPKM => new PK4();
|
|
|
|
|
public sealed override Type PKMType => typeof(PK4);
|
|
|
|
|
|
|
|
|
|
public sealed override int BoxCount => 18;
|
|
|
|
|
public sealed override int MaxEV => 255;
|
|
|
|
|
public sealed override int Generation => 4;
|
|
|
|
|
protected sealed override int EventFlagMax => 0xB60; // 2912
|
|
|
|
|
protected sealed override int EventConstMax => (EventFlag - EventConst) >> 1;
|
|
|
|
|
protected sealed override int GiftCountMax => 11;
|
|
|
|
|
public sealed override int OTLength => 7;
|
|
|
|
|
public sealed override int NickLength => 10;
|
|
|
|
|
public sealed override int MaxMoney => 999999;
|
|
|
|
|
public sealed override int MaxCoins => 50_000;
|
|
|
|
|
|
|
|
|
|
public sealed override int MaxMoveID => Legal.MaxMoveID_4;
|
|
|
|
|
public sealed override int MaxSpeciesID => Legal.MaxSpeciesID_4;
|
2021-03-14 23:16:55 +00:00
|
|
|
|
// MaxItemID
|
2021-07-27 06:33:56 +00:00
|
|
|
|
public sealed override int MaxAbilityID => Legal.MaxAbilityID_4;
|
|
|
|
|
public sealed override int MaxBallID => Legal.MaxBallID_4;
|
|
|
|
|
public sealed override int MaxGameID => Legal.MaxGameID_4; // Colo/XD
|
2019-07-14 22:06:45 +00:00
|
|
|
|
|
2016-06-20 04:22:43 +00:00
|
|
|
|
// Checksums
|
2019-09-04 01:16:10 +00:00
|
|
|
|
protected abstract int FooterSize { get; }
|
2021-05-14 19:30:40 +00:00
|
|
|
|
private ushort CalcBlockChecksum(byte[] data) => Checksums.CRC16_CCITT(new ReadOnlySpan<byte>(data, 0, data.Length - FooterSize));
|
2019-09-03 02:30:58 +00:00
|
|
|
|
private static ushort GetBlockChecksumSaved(byte[] data) => BitConverter.ToUInt16(data, data.Length - 2);
|
2019-09-04 01:16:10 +00:00
|
|
|
|
private bool GetBlockChecksumValid(byte[] data) => CalcBlockChecksum(data) == GetBlockChecksumSaved(data);
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2021-07-27 06:33:56 +00:00
|
|
|
|
protected sealed override void SetChecksums()
|
2017-01-16 00:16:35 +00:00
|
|
|
|
{
|
2019-09-03 02:30:58 +00:00
|
|
|
|
BitConverter.GetBytes(CalcBlockChecksum(General)).CopyTo(General, General.Length - 2);
|
|
|
|
|
BitConverter.GetBytes(CalcBlockChecksum(Storage)).CopyTo(Storage, Storage.Length - 2);
|
2017-01-16 00:16:35 +00:00
|
|
|
|
|
2019-09-03 02:30:58 +00:00
|
|
|
|
// Write blocks back
|
2020-03-12 04:09:36 +00:00
|
|
|
|
General.CopyTo(Data, GeneralBlockPosition * PartitionSize);
|
|
|
|
|
Storage.CopyTo(Data, (StorageBlockPosition * PartitionSize) + StorageStart);
|
2017-01-16 00:16:35 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2021-07-27 06:33:56 +00:00
|
|
|
|
public sealed override bool ChecksumsValid
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
2019-09-03 02:30:58 +00:00
|
|
|
|
if (!GetBlockChecksumValid(General))
|
|
|
|
|
return false;
|
|
|
|
|
if (!GetBlockChecksumValid(Storage))
|
2017-01-16 00:16:35 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
2016-06-20 04:22:43 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2021-07-27 06:33:56 +00:00
|
|
|
|
public sealed override string ChecksumInfo
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
2017-09-30 05:58:25 +00:00
|
|
|
|
var list = new List<string>();
|
2019-09-03 02:30:58 +00:00
|
|
|
|
if (!GetBlockChecksumValid(General))
|
2017-09-30 05:58:25 +00:00
|
|
|
|
list.Add("Small block checksum is invalid");
|
2019-09-03 02:30:58 +00:00
|
|
|
|
if (!GetBlockChecksumValid(Storage))
|
2017-09-30 05:58:25 +00:00
|
|
|
|
list.Add("Large block checksum is invalid");
|
2017-01-16 00:16:35 +00:00
|
|
|
|
|
2017-12-05 04:16:54 +00:00
|
|
|
|
return list.Count != 0 ? string.Join(Environment.NewLine, list) : "Checksums are valid.";
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-12 04:09:36 +00:00
|
|
|
|
private static int GetActiveBlock(byte[] data, int begin, int length)
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2020-05-24 05:17:03 +00:00
|
|
|
|
int offset = begin + length - 0x14;
|
|
|
|
|
return SAV4BlockDetection.CompareFooters(data, offset, offset + PartitionSize);
|
2016-09-13 01:10:42 +00:00
|
|
|
|
}
|
2018-07-25 23:00:52 +00:00
|
|
|
|
|
2019-09-03 02:30:58 +00:00
|
|
|
|
protected int WondercardFlags = int.MinValue;
|
|
|
|
|
protected int AdventureInfo = int.MinValue;
|
|
|
|
|
protected int Seal = int.MinValue;
|
2019-11-28 22:00:55 +00:00
|
|
|
|
protected int Trainer1;
|
2021-07-27 06:33:56 +00:00
|
|
|
|
public int GTS { get; protected set; } = int.MinValue;
|
2019-10-26 19:33:58 +00:00
|
|
|
|
|
2016-06-20 04:22:43 +00:00
|
|
|
|
// Storage
|
|
|
|
|
public override int PartyCount
|
|
|
|
|
{
|
2019-09-03 02:30:58 +00:00
|
|
|
|
get => General[Party - 4];
|
|
|
|
|
protected set => General[Party - 4] = (byte)value;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2021-07-27 06:33:56 +00:00
|
|
|
|
public sealed override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot);
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
|
|
|
|
// Trainer Info
|
|
|
|
|
public override string OT
|
|
|
|
|
{
|
2019-09-03 02:30:58 +00:00
|
|
|
|
get => GetString(General, Trainer1, 16);
|
|
|
|
|
set => SetString(value, OTLength).CopyTo(General, Trainer1);
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2018-04-28 18:06:58 +00:00
|
|
|
|
public override int TID
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2019-09-03 02:30:58 +00:00
|
|
|
|
get => BitConverter.ToUInt16(General, Trainer1 + 0x10);
|
|
|
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(General, Trainer1 + 0x10);
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2018-04-28 18:06:58 +00:00
|
|
|
|
public override int SID
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2019-09-03 02:30:58 +00:00
|
|
|
|
get => BitConverter.ToUInt16(General, Trainer1 + 0x12);
|
|
|
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(General, Trainer1 + 0x12);
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2016-06-22 00:35:12 +00:00
|
|
|
|
public override uint Money
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2019-09-03 02:30:58 +00:00
|
|
|
|
get => BitConverter.ToUInt32(General, Trainer1 + 0x14);
|
|
|
|
|
set => BitConverter.GetBytes(value).CopyTo(General, Trainer1 + 0x14);
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2016-06-20 04:22:43 +00:00
|
|
|
|
public override int Gender
|
|
|
|
|
{
|
2019-09-03 02:30:58 +00:00
|
|
|
|
get => General[Trainer1 + 0x18];
|
|
|
|
|
set => General[Trainer1 + 0x18] = (byte)value;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2016-06-20 04:22:43 +00:00
|
|
|
|
public override int Language
|
|
|
|
|
{
|
2019-09-03 02:30:58 +00:00
|
|
|
|
get => General[Trainer1 + 0x19];
|
|
|
|
|
set => General[Trainer1 + 0x19] = (byte)value;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2016-06-20 04:22:43 +00:00
|
|
|
|
public int Badges
|
|
|
|
|
{
|
2019-09-03 02:30:58 +00:00
|
|
|
|
get => General[Trainer1 + 0x1A];
|
|
|
|
|
set { if (value < 0) return; General[Trainer1 + 0x1A] = (byte)value; }
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2016-06-20 04:22:43 +00:00
|
|
|
|
public int Sprite
|
|
|
|
|
{
|
2019-09-03 02:30:58 +00:00
|
|
|
|
get => General[Trainer1 + 0x1B];
|
|
|
|
|
set { if (value < 0) return; General[Trainer1 + 0x1B] = (byte)value; }
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2019-07-21 19:30:21 +00:00
|
|
|
|
public uint Coin
|
|
|
|
|
{
|
2019-09-03 02:30:58 +00:00
|
|
|
|
get => BitConverter.ToUInt16(General, Trainer1 + 0x20);
|
|
|
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(General, Trainer1 + 0x20);
|
2019-07-21 19:30:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-06-22 00:35:12 +00:00
|
|
|
|
public override int PlayedHours
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2019-09-03 02:30:58 +00:00
|
|
|
|
get => BitConverter.ToUInt16(General, Trainer1 + 0x22);
|
|
|
|
|
set => BitConverter.GetBytes((ushort)value).CopyTo(General, Trainer1 + 0x22);
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2016-06-22 00:35:12 +00:00
|
|
|
|
public override int PlayedMinutes
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2019-09-03 02:30:58 +00:00
|
|
|
|
get => General[Trainer1 + 0x24];
|
|
|
|
|
set => General[Trainer1 + 0x24] = (byte)value;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2016-06-22 00:35:12 +00:00
|
|
|
|
public override int PlayedSeconds
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2019-09-03 02:30:58 +00:00
|
|
|
|
get => General[Trainer1 + 0x25];
|
|
|
|
|
set => General[Trainer1 + 0x25] = (byte)value;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2021-05-18 20:04:23 +00:00
|
|
|
|
public abstract int M { get; set; }
|
|
|
|
|
public abstract int X { get; set; }
|
|
|
|
|
public abstract int Y { get; set; }
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2021-10-10 23:11:46 +00:00
|
|
|
|
public abstract string Rival { get; set; }
|
|
|
|
|
public abstract Span<byte> Rival_Trash { get; set; }
|
|
|
|
|
|
2021-05-18 20:04:23 +00:00
|
|
|
|
public abstract int X2 { get; set; }
|
|
|
|
|
public abstract int Y2 { get; set; }
|
|
|
|
|
public abstract int Z { get; set; }
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2019-09-03 02:30:58 +00:00
|
|
|
|
public override uint SecondsToStart { get => BitConverter.ToUInt32(General, AdventureInfo + 0x34); set => BitConverter.GetBytes(value).CopyTo(General, AdventureInfo + 0x34); }
|
|
|
|
|
public override uint SecondsToFame { get => BitConverter.ToUInt32(General, AdventureInfo + 0x3C); set => BitConverter.GetBytes(value).CopyTo(General, AdventureInfo + 0x3C); }
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2021-07-27 06:33:56 +00:00
|
|
|
|
protected sealed override PKM GetPKM(byte[] data) => new PK4(data);
|
|
|
|
|
protected sealed override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray45(data);
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2021-07-27 06:33:56 +00:00
|
|
|
|
protected sealed override void SetPKM(PKM pkm, bool isParty = false)
|
2018-03-22 04:10:23 +00:00
|
|
|
|
{
|
|
|
|
|
var pk4 = (PK4)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-06-20 04:22:43 +00:00
|
|
|
|
// Daycare
|
2019-10-19 03:42:03 +00:00
|
|
|
|
public override int GetDaycareSlotOffset(int loc, int slot) => DaycareOffset + (slot * SIZE_PARTY);
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override uint? GetDaycareEXP(int loc, int slot)
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2019-10-19 03:42:03 +00:00
|
|
|
|
int ofs = DaycareOffset + ((slot+1)*SIZE_PARTY) - 4;
|
2019-09-03 02:30:58 +00:00
|
|
|
|
return BitConverter.ToUInt32(General, ofs);
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2018-12-11 04:32:08 +00:00
|
|
|
|
public override bool? IsDaycareOccupied(int loc, int slot) => null; // todo
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override void SetDaycareEXP(int loc, int slot, uint EXP)
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2019-10-19 03:42:03 +00:00
|
|
|
|
int ofs = DaycareOffset + ((slot+1)*SIZE_PARTY) - 4;
|
2019-09-03 02:30:58 +00:00
|
|
|
|
BitConverter.GetBytes(EXP).CopyTo(General, ofs);
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2019-02-02 18:19:41 +00:00
|
|
|
|
public override void SetDaycareOccupied(int loc, int slot, bool occupied)
|
|
|
|
|
{
|
|
|
|
|
// todo
|
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
|
|
|
|
// Mystery Gift
|
2019-09-03 02:30:58 +00:00
|
|
|
|
private bool MysteryGiftActive { get => (General[72] & 1) == 1; set => General[72] = (byte)((General[72] & 0xFE) | (value ? 1 : 0)); }
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
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
|
|
|
|
private static bool IsMysteryGiftAvailable(DataMysteryGift[] value)
|
2017-01-16 00:52:10 +00:00
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < 8; i++) // 8 PGT
|
2018-09-15 05:37:47 +00:00
|
|
|
|
{
|
2020-12-29 08:37:59 +00:00
|
|
|
|
if (value[i] is PGT {CardType: not 0})
|
2017-01-16 00:52:10 +00:00
|
|
|
|
return true;
|
2018-09-15 05:37:47 +00:00
|
|
|
|
}
|
2017-01-16 00:52:10 +00:00
|
|
|
|
for (int i = 8; i < 11; i++) // 3 PCD
|
2018-09-15 05:37:47 +00:00
|
|
|
|
{
|
2021-04-20 09:17:28 +00:00
|
|
|
|
if (value[i] is PCD {Gift: {CardType: not 0}})
|
2017-01-16 00:52:10 +00:00
|
|
|
|
return true;
|
2018-09-15 05:37:47 +00:00
|
|
|
|
}
|
2017-01-16 00:52:10 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-01-20 07:49:46 +00:00
|
|
|
|
|
2020-07-19 22:48:45 +00:00
|
|
|
|
private byte[] MatchMysteryGifts(DataMysteryGift[] value)
|
2017-01-20 07:49:46 +00:00
|
|
|
|
{
|
2020-07-19 22:48:45 +00:00
|
|
|
|
byte[] cardMatch = new byte[8];
|
2017-01-20 07:49:46 +00:00
|
|
|
|
for (int i = 0; i < 8; i++)
|
|
|
|
|
{
|
2020-12-22 01:12:39 +00:00
|
|
|
|
if (value[i] is not PGT pgt)
|
2017-01-20 07:49:46 +00:00
|
|
|
|
continue;
|
|
|
|
|
|
2017-01-21 03:35:56 +00:00
|
|
|
|
if (pgt.CardType == 0) // empty
|
|
|
|
|
{
|
|
|
|
|
cardMatch[i] = pgt.Slot = 0;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-20 16:43:41 +00:00
|
|
|
|
cardMatch[i] = pgt.Slot = 3;
|
2017-01-20 07:49:46 +00:00
|
|
|
|
for (byte j = 0; j < 3; j++)
|
|
|
|
|
{
|
2020-12-22 01:12:39 +00:00
|
|
|
|
if (value[8 + j] is not PCD pcd)
|
2017-01-20 07:49:46 +00:00
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Check if data matches (except Slot @ 0x02)
|
2017-01-21 03:35:56 +00:00
|
|
|
|
if (!pcd.GiftEquals(pgt))
|
2017-01-20 07:49:46 +00:00
|
|
|
|
continue;
|
|
|
|
|
|
2019-09-03 02:30:58 +00:00
|
|
|
|
if (this is SAV4HGSS)
|
2018-12-01 18:44:38 +00:00
|
|
|
|
j++; // hgss 0,1,2; dppt 1,2,3
|
2017-01-20 07:49:46 +00:00
|
|
|
|
cardMatch[i] = pgt.Slot = j;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return cardMatch;
|
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2016-06-20 04:22:43 +00:00
|
|
|
|
public override MysteryGiftAlbum GiftAlbum
|
|
|
|
|
{
|
2021-08-20 20:42:25 +00:00
|
|
|
|
get => new(MysteryGiftCards, MysteryGiftReceivedFlags) {Flags = {[2047] = false}};
|
2016-06-20 04:22:43 +00:00
|
|
|
|
set
|
|
|
|
|
{
|
2017-06-18 01:37:19 +00:00
|
|
|
|
bool available = IsMysteryGiftAvailable(value.Gifts);
|
2019-09-03 02:30:58 +00:00
|
|
|
|
if (available && !MysteryGiftActive)
|
|
|
|
|
MysteryGiftActive = true;
|
2017-01-16 00:52:10 +00:00
|
|
|
|
value.Flags[2047] = available;
|
|
|
|
|
|
2017-12-11 18:13:08 +00:00
|
|
|
|
// Check encryption for each gift (decrypted wc4 sneaking in)
|
|
|
|
|
foreach (var g in value.Gifts)
|
|
|
|
|
{
|
|
|
|
|
if (g is PGT pgt)
|
2018-09-15 05:37:47 +00:00
|
|
|
|
{
|
2017-12-11 18:13:08 +00:00
|
|
|
|
pgt.VerifyPKEncryption();
|
2018-09-15 05:37:47 +00:00
|
|
|
|
}
|
2017-12-11 18:13:08 +00:00
|
|
|
|
else if (g is PCD pcd)
|
|
|
|
|
{
|
2017-12-11 19:01:57 +00:00
|
|
|
|
var dg = pcd.Gift;
|
|
|
|
|
if (dg.VerifyPKEncryption())
|
|
|
|
|
pcd.Gift = dg; // set encrypted gift back to PCD.
|
2017-12-11 18:13:08 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-20 04:22:43 +00:00
|
|
|
|
MysteryGiftReceivedFlags = value.Flags;
|
|
|
|
|
MysteryGiftCards = value.Gifts;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2021-07-27 06:33:56 +00:00
|
|
|
|
protected sealed override bool[] MysteryGiftReceivedFlags
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
2019-02-07 07:28:02 +00:00
|
|
|
|
bool[] result = new bool[GiftFlagMax];
|
|
|
|
|
for (int i = 0; i < result.Length; i++)
|
2019-09-03 02:30:58 +00:00
|
|
|
|
result[i] = (General[WondercardFlags + (i >> 3)] >> (i & 7) & 0x1) == 1;
|
2019-02-07 07:28:02 +00:00
|
|
|
|
return result;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
|
|
|
|
set
|
|
|
|
|
{
|
2020-01-19 00:57:25 +00:00
|
|
|
|
if (GiftFlagMax != value.Length)
|
2016-06-20 04:22:43 +00:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
byte[] data = new byte[value.Length / 8];
|
|
|
|
|
for (int i = 0; i < value.Length; i++)
|
2018-09-15 05:37:47 +00:00
|
|
|
|
{
|
2016-06-20 04:22:43 +00:00
|
|
|
|
if (value[i])
|
|
|
|
|
data[i >> 3] |= (byte)(1 << (i & 7));
|
2018-09-15 05:37:47 +00:00
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2019-09-03 02:30:58 +00:00
|
|
|
|
SetData(General, data, WondercardFlags);
|
2017-01-21 20:09:15 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-27 06:33:56 +00:00
|
|
|
|
protected sealed override DataMysteryGift[] MysteryGiftCards
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
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
|
|
|
|
DataMysteryGift[] cards = new DataMysteryGift[8 + 3];
|
2016-06-20 04:22:43 +00:00
|
|
|
|
for (int i = 0; i < 8; i++) // 8 PGT
|
2019-09-03 02:30:58 +00:00
|
|
|
|
cards[i] = new PGT(General.Slice(WondercardData + (i * PGT.Size), PGT.Size));
|
2016-06-20 04:22:43 +00:00
|
|
|
|
for (int i = 8; i < 11; i++) // 3 PCD
|
2019-09-03 02:30:58 +00:00
|
|
|
|
cards[i] = new PCD(General.Slice(WondercardData + (8 * PGT.Size) + ((i-8) * PCD.Size), PCD.Size));
|
2016-06-20 04:22:43 +00:00
|
|
|
|
return cards;
|
|
|
|
|
}
|
|
|
|
|
set
|
|
|
|
|
{
|
2017-06-18 01:37:19 +00:00
|
|
|
|
var Matches = MatchMysteryGifts(value); // automatically applied
|
2019-02-02 07:26:43 +00:00
|
|
|
|
if (Matches.Length == 0)
|
2017-01-20 07:49:46 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2016-06-20 04:22:43 +00:00
|
|
|
|
for (int i = 0; i < 8; i++) // 8 PGT
|
2018-09-15 05:37:47 +00:00
|
|
|
|
{
|
2017-01-16 00:52:10 +00:00
|
|
|
|
if (value[i] is PGT)
|
2019-09-03 02:30:58 +00:00
|
|
|
|
SetData(General, value[i].Data, WondercardData + (i *PGT.Size));
|
2018-09-15 05:37:47 +00:00
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
for (int i = 8; i < 11; i++) // 3 PCD
|
2018-09-15 05:37:47 +00:00
|
|
|
|
{
|
2017-01-16 00:52:10 +00:00
|
|
|
|
if (value[i] is PCD)
|
2019-09-03 02:30:58 +00:00
|
|
|
|
SetData(General, value[i].Data, WondercardData + (8 *PGT.Size) + ((i - 8)*PCD.Size));
|
2018-09-15 05:37:47 +00:00
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-07-04 20:06:01 +00:00
|
|
|
|
|
2021-07-27 06:33:56 +00:00
|
|
|
|
protected sealed override void SetDex(PKM pkm) => Dex.SetDex(pkm);
|
|
|
|
|
public sealed override bool GetCaught(int species) => Dex.GetCaught(species);
|
|
|
|
|
public sealed override bool GetSeen(int species) => Dex.GetSeen(species);
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2017-03-22 05:50:55 +00:00
|
|
|
|
public int DexUpgraded
|
2017-03-21 03:14:37 +00:00
|
|
|
|
{
|
2017-03-22 05:50:55 +00:00
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
switch (Version)
|
|
|
|
|
{
|
|
|
|
|
case GameVersion.DP:
|
2019-09-03 02:30:58 +00:00
|
|
|
|
if (General[0x1413] != 0) return 4;
|
|
|
|
|
else if (General[0x1415] != 0) return 3;
|
|
|
|
|
else if (General[0x1404] != 0) return 2;
|
|
|
|
|
else if (General[0x1414] != 0) return 1;
|
2017-03-22 05:50:55 +00:00
|
|
|
|
else return 0;
|
|
|
|
|
case GameVersion.HGSS:
|
2019-09-03 02:30:58 +00:00
|
|
|
|
if (General[0x15ED] != 0) return 3;
|
|
|
|
|
else if (General[0x15EF] != 0) return 2;
|
|
|
|
|
else if (General[0x15EE] != 0 && (General[0x10D1] & 8) != 0) return 1;
|
2017-06-10 13:12:51 +00:00
|
|
|
|
else return 0;
|
|
|
|
|
case GameVersion.Pt:
|
2019-09-03 02:30:58 +00:00
|
|
|
|
if (General[0x1641] != 0) return 4;
|
|
|
|
|
else if (General[0x1643] != 0) return 3;
|
|
|
|
|
else if (General[0x1640] != 0) return 2;
|
|
|
|
|
else if (General[0x1642] != 0) return 1;
|
2017-03-22 05:50:55 +00:00
|
|
|
|
else return 0;
|
|
|
|
|
default: return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
switch (Version)
|
|
|
|
|
{
|
|
|
|
|
case GameVersion.DP:
|
2021-03-29 07:14:44 +00:00
|
|
|
|
General[0x1413] = value == 4 ? (byte)1 : (byte)0;
|
|
|
|
|
General[0x1415] = value >= 3 ? (byte)1 : (byte)0;
|
|
|
|
|
General[0x1404] = value >= 2 ? (byte)1 : (byte)0;
|
|
|
|
|
General[0x1414] = value >= 1 ? (byte)1 : (byte)0;
|
2017-03-22 05:50:55 +00:00
|
|
|
|
break;
|
|
|
|
|
case GameVersion.HGSS:
|
2021-03-29 07:14:44 +00:00
|
|
|
|
General[0x15ED] = value == 3 ? (byte)1 : (byte)0;
|
|
|
|
|
General[0x15EF] = value >= 2 ? (byte)1 : (byte)0;
|
|
|
|
|
General[0x15EE] = value >= 1 ? (byte)1 : (byte)0;
|
2019-09-03 02:30:58 +00:00
|
|
|
|
General[0x10D1] = (byte)((General[0x10D1] & ~8) | (value >= 1 ? 8 : 0));
|
2017-06-10 13:12:51 +00:00
|
|
|
|
break;
|
|
|
|
|
case GameVersion.Pt:
|
2021-03-29 07:14:44 +00:00
|
|
|
|
General[0x1641] = value == 4 ? (byte)1 : (byte)0;
|
|
|
|
|
General[0x1643] = value >= 3 ? (byte)1 : (byte)0;
|
|
|
|
|
General[0x1640] = value >= 2 ? (byte)1 : (byte)0;
|
|
|
|
|
General[0x1642] = value >= 1 ? (byte)1 : (byte)0;
|
2017-03-22 05:50:55 +00:00
|
|
|
|
break;
|
|
|
|
|
default: return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-03-21 03:14:37 +00:00
|
|
|
|
}
|
2017-04-07 04:47:28 +00:00
|
|
|
|
|
2021-07-27 06:33:56 +00:00
|
|
|
|
public sealed override string GetString(byte[] data, int offset, int length) => StringConverter4.GetString4(data, offset, length);
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2021-07-27 06:33:56 +00:00
|
|
|
|
public sealed override byte[] SetString(string value, int maxLength, int PadToSize = 0, ushort PadWith = 0)
|
2017-04-09 21:06:50 +00:00
|
|
|
|
{
|
|
|
|
|
if (PadToSize == 0)
|
|
|
|
|
PadToSize = maxLength + 1;
|
2019-03-21 04:50:44 +00:00
|
|
|
|
return StringConverter4.SetString4(value, maxLength, PadToSize, PadWith);
|
2017-04-09 21:06:50 +00:00
|
|
|
|
}
|
2018-09-04 22:12:35 +00:00
|
|
|
|
|
2019-12-28 02:30:39 +00:00
|
|
|
|
/// <summary> All Event Constant values for the savegame </summary>
|
2021-07-27 06:33:56 +00:00
|
|
|
|
public sealed override ushort[] GetEventConsts()
|
2019-12-28 02:30:39 +00:00
|
|
|
|
{
|
2020-12-05 14:09:33 +00:00
|
|
|
|
if (EventConstMax <= 0)
|
|
|
|
|
return Array.Empty<ushort>();
|
2019-12-28 02:30:39 +00:00
|
|
|
|
|
2020-12-05 14:09:33 +00:00
|
|
|
|
ushort[] Constants = new ushort[EventConstMax];
|
|
|
|
|
for (int i = 0; i < Constants.Length; i++)
|
|
|
|
|
Constants[i] = BitConverter.ToUInt16(General, EventConst + (i * 2));
|
|
|
|
|
return Constants;
|
|
|
|
|
}
|
2019-12-28 02:30:39 +00:00
|
|
|
|
|
2020-12-05 14:09:33 +00:00
|
|
|
|
/// <summary> All Event Constant values for the savegame </summary>
|
2021-07-27 06:33:56 +00:00
|
|
|
|
public sealed override void SetEventConsts(ushort[] value)
|
2020-12-05 14:09:33 +00:00
|
|
|
|
{
|
|
|
|
|
if (EventConstMax <= 0)
|
|
|
|
|
return;
|
|
|
|
|
if (value.Length != EventConstMax)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < value.Length; i++)
|
|
|
|
|
BitConverter.GetBytes(value[i]).CopyTo(General, EventConst + (i * 2));
|
2019-12-28 02:30:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-09-04 22:12:35 +00:00
|
|
|
|
// Seals
|
2018-09-04 22:26:16 +00:00
|
|
|
|
private const byte SealMaxCount = 99;
|
2020-11-12 05:01:41 +00:00
|
|
|
|
|
|
|
|
|
public byte[] GetSealCase() => General.Slice(Seal, (int)Seal4.MAX);
|
|
|
|
|
public void SetSealCase(byte[] value) => SetData(General, value, Seal);
|
|
|
|
|
|
2019-09-03 02:30:58 +00:00
|
|
|
|
public byte GetSealCount(Seal4 id) => General[Seal + (int)id];
|
|
|
|
|
public byte SetSealCount(Seal4 id, byte count) => General[Seal + (int)id] = Math.Min(SealMaxCount, count);
|
2019-04-30 00:21:19 +00:00
|
|
|
|
|
|
|
|
|
public void SetAllSeals(byte count, bool unreleased = false)
|
|
|
|
|
{
|
|
|
|
|
var sealIndexCount = (int)(unreleased ? Seal4.MAX : Seal4.MAXLEGAL);
|
|
|
|
|
var val = Math.Min(count, SealMaxCount);
|
|
|
|
|
for (int i = 0; i < sealIndexCount; i++)
|
2019-09-03 02:30:58 +00:00
|
|
|
|
General[Seal + i] = val;
|
2019-04-30 00:21:19 +00:00
|
|
|
|
}
|
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 int GetMailOffset(int index)
|
|
|
|
|
{
|
|
|
|
|
int ofs = (index * Mail4.SIZE);
|
|
|
|
|
return Version switch
|
|
|
|
|
{
|
|
|
|
|
GameVersion.DP => (ofs + 0x4BEC),
|
|
|
|
|
GameVersion.Pt => (ofs + 0x4E80),
|
2021-08-20 20:49:20 +00:00
|
|
|
|
_ => (ofs + 0x3FA8),
|
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 byte[] GetMailData(int ofs) => General.Slice(ofs, Mail4.SIZE);
|
2021-05-22 16:28:04 +00:00
|
|
|
|
|
2021-08-06 03:33:25 +00:00
|
|
|
|
public Mail4 GetMail(int mailIndex)
|
2021-05-22 16:28:04 +00:00
|
|
|
|
{
|
2021-08-06 03:33:25 +00:00
|
|
|
|
int ofs = GetMailOffset(mailIndex);
|
2021-05-22 16:28:04 +00:00
|
|
|
|
return new Mail4(GetMailData(ofs), ofs);
|
|
|
|
|
}
|
2021-08-21 02:52:31 +00:00
|
|
|
|
|
|
|
|
|
public abstract uint SwarmSeed { get; set; }
|
|
|
|
|
public abstract uint SwarmMaxCountModulo { get; }
|
|
|
|
|
|
|
|
|
|
public uint SwarmIndex
|
|
|
|
|
{
|
|
|
|
|
get => SwarmSeed % SwarmMaxCountModulo;
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
value %= SwarmMaxCountModulo;
|
|
|
|
|
while (SwarmIndex != value)
|
|
|
|
|
++SwarmSeed;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
|
|
|
|
}
|