mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-10 06:34:19 +00:00
Misc tweaks
Fix pk6/7 pkmeditor export, now retaining status condition instead of wiping it Abstract b2w2 key system to a save block; better documentation on its odd mechanics Allow gen1-3 filename language/ver detect to work if the filename is ` `->`_` (discord attachments changing spaces to underscores); also revise canadian French emerald filename pattern others: negligible perf/using standard library functions
This commit is contained in:
parent
e6cf61330a
commit
7d05e12c7f
31 changed files with 345 additions and 112 deletions
|
@ -153,7 +153,7 @@ public sealed class StringInstructionSet
|
|||
while (start < lines.Length)
|
||||
{
|
||||
var line = lines[start++];
|
||||
if (line.Length != 0 && line[0] == SetSeparatorChar)
|
||||
if (line.StartsWith(SetSeparatorChar))
|
||||
return start;
|
||||
}
|
||||
return start;
|
||||
|
|
|
@ -24,7 +24,7 @@ public sealed class GameDataSource
|
|||
/// <summary>
|
||||
/// List of <see cref="LanguageID"/> values to display.
|
||||
/// </summary>
|
||||
private static readonly List<ComboItem> LanguageList =
|
||||
private static readonly ComboItem[] LanguageList =
|
||||
[
|
||||
new ComboItem("JPN (日本語)", (int)LanguageID.Japanese),
|
||||
new ComboItem("ENG (English)", (int)LanguageID.English),
|
||||
|
@ -37,6 +37,18 @@ public sealed class GameDataSource
|
|||
new ComboItem("CHT (繁體中文)", (int)LanguageID.ChineseT),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of languages to display based on the generation.
|
||||
/// </summary>
|
||||
/// <param name="generation">Generation to get the language list for.</param>
|
||||
/// <returns>List of languages to display.</returns>
|
||||
public static IReadOnlyList<ComboItem> LanguageDataSource(byte generation) => generation switch
|
||||
{
|
||||
3 => LanguageList[..6], // No Korean+
|
||||
< 7 => LanguageList[..7], // No Chinese+
|
||||
_ => [.. LanguageList],
|
||||
};
|
||||
|
||||
public GameDataSource(GameStrings s)
|
||||
{
|
||||
Strings = s;
|
||||
|
@ -130,14 +142,4 @@ public sealed class GameDataSource
|
|||
var items = Strings.GetItemStrings(context, game);
|
||||
return HaX ? Util.GetCBList(items) : Util.GetCBList(items, allowed);
|
||||
}
|
||||
|
||||
public static IReadOnlyList<ComboItem> LanguageDataSource(byte generation)
|
||||
{
|
||||
var languages = new List<ComboItem>(LanguageList);
|
||||
if (generation == 3)
|
||||
languages.RemoveAll(static l => l.Value >= (int)LanguageID.Korean);
|
||||
else if (generation < 7)
|
||||
languages.RemoveAll(static l => l.Value > (int)LanguageID.Korean);
|
||||
return languages;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -181,8 +181,7 @@ public static class MoveBreed
|
|||
for (var i = 0; i < notBaseCount; i++)
|
||||
result[ctr++] = notBase[i];
|
||||
// Then clear the remainder
|
||||
for (int i = ctr; i < result.Length; i++)
|
||||
result[i] = 0;
|
||||
result[ctr..].Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
@ -21,7 +20,7 @@ public sealed class MedalVerifier : Verifier
|
|||
var pk = data.Entity;
|
||||
var train = (ISuperTrain)pk;
|
||||
var Info = data.Info;
|
||||
uint value = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(data.Entity.Data.AsSpan(0x2C));
|
||||
uint value = train.SuperTrainBitFlags;
|
||||
if ((value & 3) != 0) // 2 unused flags
|
||||
data.AddLine(GetInvalid(LSuperUnused));
|
||||
int TrainCount = train.SuperTrainingMedalCount();
|
||||
|
|
|
@ -56,11 +56,7 @@ public abstract class GBPKML : GBPKM
|
|||
return;
|
||||
|
||||
// Decimal point<->period fix
|
||||
foreach (ref var c in data)
|
||||
{
|
||||
if (c == 0xF2)
|
||||
c = 0xE8;
|
||||
}
|
||||
data.Replace<byte>(0xF2, 0xE8);
|
||||
}
|
||||
|
||||
public sealed override string Nickname
|
||||
|
|
|
@ -146,8 +146,8 @@ public static class EntityConverter
|
|||
private static PKM? IntermediaryConvert(PKM pk, Type destType, ref EntityConverterResult result) => pk switch
|
||||
{
|
||||
// Non-sequential
|
||||
PK1 pk1 when destType.Name[^1] - '0' > 2 => pk1.ConvertToPK7(),
|
||||
PK2 pk2 when destType.Name[^1] - '0' > 2 => pk2.ConvertToPK7(),
|
||||
PK1 pk1 when destType.Name[^1] - '0' is not (1 or 2) => pk1.ConvertToPK7(),
|
||||
PK2 pk2 when destType.Name[^1] - '0' is not (1 or 2) => pk2.ConvertToPK7(),
|
||||
PK2 pk2 when destType == typeof(SK2) => pk2.ConvertToSK2(),
|
||||
PK3 pk3 when destType == typeof(CK3) => pk3.ConvertToCK3(),
|
||||
PK3 pk3 when destType == typeof(XK3) => pk3.ConvertToXK3(),
|
||||
|
@ -224,7 +224,7 @@ public static class EntityConverter
|
|||
};
|
||||
}
|
||||
|
||||
if (destType.Name[^1] == '1' && pk.Species > Legal.MaxSpeciesID_1)
|
||||
if (destType.Name.EndsWith('1') && pk.Species > Legal.MaxSpeciesID_1)
|
||||
return IncompatibleSpecies;
|
||||
|
||||
return Success;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace PKHeX.Core;
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for Accessing named blocks within a Generation 5 save file.
|
||||
|
@ -6,5 +6,6 @@
|
|||
public interface ISaveBlock5B2W2
|
||||
{
|
||||
PWTBlock5 PWT { get; }
|
||||
KeySystem5 Keys { get; }
|
||||
FestaBlock5 Festa { get; }
|
||||
}
|
||||
|
|
|
@ -112,6 +112,7 @@ public sealed class SaveBlockAccessor5B2W2(SAV5B2W2 sav)
|
|||
public EntreeForest EntreeForest { get; } = new(sav, Block(sav, 60));
|
||||
public PWTBlock5 PWT { get; } = new(sav, Block(sav, 63));
|
||||
public MedalList5 Medals { get; } = new(sav, Block(sav, 68));
|
||||
public KeySystem5 Keys { get; } = new(sav, Block(sav, 69));
|
||||
public FestaBlock5 Festa { get; } = new(sav, Block(sav, 70));
|
||||
EventWork5 ISaveBlock5BW.EventWork => EventWork;
|
||||
Encount5 ISaveBlock5BW.Encount => Encount;
|
||||
|
|
|
@ -35,7 +35,7 @@ public sealed class SCBlockMetadata
|
|||
/// </summary>
|
||||
public IEnumerable<ComboItem> GetSortedBlockKeyList() => Accessor.BlockInfo
|
||||
.Select((z, i) => new ComboItem(GetBlockHint(z, i), (int)z.Key))
|
||||
.OrderBy(z => !(z.Text.Length != 0 && z.Text[0] == '*'))
|
||||
.OrderBy(z => !z.Text.StartsWith('*'))
|
||||
.ThenBy(z => GetSortKey(z));
|
||||
|
||||
/// <summary>
|
||||
|
@ -63,7 +63,7 @@ public sealed class SCBlockMetadata
|
|||
private static string GetSortKey(in ComboItem item)
|
||||
{
|
||||
var text = item.Text;
|
||||
if (text.Length != 0 && text[0] == '*')
|
||||
if (text.StartsWith('*'))
|
||||
return text;
|
||||
// key:X8, " - ", "####", " ", type
|
||||
return text[(8 + 3 + 4 + 1)..];
|
||||
|
|
|
@ -61,6 +61,7 @@ public sealed class SAV5B2W2 : SAV5, ISaveBlock5B2W2
|
|||
public FestaBlock5 Festa => Blocks.Festa;
|
||||
public PWTBlock5 PWT => Blocks.PWT;
|
||||
public MedalList5 Medals => Blocks.Medals;
|
||||
public KeySystem5 Keys => Blocks.Keys;
|
||||
|
||||
public string Rival
|
||||
{
|
||||
|
|
|
@ -58,11 +58,7 @@ public static class SlotPointerUtil
|
|||
public static void UpdateRepointFrom(int newIndex, int oldIndex, Span<int> slotPointers)
|
||||
{
|
||||
// Don't return on first match; assume multiple pointers can point to the same slot
|
||||
foreach (ref var ptr in slotPointers)
|
||||
{
|
||||
if (ptr == oldIndex)
|
||||
ptr = newIndex;
|
||||
}
|
||||
slotPointers.Replace(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
public static void UpdateMove(int bMove, int cMove, int slotsPerBox, params IList<int>[] ptrset)
|
||||
|
|
|
@ -5,8 +5,8 @@ namespace PKHeX.Core;
|
|||
public sealed class BoxLayout5(SAV5 sav, Memory<byte> raw) : SaveBlock<SAV5>(sav, raw)
|
||||
{
|
||||
public int CurrentBox { get => Data[0]; set => Data[0] = (byte)value; }
|
||||
public int GetBoxNameOffset(int box) => (0x28 * box) + 4;
|
||||
public int GetBoxWallpaperOffset(int box) => 0x3C4 + box;
|
||||
private static int GetBoxNameOffset(int box) => (0x28 * box) + 4;
|
||||
private static int GetBoxWallpaperOffset(int box) => 0x3C4 + box;
|
||||
|
||||
public int GetBoxWallpaper(int box)
|
||||
{
|
||||
|
|
216
PKHeX.Core/Saves/Substructures/Gen5/KeySystem5.cs
Normal file
216
PKHeX.Core/Saves/Substructures/Gen5/KeySystem5.cs
Normal file
|
@ -0,0 +1,216 @@
|
|||
using System;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class KeySystem5(SAV5B2W2 SAV, Memory<byte> raw) : SaveBlock<SAV5B2W2>(SAV, raw)
|
||||
{
|
||||
// 0x00-0x27: Unknown
|
||||
private const int OffsetKeysObtained = 0x28; // 5 * sizeof(uint)
|
||||
private const int OffsetKeysUnlocked = 0x3C; // 5 * sizeof(uint)
|
||||
// 3x selections (Difficulty, City, Chamber) - 3 * sizeof(uint)
|
||||
private const int OffsetCrypto = 0x5C;
|
||||
|
||||
// The game uses a simple XOR encryption and hardcoded magic numbers to indicate selections and key status.
|
||||
// If the value is not zero, it is encrypted with the XOR key. Compare to the associated magic number.
|
||||
// Magic numbers (game code), found in ram: 0x0208FA24
|
||||
// Selections - Magic Numbers
|
||||
private const uint MagicCityWhiteForest = 0x34525;
|
||||
private const uint MagicCityBlackCity = 0x11963;
|
||||
private const uint MagicDifficultyEasy = 0x31239;
|
||||
private const uint MagicDifficultyNormal = 0x15657;
|
||||
private const uint MagicDifficultyChallenge = 0x49589;
|
||||
private const uint MagicMysteryDoorRock = 0x94525;
|
||||
private const uint MagicMysteryDoorIron = 0x81963;
|
||||
private const uint MagicMysteryDoorIceberg = 0x38569;
|
||||
|
||||
// magic numbers, immediately after ^ in ram. same as below set
|
||||
private static ReadOnlySpan<uint> MagicKeyObtained =>
|
||||
[
|
||||
0x35691, // 0x28 Obtained Key (EasyMode)
|
||||
0x18256, // 0x2C Obtained Key (Challenge)
|
||||
0x59389, // 0x30 Obtained Key (City)
|
||||
0x48292, // 0x34 Obtained Key (Iron)
|
||||
0x09892, // 0x38 Obtained Key (Iceberg)
|
||||
];
|
||||
|
||||
private static ReadOnlySpan<uint> MagicKeyUnlocked =>
|
||||
[
|
||||
0x93389, // 0x3C Unlocked (EasyMode)
|
||||
0x22843, // 0x40 Unlocked (Challenge)
|
||||
0x34771, // 0x44 Unlocked (City)
|
||||
0xAB031, // 0x48 Unlocked (Iron)
|
||||
0xB3818, // 0x4C Unlocked (Iceberg)
|
||||
];
|
||||
|
||||
public bool GetIsKeyObtained(KeyType5 key)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan((uint)key, (uint)KeyType5.Iceberg);
|
||||
var offset = OffsetKeysObtained + (sizeof(uint) * (int)key);
|
||||
var expect = MagicKeyObtained[(int)key] ^ Crypto;
|
||||
var value = ReadUInt32LittleEndian(Data[offset..]);
|
||||
return value == expect;
|
||||
}
|
||||
|
||||
public void SetIsKeyObtained(KeyType5 key, bool value)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan((uint)key, (uint)KeyType5.Iceberg);
|
||||
var offset = OffsetKeysObtained + (sizeof(uint) * (int)key);
|
||||
var expect = MagicKeyObtained[(int)key] ^ Crypto;
|
||||
WriteUInt32LittleEndian(Data[offset..], value ? expect : 0);
|
||||
}
|
||||
|
||||
public bool GetIsKeyUnlocked(KeyType5 key)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan((uint)key, (uint)KeyType5.Iceberg);
|
||||
var offset = OffsetKeysUnlocked + (sizeof(uint) * (int)key);
|
||||
var expect = MagicKeyUnlocked[(int)key + 5] ^ Crypto;
|
||||
var value = ReadUInt32LittleEndian(Data[offset..]);
|
||||
return value == expect;
|
||||
}
|
||||
|
||||
public void SetIsKeyUnlocked(KeyType5 key, bool value)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan((uint)key, (uint)KeyType5.Iceberg);
|
||||
var offset = OffsetKeysUnlocked + (sizeof(uint) * (int)key);
|
||||
var expect = MagicKeyUnlocked[(int)key + 5] ^ Crypto;
|
||||
WriteUInt32LittleEndian(Data[offset..], value ? expect : 0);
|
||||
}
|
||||
|
||||
// 0x50 - Difficulty Selected (uses selection's magic number) - 0 if default
|
||||
public Difficulty5 ActiveDifficulty
|
||||
{
|
||||
get
|
||||
{
|
||||
uint value = ReadUInt32LittleEndian(Data[0x50..]);
|
||||
if (value is 0)
|
||||
return Difficulty5.Normal;
|
||||
|
||||
var xor = value ^ Crypto;
|
||||
return xor switch
|
||||
{
|
||||
MagicDifficultyEasy => Difficulty5.Easy,
|
||||
MagicDifficultyNormal => Difficulty5.Normal,
|
||||
MagicDifficultyChallenge => Difficulty5.Challenge,
|
||||
_ => Difficulty5.Normal, // default
|
||||
};
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value is Difficulty5.Normal)
|
||||
{
|
||||
WriteUInt32LittleEndian(Data[0x50..], 0);
|
||||
return;
|
||||
}
|
||||
uint write = value switch
|
||||
{
|
||||
Difficulty5.Easy => MagicDifficultyEasy,
|
||||
Difficulty5.Challenge => MagicDifficultyChallenge,
|
||||
_ => MagicDifficultyNormal, // default
|
||||
};
|
||||
WriteUInt32LittleEndian(Data[0x50..], write ^ Crypto);
|
||||
}
|
||||
}
|
||||
|
||||
// 0x54 - City Selected (uses selection's magic number) - 0 if default
|
||||
public City5 ActiveCity
|
||||
{
|
||||
get
|
||||
{
|
||||
var initial = SAV.Version is GameVersion.W2 ? City5.WhiteForest : City5.BlackCity;
|
||||
uint value = ReadUInt32LittleEndian(Data[0x54..]);
|
||||
if (value is 0)
|
||||
return initial;
|
||||
|
||||
var xor = value ^ Crypto;
|
||||
return xor switch
|
||||
{
|
||||
MagicCityWhiteForest => City5.WhiteForest,
|
||||
MagicCityBlackCity => City5.BlackCity,
|
||||
_ => initial, // default
|
||||
};
|
||||
}
|
||||
set
|
||||
{
|
||||
var initial = SAV.Version is GameVersion.W2 ? City5.WhiteForest : City5.BlackCity;
|
||||
if (value == initial)
|
||||
{
|
||||
WriteUInt32LittleEndian(Data[0x54..], 0);
|
||||
return;
|
||||
}
|
||||
uint write = value == City5.WhiteForest ? MagicCityWhiteForest : MagicCityBlackCity;
|
||||
WriteUInt32LittleEndian(Data[0x54..], write ^ Crypto);
|
||||
}
|
||||
}
|
||||
|
||||
// 0x58 - Chamber Selected (uses selection's magic number) - 0 if default
|
||||
public Chamber5 ActiveChamber
|
||||
{
|
||||
get
|
||||
{
|
||||
uint value = ReadUInt32LittleEndian(Data[0x58..]);
|
||||
if (value is 0)
|
||||
return Chamber5.Rock;
|
||||
|
||||
var xor = value ^ Crypto;
|
||||
return xor switch
|
||||
{
|
||||
MagicMysteryDoorRock => Chamber5.Rock,
|
||||
MagicMysteryDoorIron => Chamber5.Iron,
|
||||
MagicMysteryDoorIceberg => Chamber5.Iceberg,
|
||||
_ => Chamber5.Rock, // default
|
||||
};
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value is Chamber5.Rock)
|
||||
{
|
||||
WriteUInt32LittleEndian(Data[0x58..], 0);
|
||||
return;
|
||||
}
|
||||
uint write = value switch
|
||||
{
|
||||
Chamber5.Iron => MagicMysteryDoorIron,
|
||||
Chamber5.Iceberg => MagicMysteryDoorIceberg,
|
||||
_ => MagicMysteryDoorRock, // default
|
||||
};
|
||||
WriteUInt32LittleEndian(Data[0x58..], write ^ Crypto);
|
||||
}
|
||||
}
|
||||
|
||||
// This value should be > 0xFFFFF to ensure the magic numbers aren't visible in savedata.
|
||||
public uint Crypto
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Data[OffsetCrypto..]);
|
||||
set => WriteUInt32LittleEndian(Data[OffsetCrypto..], value);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Difficulty5
|
||||
{
|
||||
Easy = 0,
|
||||
Normal = 1,
|
||||
Challenge = 2,
|
||||
}
|
||||
|
||||
public enum City5
|
||||
{
|
||||
WhiteForest = 0,
|
||||
BlackCity = 1,
|
||||
}
|
||||
|
||||
public enum Chamber5
|
||||
{
|
||||
Rock = 0,
|
||||
Iron = 1,
|
||||
Iceberg = 2,
|
||||
}
|
||||
|
||||
public enum KeyType5
|
||||
{
|
||||
Easy = 0,
|
||||
Challenge = 1,
|
||||
City = 2,
|
||||
Iron = 3,
|
||||
Iceberg = 4,
|
||||
}
|
|
@ -51,6 +51,22 @@ public sealed class Misc5BW(SAV5BW sav, Memory<byte> raw) : Misc5(sav, raw)
|
|||
{
|
||||
protected override int TransferMinigameScoreOffset => 0x14;
|
||||
protected override int BadgeVictoryOffset => 0x58; // thru 0xB7
|
||||
|
||||
public const uint LibertyTicketMagic = 2010_04_06; // 0x132B536
|
||||
|
||||
public uint LibertyTicketState
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Data[0xBC..]);
|
||||
set => WriteUInt32LittleEndian(Data[0xBC..], value);
|
||||
}
|
||||
|
||||
public uint LibertyTicketExpectValue => LibertyTicketMagic ^ sav.ID32;
|
||||
|
||||
public bool IsLibertyTicketActivated
|
||||
{
|
||||
get => LibertyTicketState == LibertyTicketExpectValue;
|
||||
set => LibertyTicketState = value ? LibertyTicketExpectValue : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Misc5B2W2(SAV5B2W2 sav, Memory<byte> raw) : Misc5(sav, raw)
|
||||
|
|
|
@ -22,7 +22,7 @@ public sealed class BoxLayout6 : SaveBlock<SAV6>, IBoxDetailName, IBoxDetailWall
|
|||
public BoxLayout6(SAV6XY sav, Memory<byte> raw) : base(sav, raw) { }
|
||||
public BoxLayout6(SAV6AO sav, Memory<byte> raw) : base(sav, raw) { }
|
||||
|
||||
public int GetBoxWallpaperOffset(int box) => PCBackgrounds + box;
|
||||
private static int GetBoxWallpaperOffset(int box) => PCBackgrounds + box;
|
||||
|
||||
public int GetBoxWallpaper(int box)
|
||||
{
|
||||
|
|
|
@ -50,6 +50,8 @@ public sealed class FashionBlock7(SAV7 sav, Memory<byte> raw) : SaveBlock<SAV7>(
|
|||
private static ReadOnlySpan<ushort> DefaultFashionOffsetUU_F => [ 0x05E, 0x208, 0x264, 0x395, 0x3B4, 0x4F9, 0x5A8 ];
|
||||
|
||||
public void ImportPayload(ReadOnlySpan<byte> data) => SAV.SetData(Data[..FashionLength], data);
|
||||
|
||||
public void GiveAgentSunglasses() => Data[0xD0] = 3;
|
||||
}
|
||||
|
||||
// Every fashion item is 2 bits, New Flag (high) & Owned Flag (low)
|
||||
|
|
|
@ -42,7 +42,7 @@ public sealed class BattleTrainerStatus8b(SAV8BS sav, Memory<byte> raw) : SaveBl
|
|||
}
|
||||
}
|
||||
|
||||
private int GetTrainerOffset(int trainer)
|
||||
private static int GetTrainerOffset(int trainer)
|
||||
{
|
||||
if ((uint)trainer >= COUNT_TRAINER)
|
||||
throw new ArgumentOutOfRangeException(nameof(trainer));
|
||||
|
|
|
@ -138,6 +138,18 @@ public static class SaveLanguage
|
|||
private static bool Contains(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
|
||||
=> span.Contains(value, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static bool ContainsSpU(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
|
||||
{
|
||||
if (Contains(span, value))
|
||||
return true;
|
||||
|
||||
// Check for underscores too; replace the input w/ spaces to underscore
|
||||
Span<char> tmp = stackalloc char[value.Length];
|
||||
value.CopyTo(tmp);
|
||||
tmp.Replace(' ', '_');
|
||||
return Contains(span, tmp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="InferFrom(SAV1)"/>
|
||||
public static SaveLanguageResult InferFrom1(ReadOnlySpan<char> name, GameVersion hint = Any)
|
||||
{
|
||||
|
@ -191,9 +203,9 @@ public static class SaveLanguage
|
|||
if (MaybeGD(hint)) {
|
||||
if (Contains(name, "golde")) return (German, GD);
|
||||
if (Contains(name, "gold")) return (English, GD);
|
||||
if (Contains(name, "e oro")) return (Italian, GD);
|
||||
if (ContainsSpU(name, "e oro")) return (Italian, GD);
|
||||
if (Contains(name, "oro")) return (Spanish, GD);
|
||||
if (Contains(name, "n or")) return (French, GD);
|
||||
if (ContainsSpU(name, "n or")) return (French, GD);
|
||||
if (Contains(name, "金")) return (Japanese, GD);
|
||||
if (Contains(name, "금")) return (Korean, GD);
|
||||
|
||||
|
@ -204,7 +216,7 @@ public static class SaveLanguage
|
|||
if (Contains(name, "silv")) return (English, SI);
|
||||
if (Contains(name, "silb")) return (German, SI);
|
||||
if (Contains(name, "plat")) return (Spanish, SI);
|
||||
if (Contains(name, "e arg")) return (Italian, SI);
|
||||
if (ContainsSpU(name, "e arg")) return (Italian, SI);
|
||||
if (Contains(name, "arge")) return (French, SI);
|
||||
if (Contains(name, "銀")) return (Japanese, SI);
|
||||
if (Contains(name, "은")) return (Korean, SI);
|
||||
|
@ -216,8 +228,8 @@ public static class SaveLanguage
|
|||
if (Contains(name, "cry")) return (English, C);
|
||||
if (Contains(name, "kri")) return (German, C);
|
||||
if (Contains(name, "cristall")) return (Italian, C);
|
||||
if (Contains(name, "on cri")) return (French, C);
|
||||
if (Contains(name, "ón crist")) return (Spanish, C);
|
||||
if (ContainsSpU(name, "on cri")) return (French, C);
|
||||
if (ContainsSpU(name, "ón crist")) return (Spanish, C);
|
||||
if (Contains(name, "クリ")) return (Japanese, C);
|
||||
}
|
||||
|
||||
|
@ -270,7 +282,7 @@ public static class SaveLanguage
|
|||
if (Contains(name, "esm")) return (Spanish, E);
|
||||
if (Contains(name, "smar")) return (German, E);
|
||||
if (Contains(name, "smer")) return (Italian, E);
|
||||
if (Contains(name, "éme") || Contains(name, "n eme")) return (French, E);
|
||||
if (Contains(name, "merau") || ContainsSpU(name, "ion emerald de")) return (French, E);
|
||||
if (Contains(name, "emer")) return (English, E);
|
||||
if (Contains(name, "エメ")) return (Japanese, E);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using PKHeX.Core;
|
||||
|
||||
namespace PKHeX.WinForms.Controls;
|
||||
|
@ -39,11 +39,9 @@ public partial class PKMEditor
|
|||
// Toss in Party Stats
|
||||
SavePartyStats(pk6);
|
||||
|
||||
// Unneeded Party Stats (Status, Flags, Unused)
|
||||
pk6.Data[0xE8] = pk6.Data[0xE9] = pk6.Data[0xEA] = pk6.Data[0xEB] =
|
||||
pk6.Data[0xEF] =
|
||||
pk6.Data[0xFE] = pk6.Data[0xFF] = pk6.Data[0x100] =
|
||||
pk6.Data[0x101] = pk6.Data[0x102] = pk6.Data[0x103] = 0;
|
||||
// Ensure party stats are essentially clean.
|
||||
pk6.Data.AsSpan(0xFE).Clear();
|
||||
// Status Condition is allowed to be mutated to pre-set conditions like Burn for Guts.
|
||||
|
||||
pk6.FixMoves();
|
||||
pk6.FixRelearn();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using PKHeX.Core;
|
||||
|
||||
namespace PKHeX.WinForms.Controls;
|
||||
|
@ -34,11 +34,9 @@ public partial class PKMEditor
|
|||
// Toss in Party Stats
|
||||
SavePartyStats(pk7);
|
||||
|
||||
// Unneeded Party Stats (Status, Flags, Unused)
|
||||
pk7.Status_Condition = pk7.DirtType = pk7.DirtLocation =
|
||||
pk7.Data[0xEF] =
|
||||
pk7.Data[0xFE] = pk7.Data[0xFF] = pk7.Data[0x100] =
|
||||
pk7.Data[0x101] = pk7.Data[0x102] = pk7.Data[0x103] = 0;
|
||||
// Ensure party stats are essentially clean.
|
||||
pk7.Data.AsSpan(0xFE).Clear();
|
||||
// Status Condition is allowed to be mutated to pre-set conditions like Burn for Guts.
|
||||
|
||||
pk7.FixMoves();
|
||||
pk7.FixRelearn();
|
||||
|
|
|
@ -1207,7 +1207,7 @@ public sealed partial class PKMEditor : UserControl, IMainEditor
|
|||
{
|
||||
bool g4 = Entity.Gen4;
|
||||
CB_GroundTile.Visible = Label_GroundTile.Visible = g4 && Entity.Format < 7;
|
||||
if (!g4)
|
||||
if (FieldsLoaded && !g4)
|
||||
CB_GroundTile.SelectedValue = (int)GroundTileType.None;
|
||||
}
|
||||
|
||||
|
|
|
@ -228,12 +228,14 @@ public partial class Main : Form
|
|||
showChangelog = false;
|
||||
|
||||
// Version Check
|
||||
if (Settings.Startup.Version.Length != 0 && Settings.Startup.ShowChangelogOnUpdate) // already run on system
|
||||
var ver = Program.CurrentVersion;
|
||||
var startup = Settings.Startup;
|
||||
if (startup.ShowChangelogOnUpdate && startup.Version.Length != 0) // already run on system
|
||||
{
|
||||
bool parsed = Version.TryParse(Settings.Startup.Version, out var lastrev);
|
||||
showChangelog = parsed && lastrev < Program.CurrentVersion;
|
||||
bool parsed = Version.TryParse(startup.Version, out var lastrev);
|
||||
showChangelog = parsed && lastrev < ver;
|
||||
}
|
||||
Settings.Startup.Version = Program.CurrentVersion.ToString(); // set current version so this doesn't happen until the user updates next time
|
||||
startup.Version = ver.ToString(); // set current version so this doesn't happen until the user updates next time
|
||||
|
||||
// BAK Prompt
|
||||
if (!Settings.Backup.BAKPrompt)
|
||||
|
@ -261,6 +263,8 @@ public partial class Main : Form
|
|||
|
||||
private void FormLoadPlugins()
|
||||
{
|
||||
if (Plugins.Count != 0)
|
||||
return; // already loaded
|
||||
#if !MERGED // merged should load dlls from within too, folder is no longer required
|
||||
if (!Directory.Exists(PluginPath))
|
||||
return;
|
||||
|
|
|
@ -35,6 +35,8 @@ public partial class MemoryAmie : Form
|
|||
{
|
||||
tabControl1.TabPages.Remove(Tab_Residence);
|
||||
}
|
||||
if (Entity is PK9)
|
||||
tabControl1.TabPages.Remove(Tab_Other); // No Fullness/Enjoyment stored.
|
||||
|
||||
GetLangStrings();
|
||||
LoadFields();
|
||||
|
@ -275,20 +277,22 @@ public partial class MemoryAmie : Form
|
|||
|
||||
private string GetMemoryString(ComboBox m, Control arg, Control q, Control f, string tr)
|
||||
{
|
||||
var messages = GameInfo.Strings.memories;
|
||||
string result;
|
||||
bool enabled;
|
||||
int mem = WinFormsUtil.GetIndex(m);
|
||||
if (mem == 0)
|
||||
{
|
||||
string nn = Entity.Nickname;
|
||||
result = string.Format(GameInfo.Strings.memories[0], nn);
|
||||
result = string.Format(messages[0], nn);
|
||||
enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var msg = (uint)mem < messages.Length ? messages[mem] : $"{mem}";
|
||||
string nn = Entity.Nickname;
|
||||
string a = arg.Text;
|
||||
result = string.Format(GameInfo.Strings.memories[mem], nn, tr, a, f.Text, q.Text);
|
||||
result = string.Format(msg, nn, tr, a, f.Text, q.Text);
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
|
@ -353,8 +357,12 @@ public partial class MemoryAmie : Form
|
|||
|
||||
private void ClickResetLocation(object sender, EventArgs e)
|
||||
{
|
||||
Label[] senderarr = [L_Geo0, L_Geo1, L_Geo2, L_Geo3, L_Geo4];
|
||||
int index = Array.IndexOf(senderarr, sender);
|
||||
if (sender is not Label l)
|
||||
return;
|
||||
Label[] labels = [L_Geo0, L_Geo1, L_Geo2, L_Geo3, L_Geo4];
|
||||
int index = Array.IndexOf(labels, l);
|
||||
if (index < 0)
|
||||
return;
|
||||
PrevCountries[index].SelectedValue = 0;
|
||||
|
||||
PrevRegions[index].InitializeBinding();
|
||||
|
|
|
@ -215,7 +215,7 @@ public partial class ReportGrid : Form
|
|||
private static string[] ConvertTabbedToRedditTable(ReadOnlySpan<string> lines)
|
||||
{
|
||||
string[] newlines = new string[lines.Length + 1];
|
||||
int tabcount = lines[0].Count(c => c == '\t');
|
||||
int tabcount = lines[0].AsSpan().Count('\t');
|
||||
|
||||
newlines[0] = lines[0].Replace('\t', '|');
|
||||
newlines[1] = string.Join(":--:", Enumerable.Repeat('|', tabcount + 2)); // 2 pipes for each end
|
||||
|
|
|
@ -288,7 +288,7 @@ public partial class SAV_FolderList : Form
|
|||
return list;
|
||||
}
|
||||
|
||||
private static void CleanBackups(string path, bool deleteNotSaves)
|
||||
public static void CleanBackups(string path, bool deleteNotSaves)
|
||||
{
|
||||
var files = Directory.GetFiles(path);
|
||||
foreach (var file in files)
|
||||
|
@ -371,14 +371,14 @@ public partial class SAV_FolderList : Form
|
|||
var cm = (CurrencyManager?)BindingContext?[dg.DataSource];
|
||||
cm?.SuspendBinding();
|
||||
int column = CB_FilterColumn.SelectedIndex - 1;
|
||||
var text = TB_FilterTextContains.Text;
|
||||
var text = TB_FilterTextContains.Text.AsSpan();
|
||||
|
||||
for (int i = 0; i < dg.RowCount; i++)
|
||||
ToggleRowVisibility(dg, column, text, i);
|
||||
cm?.ResumeBinding();
|
||||
}
|
||||
|
||||
private static void ToggleRowVisibility(DataGridView dg, int column, string text, int rowIndex)
|
||||
private static void ToggleRowVisibility(DataGridView dg, int column, ReadOnlySpan<char> text, int rowIndex)
|
||||
{
|
||||
var row = dg.Rows[rowIndex];
|
||||
if (text.Length == 0 || column < 0)
|
||||
|
@ -393,6 +393,6 @@ public partial class SAV_FolderList : Form
|
|||
row.Visible = false;
|
||||
return;
|
||||
}
|
||||
row.Visible = value.Contains(text, StringComparison.CurrentCultureIgnoreCase); // case insensitive contains
|
||||
row.Visible = value.AsSpan().Contains(text, StringComparison.CurrentCultureIgnoreCase); // case insensitive contains
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,11 +20,6 @@ public partial class SAV_Misc5 : Form
|
|||
private ComboBox[] cbr = null!;
|
||||
private int ofsFly;
|
||||
private int[] FlyDestC = null!;
|
||||
private const int ofsLibPass = 0x212BC;
|
||||
private const uint keyLibPass = 2010_04_06; // 0x132B536
|
||||
private uint valLibPass;
|
||||
private bool bLibPass;
|
||||
private const int ofsKS = 0x25828;
|
||||
|
||||
public SAV_Misc5(SAV5 sav)
|
||||
{
|
||||
|
@ -72,18 +67,6 @@ public partial class SAV_Misc5 : Form
|
|||
|
||||
private void SaveRecord() => SAV.Records.EndAccess();
|
||||
|
||||
private static ReadOnlySpan<uint> keyKS =>
|
||||
[
|
||||
// 0x34525, 0x11963, // Selected City
|
||||
// 0x31239, 0x15657, 0x49589, // Selected Difficulty
|
||||
// 0x94525, 0x81963, 0x38569, // Selected Mystery Door
|
||||
0x35691, 0x18256, 0x59389, 0x48292, 0x09892, // Obtained Keys(EasyMode, Challenge, City, Iron, Iceberg)
|
||||
0x93389, 0x22843, 0x34771, 0xAB031, 0xB3818, // Unlocked(EasyMode, Challenge, City, Iron, Iceberg)
|
||||
];
|
||||
|
||||
private uint[] valKS = null!;
|
||||
private bool[] bKS = null!;
|
||||
|
||||
private void ReadMain()
|
||||
{
|
||||
string[]? FlyDestA;
|
||||
|
@ -180,30 +163,25 @@ public partial class SAV_Misc5 : Form
|
|||
}
|
||||
|
||||
// LibertyPass
|
||||
valLibPass = keyLibPass ^ SAV.ID32;
|
||||
bLibPass = ReadUInt32LittleEndian(SAV.Data.AsSpan(ofsLibPass)) == valLibPass;
|
||||
CHK_LibertyPass.Checked = bLibPass;
|
||||
CHK_LibertyPass.Checked = bw.Misc.IsLibertyTicketActivated;
|
||||
}
|
||||
else if (SAV is SAV5B2W2)
|
||||
else if (SAV is SAV5B2W2 b2w2)
|
||||
{
|
||||
TC_Misc.TabPages.Remove(TAB_BWCityForest);
|
||||
GB_Roamer.Visible = CHK_LibertyPass.Visible = false;
|
||||
|
||||
var keys = b2w2.Keys;
|
||||
// KeySystem
|
||||
string[] KeySystemA =
|
||||
[
|
||||
"Obtain EasyKey", "Obtain ChallengeKey", "Obtain CityKey", "Obtain IronKey", "Obtain IcebergKey",
|
||||
"Unlock EasyMode", "Unlock ChallengeMode", "Unlock City", "Unlock IronChamber",
|
||||
"Unlock IcebergChamber",
|
||||
"Unlock EasyMode", "Unlock ChallengeMode", "Unlock City", "Unlock IronChamber", "Unlock IcebergChamber",
|
||||
];
|
||||
uint KSID = ReadUInt32LittleEndian(SAV.Data.AsSpan(ofsKS + 0x34));
|
||||
valKS = new uint[keyKS.Length];
|
||||
bKS = new bool[keyKS.Length];
|
||||
CLB_KeySystem.Items.Clear();
|
||||
for (int i = 0; i < valKS.Length; i++)
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
valKS[i] = keyKS[i] ^ KSID;
|
||||
bKS[i] = ReadUInt32LittleEndian(SAV.Data.AsSpan(ofsKS + (i << 2))) == valKS[i];
|
||||
CLB_KeySystem.Items.Add(KeySystemA[i], bKS[i]);
|
||||
CLB_KeySystem.Items.Add(KeySystemA[i], keys.GetIsKeyObtained((KeyType5)i));
|
||||
CLB_KeySystem.Items.Add(KeySystemA[i + 5], keys.GetIsKeyUnlocked((KeyType5)i));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -274,19 +252,23 @@ public partial class SAV_Misc5 : Form
|
|||
}
|
||||
|
||||
// LibertyPass
|
||||
if (CHK_LibertyPass.Checked != bLibPass)
|
||||
WriteUInt32LittleEndian(SAV.Data.AsSpan(ofsLibPass), bLibPass ? 0u : valLibPass);
|
||||
if (CHK_LibertyPass.Checked != bw.Misc.IsLibertyTicketActivated)
|
||||
bw.Misc.IsLibertyTicketActivated = CHK_LibertyPass.Checked;
|
||||
}
|
||||
else if (SAV is SAV5B2W2)
|
||||
else if (SAV is SAV5B2W2 b2w2)
|
||||
{
|
||||
// KeySystem
|
||||
for (int i = 0; i < CLB_KeySystem.Items.Count; i++)
|
||||
var keys = b2w2.Keys;
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
if (CLB_KeySystem.GetItemChecked(i) == bKS[i])
|
||||
continue;
|
||||
var dest = SAV.Data.AsSpan(ofsKS + (i << 2));
|
||||
var value = bKS[i] ? 0u : valKS[i];
|
||||
WriteUInt32LittleEndian(dest, value);
|
||||
var index = i * 2;
|
||||
var obtain = CLB_KeySystem.GetItemChecked(index);
|
||||
if (obtain != keys.GetIsKeyObtained((KeyType5)i))
|
||||
keys.SetIsKeyObtained((KeyType5)i, obtain);
|
||||
|
||||
var unlock = CLB_KeySystem.GetItemChecked(index + 1);
|
||||
if (unlock != keys.GetIsKeyUnlocked((KeyType5)i))
|
||||
keys.SetIsKeyUnlocked((KeyType5)i, unlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -756,7 +756,7 @@ public partial class SAV_FestivalPlaza : Form
|
|||
{
|
||||
if (NUD_Grade.Value < 30 && DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, "Agent Sunglasses is reward of Grade 30.", "Continue?"))
|
||||
return;
|
||||
SAV.Fashion.Data[0xD0] = 3;
|
||||
SAV.Fashion.GiveAgentSunglasses();
|
||||
B_AgentGlass.Enabled = false;
|
||||
System.Media.SystemSounds.Asterisk.Play();
|
||||
}
|
||||
|
|
|
@ -91,6 +91,7 @@ public partial class SAV_Misc8b : Form
|
|||
{
|
||||
Unlocker.UnlockZones();
|
||||
System.Media.SystemSounds.Asterisk.Play();
|
||||
B_Zones.Enabled = false;
|
||||
}
|
||||
|
||||
private void B_DefeatEyecatch_Click(object sender, EventArgs e)
|
||||
|
@ -113,5 +114,6 @@ public partial class SAV_Misc8b : Form
|
|||
{
|
||||
Unlocker.UnlockFashion();
|
||||
System.Media.SystemSounds.Asterisk.Play();
|
||||
B_Fashion.Enabled = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,8 +29,7 @@ public partial class SAV_PokedexSVKitakami : Form
|
|||
CB_Species.Items.Clear();
|
||||
|
||||
var empty = new string[32];
|
||||
foreach (ref var x in empty.AsSpan())
|
||||
x = string.Empty;
|
||||
empty.AsSpan().Fill(string.Empty);
|
||||
CLB_FormSeen.Items.AddRange(empty);
|
||||
CLB_FormObtained.Items.AddRange(empty);
|
||||
CLB_FormHeard.Items.AddRange(empty);
|
||||
|
|
|
@ -302,7 +302,7 @@ public static class WinFormsUtil
|
|||
public static bool SavePKMDialog(PKM pk)
|
||||
{
|
||||
string pkx = pk.Extension;
|
||||
bool allowEncrypted = pk.Format >= 3 && pkx[0] == 'p';
|
||||
bool allowEncrypted = pk.Format >= 3 && pkx.StartsWith('p');
|
||||
var genericFilter = $"Decrypted PKM File|*.{pkx}" +
|
||||
(allowEncrypted ? $"|Encrypted PKM File|*.e{pkx[1..]}" : string.Empty) +
|
||||
"|Binary File|*.bin" +
|
||||
|
|
|
@ -168,7 +168,8 @@ public class ShowdownSetTests
|
|||
public void SimulatorParseDuplicate(string text, int moveCount)
|
||||
{
|
||||
var set = new ShowdownSet(text);
|
||||
var actual = set.Moves.Count(z => z != 0);
|
||||
var result = set.Moves.AsSpan();
|
||||
var actual = result.Length - result.Count<ushort>(0);
|
||||
actual.Should().Be(moveCount);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue