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:
Kurt 2024-06-01 17:44:32 -05:00
parent e6cf61330a
commit 7d05e12c7f
31 changed files with 345 additions and 112 deletions

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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>

View file

@ -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();

View file

@ -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

View file

@ -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;

View file

@ -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; }
}

View file

@ -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;

View file

@ -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)..];

View file

@ -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
{

View file

@ -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)

View file

@ -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)
{

View 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,
}

View file

@ -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)

View file

@ -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)
{

View file

@ -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)

View file

@ -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));

View file

@ -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);
}

View file

@ -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();

View file

@ -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();

View file

@ -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;
}

View file

@ -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;

View file

@ -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();

View file

@ -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

View file

@ -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
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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" +

View file

@ -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);
}