Minor tweaks

I quite like the DeSmuME footer check simplification.
This commit is contained in:
Kurt 2023-10-14 19:28:46 -07:00
parent 738c51d596
commit da27814504
9 changed files with 66 additions and 62 deletions

View file

@ -189,7 +189,7 @@ public sealed class LegalityAnalysis
Nickname.Verify(this);
Level.Verify(this);
Level.VerifyG1(this);
Trainer.VerifyOTG1(this);
Trainer.VerifyOTGB(this);
MiscValues.VerifyMiscG1(this);
if (Entity.Format == 2)
Item.Verify(this);

View file

@ -12,7 +12,7 @@ public static class ShinyUtil
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool GetIsShiny(in uint id32, in uint pid, [ConstantExpected] uint cmp = 16) => GetShinyXor(id32, pid) < cmp;
public static bool GetIsShiny(in uint id32, in uint pid, [ConstantExpected(Max = 16, Min = 8)] uint cmp = 16) => GetShinyXor(id32, pid) < cmp;
public static uint GetShinyXor(in uint pid, in uint id32)
{

View file

@ -93,25 +93,25 @@ internal static class GBRestrictions
return rate == PersonalTable.RB[species].CatchRate;
}
private static bool RateMatchesEither(byte catch_rate, ushort rate)
{
return catch_rate == PersonalTable.RB[rate].CatchRate || catch_rate == PersonalTable.Y[rate].CatchRate;
}
private static bool GetCatchRateMatchesPreEvolution(PK1 pk, byte catch_rate)
{
// For species catch rate, discard any species that has no valid encounters and a different catch rate than their pre-evolutions
var head = new EvoCriteria { Species = pk.Species, Form = pk.Form, LevelMax = (byte)pk.CurrentLevel }; // as struct to avoid boxing
while (true)
do
{
var s = head.Species;
if (!IsSpeciesNotAvailableCatchRate((byte)s))
{
if (catch_rate == PersonalTable.RB[s].CatchRate || catch_rate == PersonalTable.Y[s].CatchRate)
if (!IsSpeciesNotAvailableCatchRate((byte)s) && RateMatchesEither(catch_rate, s))
return true;
}
if (!EvolutionGroup1.Instance.TryDevolve(head, pk, head.LevelMax, 2, false, out head))
break;
}
while (EvolutionGroup1.Instance.TryDevolve(head, pk, head.LevelMax, 2, false, out head));
// Account for oddities via special catch rate encounters
if (catch_rate is 167 or 168 && IsStadiumGiftSpecies((byte)pk.Species))
if (catch_rate is 167 or 168 && IsStadiumGiftSpecies((byte)head.Species))
return true;
return false;
}

View file

@ -34,7 +34,7 @@ public sealed class TrainerNameVerifier : Verifier
if (pk.VC)
{
VerifyOTG1(data);
VerifyOTGB(data);
}
else if (ot.Length > Legal.GetMaxLengthOT(data.Info.Generation, (LanguageID)pk.Language))
{
@ -65,11 +65,11 @@ public sealed class TrainerNameVerifier : Verifier
_ => true,
};
public static bool IsEdgeCaseLength(PKM pk, IEncounterTemplate e, string ot)
public static bool IsEdgeCaseLength(PKM pk, IEncounterTemplate e, ReadOnlySpan<char> ot)
{
if (e.EggEncounter)
{
if (e is WC3 wc3 && pk.IsEgg && wc3.OT_Name == ot)
if (e is WC3 wc3 && pk.IsEgg && ot.SequenceEqual(wc3.OT_Name))
return true; // Fixed OT Mystery Gift Egg
bool eggEdge = pk.IsEgg ? pk.IsTradedEgg || pk.Format == 3 : pk.WasTradedEgg;
if (!eggEdge)
@ -86,11 +86,22 @@ public sealed class TrainerNameVerifier : Verifier
return false;
}
public void VerifyOTG1(LegalityAnalysis data)
public void VerifyOTGB(LegalityAnalysis data)
{
var pk = data.Entity;
string tr = pk.OT_Name;
var enc = data.EncounterOriginal;
if (pk.OT_Gender == 1)
{
// Transferring from RBY->Gen7 won't have OT Gender in PK1, nor will PK1 originated encounters.
// GSC Trades already checked for OT Gender matching.
if (pk is { Format: > 2, VC1: true } || enc is { Generation: 1 } or EncounterGift2 { IsGift: true })
data.AddLine(GetInvalid(LG1OTGender));
}
if (enc is IFixedTrainer { IsFixedTrainer: true })
return; // already verified
string tr = pk.OT_Name;
if (tr.Length == 0)
{
if (pk is SK2 {TID16: 0, IsRental: true})
@ -103,50 +114,46 @@ public sealed class TrainerNameVerifier : Verifier
return;
}
}
VerifyG1OTWithinBounds(data, tr);
if (pk.OT_Gender == 1)
{
if (pk is ICaughtData2 {CaughtData:0} or { Format: > 2, VC1: true } || data is {EncounterOriginal: {Generation:1} or EncounterGift2 {IsGift:true}})
data.AddLine(GetInvalid(LG1OTGender));
}
VerifyGBOTWithinBounds(data, tr);
}
private void VerifyG1OTWithinBounds(LegalityAnalysis data, ReadOnlySpan<char> str)
private void VerifyGBOTWithinBounds(LegalityAnalysis data, ReadOnlySpan<char> str)
{
if (StringConverter12.GetIsG1English(str))
{
if (str.Length > 7 && data.EncounterOriginal is not IFixedTrainer { IsFixedTrainer: true }) // OT already verified; GER shuckle has 8 chars
data.AddLine(GetInvalid(LOTLong));
}
else if (StringConverter12.GetIsG1Japanese(str))
var pk = data.Entity;
if (pk.Japanese)
{
if (str.Length > 5)
data.AddLine(GetInvalid(LOTLong));
if (!StringConverter12.GetIsG1Japanese(str))
data.AddLine(GetInvalid(LG1CharOT));
}
else if (data.Entity.Korean && StringConverter2KOR.GetIsG2Korean(str))
else if (pk.Korean)
{
if (str.Length > 5)
data.AddLine(GetInvalid(LOTLong));
if (!StringConverter2KOR.GetIsG2Korean(str))
data.AddLine(GetInvalid(LG1CharOT));
}
else if (data.EncounterOriginal is not EncounterTrade2) // OT already verified; SPA Shuckle/Voltorb transferred from French can yield 2 inaccessible chars
else
{
if (str.Length > 7)
data.AddLine(GetInvalid(LOTLong));
if (!StringConverter12.GetIsG1English(str))
data.AddLine(GetInvalid(LG1CharOT));
}
}
private static bool IsOTNameSuspicious(string name)
private static bool IsOTNameSuspicious(ReadOnlySpan<char> name)
{
foreach (var s in SuspiciousOTNames)
{
if (s.StartsWith(name, StringComparison.InvariantCultureIgnoreCase))
if (name.StartsWith(s, StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
public static bool ContainsTooManyNumbers(string str, int originalGeneration)
public static bool ContainsTooManyNumbers(ReadOnlySpan<char> str, int originalGeneration)
{
if (originalGeneration <= 3)
return false; // no limit from these generations
@ -157,11 +164,11 @@ public sealed class TrainerNameVerifier : Verifier
return count > max;
}
private static int GetNumberCount(string str)
private static int GetNumberCount(ReadOnlySpan<char> str)
{
static bool IsNumber(char c)
{
if ('' <= c)
if (c >= '')
return c <= '';
return (uint)(c - '0') <= 9;
}

View file

@ -5,9 +5,16 @@ namespace PKHeX.Core;
public static class EntityFileExtension
{
// All side-game formats that don't follow the usual pk* format
private const string ExtensionSK2 = "sk2";
private const string ExtensionCK3 = "ck3";
private const string ExtensionXK3 = "xk3";
private const string ExtensionBK4 = "bk4";
private const string ExtensionRK4 = "rk4";
private const string ExtensionPB7 = "pb7";
private const string ExtensionPB8 = "pb8";
private const string ExtensionPA8 = "pa8";
private const int CountExtra = 8;
public static IReadOnlyList<string> Extensions7b => new[] { ExtensionPB7 };
@ -19,20 +26,22 @@ public static class EntityFileExtension
public static string[] GetExtensions(int maxGeneration = PKX.Generation)
{
int min = maxGeneration is <= 2 or >= 7 ? 1 : 3;
int size = maxGeneration - min + 1 + 6;
int size = maxGeneration - min + 1 + CountExtra;
var result = new List<string>(size);
for (int i = min; i <= maxGeneration; i++)
result.Add($"pk{i}");
if (min < 3)
result.Add(ExtensionSK2); // stadium
if (maxGeneration >= 3)
{
result.Add("ck3"); // colosseum
result.Add("xk3"); // xd
result.Add(ExtensionCK3); // colosseum
result.Add(ExtensionXK3); // xd
}
if (maxGeneration >= 4)
{
result.Add("bk4"); // battle revolution
result.Add("rk4"); // My Pokemon Ranch
result.Add(ExtensionBK4); // battle revolution
result.Add(ExtensionRK4); // My Pokemon Ranch
}
if (maxGeneration >= 7)
result.Add(ExtensionPB7); // let's go
@ -56,6 +65,7 @@ public static class EntityFileExtension
return prefer;
static bool Is(ReadOnlySpan<char> ext, ReadOnlySpan<char> str) => ext.EndsWith(str, StringComparison.InvariantCultureIgnoreCase);
if (Is(ext, "a8")) return EntityContext.Gen8a;
if (Is(ext, "b8")) return EntityContext.Gen8b;
if (Is(ext, "k8")) return EntityContext.Gen8;
if (Is(ext, "b7")) return EntityContext.Gen7b;

View file

@ -8,7 +8,7 @@ namespace PKHeX.Core;
public sealed class BV6 : BattleVideo
{
internal const int SIZE = 0x2E60;
public const int SIZE = 0x2E60;
private const string NPC = "NPC";
private readonly byte[] Data;
private const int PlayerCount = 4;

View file

@ -7,7 +7,7 @@ namespace PKHeX.Core;
public sealed class BV7 : BattleVideo
{
internal const int SIZE = 0x2BC0;
public const int SIZE = 0x2BC0;
private const string NPC = "NPC";
private const int PlayerCount = 4;

View file

@ -11,20 +11,7 @@ public sealed class SaveHandlerDeSmuME : ISaveHandler
private const int RealSize = SaveUtil.SIZE_G4RAW;
private const int ExpectedSize = RealSize + sizeFooter;
private const string SignatureDSV = "|-DESMUME SAVE-|";
private static bool GetHasFooter(ReadOnlySpan<byte> input)
{
var start = input.Length - SignatureDSV.Length;
var footer = input[start..];
for (int i = SignatureDSV.Length - 1; i >= 0; i--)
{
byte c = (byte)SignatureDSV[i];
if (footer[i] != c)
return false;
}
return true;
}
private static bool GetHasFooter(ReadOnlySpan<byte> input) => input.EndsWith("|-DESMUME SAVE-|"u8);
public bool IsRecognized(long size) => size is ExpectedSize;

View file

@ -193,7 +193,7 @@ public static class SaveExtensions
/// <returns>Template if it exists, or a blank <see cref="PKM"/> from the <see cref="sav"/></returns>
public static PKM LoadTemplate(this SaveFile sav, string? templatePath = null)
{
if (templatePath == null || !Directory.Exists(templatePath))
if (!Directory.Exists(templatePath))
return LoadTemplateInternal(sav);
var di = new DirectoryInfo(templatePath);