mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-04-28 03:27:14 -05:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2e0381193 | ||
|
|
b4c5bd96e6 | ||
|
|
72847381cc | ||
|
|
a85257da7d | ||
|
|
c2bb4188e5 | ||
|
|
906cae24e8 | ||
|
|
2ff7ad40d9 | ||
|
|
cc7708e9d4 | ||
|
|
3d7a371395 | ||
|
|
8a1c364207 | ||
|
|
0a1e955007 | ||
|
|
e3fd457272 | ||
|
|
08178f70fe | ||
|
|
75e2a7a497 | ||
|
|
516cc25d91 | ||
|
|
4007eb7ca8 | ||
|
|
452c8b5b81 | ||
|
|
ee79ba2f61 | ||
|
|
dfcd86d950 | ||
|
|
c58981e365 | ||
|
|
2f9512e33b | ||
|
|
234f402beb | ||
|
|
021b93b87f | ||
|
|
227b9aaf13 | ||
|
|
2ece772735 | ||
|
|
2cb62f7aa4 | ||
|
|
6b8599b6ed | ||
|
|
b449867e72 | ||
|
|
9c03ef1f23 | ||
|
|
4568392f61 | ||
|
|
2b4a01512e | ||
|
|
13110adfd7 | ||
|
|
2321e764cb | ||
|
|
298a53130c | ||
|
|
d7284ce590 | ||
|
|
301bf1bec0 | ||
|
|
fc80208a06 | ||
|
|
9742ed43da | ||
|
|
0ebf575a03 | ||
|
|
097ff3a870 | ||
|
|
6da88d6232 | ||
|
|
e132a2719a | ||
|
|
5e7fc6c817 | ||
|
|
245873aa8a | ||
|
|
f31503ac7c | ||
|
|
8d24b16fe3 | ||
|
|
d21bc2ff6d | ||
|
|
7c4b15bffa | ||
|
|
02eaca9a0c | ||
|
|
dc2a60f830 | ||
|
|
d09a425e6a | ||
|
|
fcdce737a3 | ||
|
|
d9bdbcae68 | ||
|
|
5f44fb75d4 | ||
|
|
d15526a0be | ||
|
|
5567577a26 | ||
|
|
e41797d0cb | ||
|
|
5b511a8078 | ||
|
|
d8481e711b | ||
|
|
aad2839736 | ||
|
|
49505a4a8c | ||
|
|
ecfd8f6748 | ||
|
|
5bf1e2cf45 | ||
|
|
793525875f | ||
|
|
492aea166e | ||
|
|
83071ca7c2 | ||
|
|
6df330e62f | ||
|
|
66df00d038 | ||
|
|
4784e2de82 |
2
.github/README-de.md
vendored
2
.github/README-de.md
vendored
|
|
@ -10,7 +10,7 @@ Die folgenden Dateien werden unterstützt:
|
|||
* Einzelne Pokémon (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
|
||||
* Wunderkarten (\*.pgt, \*.pcd, \*.pgf, .wc\*), inklusive Konvertierung zu .pk\*
|
||||
* Import von GO Park Pokémon (\*.gp1) inklusive Konvertierung zu .pb7
|
||||
* Import von Teams aus entschlüsselten 3DS Battle Videos.
|
||||
* Import von Teams aus entschlüsselten 3DS Kampfvideos.
|
||||
* Transfer von einer Generation zur anderen, mit automatischer Umwandlung des Formats.
|
||||
|
||||
Alle Daten werden so angezeigt, dass sie bearbeitet und gespeichert werden können.
|
||||
|
|
|
|||
4
.github/README-es.md
vendored
4
.github/README-es.md
vendored
|
|
@ -10,7 +10,7 @@ Soporta los siguientes archivos:
|
|||
* Archivos de entidades individuales de Pokémon (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
|
||||
* Archivos de Regalos Misteriosos (\*.pgt, \*.pcd, \*.pgf, .wc\*) incluyendo conversión a .pk\*
|
||||
* Importar archivos de entidades de GO Park (\*.gp1) incluyendo conversión a .pb7
|
||||
* Importar equipos desde archivos Decrypted 3DS Battle Videos
|
||||
* Importar equipos desde archivos de Vídeos de Combate de 3DS desencriptados.
|
||||
* Pasar de una generación a la siguiente, convirtiendo los archivos en el proceso.
|
||||
|
||||
Los datos son visualizados en una forma que permite modificarlos y guardarlos.
|
||||
|
|
@ -42,7 +42,7 @@ La generación de códigos QR de PKHeX es la de [QRCoder](https://github.com/cod
|
|||
|
||||
La colección de sprites de Pokémons Shiny de PKHeX fue tomada de [pokesprite](https://github.com/msikma/pokesprite), licenciado bajo [la licencia MIT](https://github.com/msikma/pokesprite/blob/master/LICENSE).
|
||||
|
||||
PKHeX's Pokémon Legends: Arceus sprite collection is taken from the [National Pokédex - Icon Dex](https://www.deviantart.com/pikafan2000/art/National-Pokedex-Version-Delta-Icon-Dex-824897934) project and its abundance of collaborators and contributors.
|
||||
La colección de sprites de Leyendas Pokémon: Arceus de PKHeX proviene del proyecto [National Pokédex - Icon Dex](https://www.deviantart.com/pikafan2000/art/National-Pokedex-Version-Delta-Icon-Dex-824897934) y su gran cantidad de colaboradores y contribuyentes.
|
||||
|
||||
### IDE
|
||||
|
||||
|
|
|
|||
4
.github/README-ko.md
vendored
4
.github/README-ko.md
vendored
|
|
@ -10,7 +10,7 @@ PKHeX(포케헥스)
|
|||
* 개별 포켓몬 엔티티 파일 (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
|
||||
* 이상한소포 파일(\*.pgt, \*.pcd, \*.pgf, .wc\*)을 .pk로 변환하는 기능 포함
|
||||
* GO 파크 엔티티 가져오기 (\*.gp1) .pb7로 변환 포함
|
||||
* Decrypted 3DS Battle Videos에서 팀 가져오기
|
||||
* 복호화된 3DS 배틀비디오에서 팀 가져오기
|
||||
* 한 세대에서 다른 세대로 이동하면서 그 과정에서 형식이 변환됩니다.
|
||||
|
||||
데이터는 편집하고 저장할 수 있는 보기로 표시됩니다.
|
||||
|
|
@ -42,7 +42,7 @@ PKHeX의 QR 코드 생성 코드는 [the MIT license](https://github.com/codebud
|
|||
|
||||
PKHeX의 이로치(색이다른) 스프라이트 컬렉션은 [the MIT license](https://github.com/msikma/pokesprite/blob/master/LICENSE)에 따라 라이선스가 부여된 [pokesprite](https://github.com/msikma/pokesprite)에서 가져왔습니다.
|
||||
|
||||
PKheX의 Pokémon LEGENDS 아르세우스 스프라이트 컬렉션은 [National Pokédex - Icon Dex](https://www.deviantart.com/pikafan2000/art/National-Pokedex-Version-Delta-Icon-Dex-824897934) 프로젝트와 수많은 협력자 및 기여자의 도움을 받아 만들어졌습니다.
|
||||
PKHeX의 Pokémon LEGENDS 아르세우스 스프라이트 컬렉션은 [National Pokédex - Icon Dex](https://www.deviantart.com/pikafan2000/art/National-Pokedex-Version-Delta-Icon-Dex-824897934) 프로젝트와 수많은 협력자 및 기여자의 도움을 받아 만들어졌습니다.
|
||||
|
||||
### IDE(통합 개발 환경)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>26.03.20</Version>
|
||||
<Version>26.04.11</Version>
|
||||
<LangVersion>14</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -70,4 +72,9 @@ public interface IBattleTemplate : ISpeciesForm, IGigantamaxReadOnly, IDynamaxLe
|
|||
/// <see cref="PKM.Moves"/> of the Set entity.
|
||||
/// </summary>
|
||||
ushort[] Moves { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the properties are probably from a Pokémon Champions set.
|
||||
/// </summary>
|
||||
bool IsChampions => Level == 50 && !IVs.ContainsAnyExcept(31) && EffortValues.IsChampions(EVs);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,6 +195,11 @@ public void ApplySetDetails(IBattleTemplate set)
|
|||
}
|
||||
else
|
||||
{
|
||||
// Champions revises EV behavior to be /8.
|
||||
// If the user is requesting a Champions-like set import, apply EVs that way.
|
||||
if (set.IsChampions)
|
||||
pk.SetEVsChampions(evs);
|
||||
else
|
||||
pk.SetEVs(evs);
|
||||
}
|
||||
|
||||
|
|
@ -266,6 +271,13 @@ public void ApplySetDetails(IBattleTemplate set)
|
|||
pk.RefreshChecksum();
|
||||
}
|
||||
|
||||
private void SetEVsChampions(ReadOnlySpan<int> evs)
|
||||
{
|
||||
Span<int> final = stackalloc int[6];
|
||||
EffortValues.ConvertFromChampions(evs, final);
|
||||
pk.SetEVs(final);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.HeldItem"/> value depending on the current format and the provided item index & format.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -100,7 +100,13 @@ public class EntitySummary : IFatefulEncounterReadOnly // do NOT seal, allow inh
|
|||
public string Relearn2 => Get(Strings.movelist, Entity.RelearnMove2);
|
||||
public string Relearn3 => Get(Strings.movelist, Entity.RelearnMove3);
|
||||
public string Relearn4 => Get(Strings.movelist, Entity.RelearnMove4);
|
||||
public ushort Checksum => Entity is ISanityChecksum s ? s.Checksum : Checksums.CRC16_CCITT(Entity.Data[Entity.SIZE_STORED..]);
|
||||
public ushort Checksum => Entity switch
|
||||
{
|
||||
ISanityChecksum s => s.Checksum,
|
||||
PK1 gb => gb.GetSingleListChecksum(),
|
||||
PK2 gb => gb.GetSingleListChecksum(),
|
||||
_ => Checksums.CRC16_CCITT(Entity.Data[..Entity.SIZE_STORED]),
|
||||
};
|
||||
public int Friendship => Entity.OriginalTrainerFriendship;
|
||||
public int EggYear => Entity.EggMetDate.GetValueOrDefault().Year;
|
||||
public int EggMonth => Entity.EggMetDate.GetValueOrDefault().Month;
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ public static string GetMessage(PKM pk)
|
|||
return GetMessage(pk7);
|
||||
|
||||
var server = GetExploitURLPrefixPKM(pk.Format);
|
||||
var data = pk.EncryptedBoxData;
|
||||
Span<byte> data = stackalloc byte[pk.SIZE_STORED];
|
||||
pk.WriteEncryptedDataStored(data);
|
||||
return GetMessageBase64(data, server);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ public sealed class AdvancedSettings
|
|||
[LocalizedDescription("Hide event variable names for that contain any of the comma-separated substrings below. Removes event values from the GUI that the user doesn't care to view.")]
|
||||
public string HideEvent8Contains { get; set; } = string.Empty;
|
||||
|
||||
[LocalizedDescription("Minimum distance threshold that mouse movement must exceed before a drag operation is started from a slot.")]
|
||||
public int DragStartThreshold { get; set; } = 0;
|
||||
|
||||
[Browsable(false)]
|
||||
public string[] GetExclusionList8() => Array.ConvertAll(HideEvent8Contains.Split(',', StringSplitOptions.RemoveEmptyEntries), z => z.Trim());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ public sealed class SoundSettings
|
|||
{
|
||||
[LocalizedDescription("Play Sound when loading a new Save File")]
|
||||
public bool PlaySoundSAVLoad { get; set; } = true;
|
||||
|
||||
[LocalizedDescription("Play Sound when popping up Legality Report")]
|
||||
public bool PlaySoundLegalityCheck { get; set; } = true;
|
||||
|
||||
[LocalizedDescription("Play Sound when performing any other action that would be reasonable to sound alert.")]
|
||||
public bool PlaySoundOther { get; set; } = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,10 +47,10 @@ public sealed class FakeSaveFile : SaveFile
|
|||
protected override void SetChecksums() { }
|
||||
public override GameVersion Version { get => GameVersion.R; set { } }
|
||||
public override Type PKMType => typeof(PK3);
|
||||
protected override PK3 GetPKM(byte[] data) => BlankPKM;
|
||||
protected override byte[] DecryptPKM(byte[] data) => data;
|
||||
protected override PK3 GetPKM(Memory<byte> data) => BlankPKM;
|
||||
protected override void DecryptPKM(Span<byte> data) { }
|
||||
public override PK3 BlankPKM => new();
|
||||
public override EntityContext Context => EntityContext.Gen3;
|
||||
protected override int SIZE_STORED => 0;
|
||||
protected override int SIZE_PARTY => 0;
|
||||
public override int SIZE_STORED => 0;
|
||||
public override int SIZE_PARTY => 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,9 @@ public static int Export(SaveFile sav, string destPath, IFileNamer<PKM> namer, B
|
|||
|
||||
int count = GetSlotCountForBox(boxSlotCount, box, total);
|
||||
int ctr = 0;
|
||||
// Export each slot in the box.
|
||||
// Export each slot in the box with party stats, to be nice to any external analysis.
|
||||
bool isPartyFormat = sav.SIZE_BOXSLOT == sav.SIZE_PARTY;
|
||||
Span<byte> data = stackalloc byte[sav.SIZE_PARTY];
|
||||
for (int slot = 0; slot < count; slot++)
|
||||
{
|
||||
var pk = sav.GetBoxSlotAtIndex(box, slot);
|
||||
|
|
@ -98,7 +100,13 @@ public static int Export(SaveFile sav, string destPath, IFileNamer<PKM> namer, B
|
|||
|
||||
var fileName = GetFileName(pk, settings.FileIndexPrefix, namer, box, slot, boxSlotCount);
|
||||
var fn = Path.Combine(destPath, fileName);
|
||||
File.WriteAllBytes(fn, pk.DecryptedPartyData);
|
||||
|
||||
// Assume that all PKM read for the loop all are the same shape; the if-else will always travel one path.
|
||||
// We don't have to worry about lingering party data from a previous loop iteration.
|
||||
if (!isPartyFormat)
|
||||
pk.ForcePartyData(); // Rather than export all-zero party stats, calculate what they would be.
|
||||
pk.WriteDecryptedDataParty(data);
|
||||
File.WriteAllBytes(fn, data);
|
||||
ctr++;
|
||||
}
|
||||
return ctr;
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ private static List<SlotInfoMisc> GetExtraSlots9a(SAV9ZA sav)
|
|||
{
|
||||
const int size = 0x1F0;
|
||||
var ofs = (i * size) + 8;
|
||||
var entry = shinyCache.Raw.Slice(ofs, PokeCrypto.SIZE_9PARTY);
|
||||
var entry = shinyCache.Raw.Slice(ofs, PokeCrypto.SIZE_8PARTY);
|
||||
if (EntityDetection.IsPresent(entry.Span))
|
||||
list.Add(new(entry, i, true) { Type = StorageSlotType.Shiny, HideLegality = true }); // no OT info
|
||||
else
|
||||
|
|
@ -266,7 +266,7 @@ private static List<SlotInfoMisc> GetExtraSlots9a(SAV9ZA sav)
|
|||
{
|
||||
const int size = 0x1A8;
|
||||
var ofs = (i * size) + 8;
|
||||
var entry = giveAway.Raw.Slice(ofs, PokeCrypto.SIZE_9PARTY);
|
||||
var entry = giveAway.Raw.Slice(ofs, PokeCrypto.SIZE_8PARTY);
|
||||
if (EntityDetection.IsPresent(entry.Span))
|
||||
list.Add(new(entry, i, true, Mutable: true) { Type = StorageSlotType.Scripted });
|
||||
else
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ public GameDataSource(GameStrings s)
|
|||
/// <remarks>Most recent games are at the top, loosely following Generation groups.</remarks>
|
||||
private static ReadOnlySpan<byte> OrderedVersionArray =>
|
||||
[
|
||||
53, // Champions
|
||||
52, // 9 Z-A
|
||||
50, 51, // 9 S/V
|
||||
47, // 8 PLA
|
||||
|
|
|
|||
|
|
@ -448,6 +448,10 @@ private void SanitizeMetLocations()
|
|||
Gen6.Met4[35] += " (-)";
|
||||
Gen7.Met4[38] += " (-)";
|
||||
Gen7b.Met4[27] += " (-)";
|
||||
|
||||
// only a duplicate in LATAM Spanish
|
||||
if (Language is SpanishL)
|
||||
Gen5.Met4[47] += " (-)";
|
||||
}
|
||||
|
||||
if (Language is Korean)
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ private EntityContext GetContextInternal()
|
|||
BD or SP => EntityContext.Gen8b,
|
||||
SW or SH => EntityContext.Gen8,
|
||||
SL or VL => EntityContext.Gen9,
|
||||
ZA => EntityContext.Gen9a,
|
||||
ZA or CP => EntityContext.Gen9a,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
|
|
@ -248,7 +248,7 @@ public bool Contains(GameVersion g2)
|
|||
Gen8 => version1 is SW or SH or BD or SP or SWSH or BDSP or PLA,
|
||||
|
||||
SV => version1 is SL or VL,
|
||||
Gen9 => version1 is SL or VL or SV or ZA,
|
||||
Gen9 => version1 is SL or VL or SV or ZA or CP,
|
||||
|
||||
_ => false,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public sealed class ItemStorage3E : IItemStorage
|
|||
// R/S
|
||||
259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 270, 271, 272, 273, 274, 275, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288,
|
||||
// FR/LG
|
||||
372,
|
||||
370, 371, 372,
|
||||
// E
|
||||
375, 376,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -168,7 +168,9 @@ public static InventoryType GetInventoryPouch(ushort itemIndex)
|
|||
}
|
||||
|
||||
public static bool IsMegaStone(ushort item) => MegaStones.Contains(item);
|
||||
public static bool IsUniqueHeldItem(ushort item) => IsMegaStone(item) || item is (0534 or 0535); // Primal Orbs
|
||||
public static bool IsUniqueHeldItem(ushort item) => IsMegaStone(item) || IsOrb(item);
|
||||
public static bool IsOrb(ushort item) => item is (0534 or 0535); // Primal Orbs
|
||||
|
||||
public static ushort[] GetAllUniqueHeldItems() => [..MegaStones, 0534, 0535];
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,20 @@ private static void AnalyzeInventory(BulkAnalysis input, SAV9ZA za)
|
|||
continue;
|
||||
|
||||
var item = items.GetItem(stone);
|
||||
if (ItemStorage9ZA.IsOrb(stone))
|
||||
{
|
||||
// Handled via Other items (give, unique), not Mega Stones (loan).
|
||||
if (item.IsNew) // Not acquired/given by the save file, thus not able to be held.
|
||||
input.AddLine(slot, Identifier, BulkCheckResult.NoIndex, BulkHeldItemInventoryNotAcquired_0, stone);
|
||||
else if (seenStones.TryGetValue(stone, out var otherIndex)) // Already given to another slot.
|
||||
input.AddLine(slot, input.AllData[otherIndex], Identifier, i, index2: otherIndex, BulkHeldItemInventoryMultipleSlots_0, stone);
|
||||
else // First time seeing this item, all good.
|
||||
seenStones[stone] = i;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Mega Stone
|
||||
if (item.Count == 0) // Not acquired by the save file, thus not able to be held.
|
||||
input.AddLine(slot, Identifier, BulkCheckResult.NoIndex, BulkHeldItemInventoryNotAcquired_0, stone);
|
||||
else if (!item.IsHeld) // Not marked as held, so it's still "in the bag" (not given).
|
||||
|
|
|
|||
|
|
@ -95,15 +95,26 @@ internal static class Encounters2
|
|||
new(202, 15, C) { Location = 016 }, // Wobbuffet @ Goldenrod City (Game Corner)
|
||||
];
|
||||
|
||||
private static IndividualValueSet AllZero => new(00, 00, 00, 00, 00, 00);
|
||||
private static IndividualValueSet Shiny2 => new(00, 02, 10, 10, 10, 10);
|
||||
|
||||
public static readonly EncounterStatic2[] StaticOddEggC =
|
||||
[
|
||||
new(172, 05, C) { IsEgg = true, Moves = new((int)Move.ThunderShock,(int)Move.Charm, (int)Move.DizzyPunch)}, // Pichu
|
||||
new(173, 05, C) { IsEgg = true, Moves = new((int)Move.Pound, (int)Move.Charm, (int)Move.DizzyPunch)}, // Cleffa
|
||||
new(174, 05, C) { IsEgg = true, Moves = new((int)Move.Sing, (int)Move.Charm, (int)Move.DizzyPunch)}, // Igglybuff
|
||||
new(236, 05, C) { IsEgg = true, Moves = new((int)Move.Tackle, (int)Move.DizzyPunch)}, // Tyrogue
|
||||
new(238, 05, C) { IsEgg = true, Moves = new((int)Move.Pound, (int)Move.Lick, (int)Move.DizzyPunch)}, // Smoochum
|
||||
new(239, 05, C) { IsEgg = true, Moves = new((int)Move.QuickAttack, (int)Move.Leer, (int)Move.DizzyPunch)}, // Elekid
|
||||
new(240, 05, C) { IsEgg = true, Moves = new((int)Move.Ember, (int)Move.DizzyPunch)}, // Magby
|
||||
new(172, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.ThunderShock,(int)Move.Charm, (int)Move.DizzyPunch)}, // Pichu
|
||||
new(173, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.Pound, (int)Move.Charm, (int)Move.DizzyPunch)}, // Cleffa
|
||||
new(174, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.Sing, (int)Move.Charm, (int)Move.DizzyPunch)}, // Igglybuff
|
||||
new(236, 05, C) { IsEgg = true, Gender = 0, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.Tackle, (int)Move.DizzyPunch)}, // Tyrogue
|
||||
new(238, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.Pound, (int)Move.Lick, (int)Move.DizzyPunch)}, // Smoochum
|
||||
new(239, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.QuickAttack, (int)Move.Leer, (int)Move.DizzyPunch)}, // Elekid
|
||||
new(240, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.Ember, (int)Move.DizzyPunch)}, // Magby
|
||||
|
||||
new(172, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.ThunderShock,(int)Move.Charm, (int)Move.DizzyPunch)}, // Shiny Pichu
|
||||
new(173, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.Pound, (int)Move.Charm, (int)Move.DizzyPunch)}, // Shiny Cleffa
|
||||
new(174, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.Sing, (int)Move.Charm, (int)Move.DizzyPunch)}, // Shiny Igglybuff
|
||||
new(236, 05, C) { IsEgg = true, Gender = 0, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.Tackle, (int)Move.DizzyPunch)}, // Shiny Tyrogue
|
||||
new(238, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.Pound, (int)Move.Lick, (int)Move.DizzyPunch)}, // Shiny Smoochum
|
||||
new(239, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.QuickAttack, (int)Move.Leer, (int)Move.DizzyPunch)}, // Shiny Elekid
|
||||
new(240, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.Ember, (int)Move.DizzyPunch)}, // Shiny Magby
|
||||
];
|
||||
|
||||
internal static readonly EncounterStatic2 CelebiVC = new(251, 30, C) { Location = 014 }; // Celebi @ Ilex Forest (VC)
|
||||
|
|
@ -121,7 +132,7 @@ internal static class Encounters2
|
|||
new(TradeNames, 7, 178, 15, 15616) { Gender = 0, IVs = new(08, 09, 06, 08, 06, 06) }, // Xatu @ Pewter City for Haunter [wild]
|
||||
new(TradeNames, 8, 082, 05, 50082) { Gender = 2, IVs = new(08, 09, 06, 06, 06, 06) }, // Magneton @ Power Plant for Dugtrio [traded for Lickitung]
|
||||
|
||||
new(TradeNames, 9, 021, 10, 01001), // Spearow @ Goldenrod City for free
|
||||
new(TradeNames, 10, 213, 15, 00518), // Shuckle @ Cianwood City for free
|
||||
new(TradeNames, 9, 021, 10, 01001) { Shiny = Shiny.Random }, // Spearow @ Goldenrod City for free
|
||||
new(TradeNames, 10, 213, 15, 00518) { Shiny = Shiny.Random }, // Shuckle @ Cianwood City for free
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ private static EncounterArea3[] GetSwarm([ConstantExpected] string resource, [Le
|
|||
// Stationary
|
||||
new(352, 30, RSE) { Location = 034 }, // Kecleon @ Route 119
|
||||
new(352, 30, RSE) { Location = 035 }, // Kecleon @ Route 120
|
||||
new(100, 25, RSE) { Location = 062 }, // Voltorb @ New Mauville
|
||||
|
||||
// Stationary Lengendary
|
||||
new(377, 40, RSE) { Location = 082 }, // Regirock @ Desert Ruins
|
||||
|
|
|
|||
|
|
@ -255,5 +255,9 @@ public static class EncounterServerDate
|
|||
{0102, new(2025, 10, 23, 2026, 02, 01, +2)}, // Slowpoke PokéCenter Gift
|
||||
{0101, new(2025, 10, 31, 2027, 02, 01)}, // PokéCenter Audino Birthday Gift
|
||||
{1607, new(2025, 12, 09, 2026, 01, 20)}, // Alpha Charizard
|
||||
|
||||
{9031, new(2026, 04, 02)}, // Alpha Chikorita
|
||||
{9032, new(2026, 04, 02)}, // Alpha Tepig
|
||||
{9033, new(2026, 04, 02)}, // Alpha Totodile
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@ public static class EncounterGenerator
|
|||
9 => version switch
|
||||
{
|
||||
GameVersion.ZA => EncounterGenerator9a.Instance,
|
||||
_ => EncounterGenerator9.Instance,
|
||||
GameVersion.SL or GameVersion.VL => EncounterGenerator9.Instance,
|
||||
_ => EncounterGeneratorDummy.Instance, // Champions
|
||||
},
|
||||
_ => EncounterGeneratorDummy.Instance,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -48,27 +48,25 @@ public EncounterTrade1(ReadOnlySpan<string[]> names, byte index, ushort species,
|
|||
LevelMinGSC = levelMinGSC;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When transferred to Gen7+ via Bank, the nickname for a Japanese Dugtrio in Hiragana changes from "ぐリお" to "ぐりお".
|
||||
/// </summary>
|
||||
public const string HiraganaDugtrio7 = "ぐりお";
|
||||
|
||||
private bool IsNicknameValid(PKM pk, ReadOnlySpan<char> nick)
|
||||
{
|
||||
if (pk.Format <= 2)
|
||||
return IsNicknameAnyMatch(nick);
|
||||
|
||||
// Converted string 1/2->7 to language specific value
|
||||
// Nicknames can be from any of the languages it can trade between.
|
||||
int lang = pk.Language;
|
||||
if (lang == 1)
|
||||
{
|
||||
// Special consideration for Hiragana strings that are transferred
|
||||
if (Version == GameVersion.YW && Species == (int)Core.Species.Dugtrio)
|
||||
return nick is "ぐりお";
|
||||
return nick.SequenceEqual(Nicknames.Span[(int)LanguageID.Japanese]);
|
||||
}
|
||||
if (!pk.Japanese)
|
||||
return DetectLanguage(nick, Nicknames.Span, 2) >= 2;
|
||||
|
||||
return GetNicknameIndex(nick) >= 2;
|
||||
// Converted Japanese strings 1/2->7 can mutate from an exact match.
|
||||
// Special consideration for Hiragana strings that are transferred: only Dugtrio's nickname changes when transferred to Gen7+.
|
||||
if (pk.Format > 2 && Version == GameVersion.YW && Species == (int)Core.Species.Dugtrio)
|
||||
return nick is HiraganaDugtrio7;
|
||||
// Otherwise, must match the Japanese nickname exactly.
|
||||
return Nicknames.Span[(int)LanguageID.Japanese].SequenceEqual(nick);
|
||||
}
|
||||
|
||||
private bool IsNicknameAnyMatch(ReadOnlySpan<char> current) => GetNicknameIndex(current) >= 0;
|
||||
|
||||
private static bool IsTrainerNameValid(PKM pk)
|
||||
{
|
||||
if (pk.Format <= 2)
|
||||
|
|
@ -83,12 +81,12 @@ private static bool IsTrainerNameValid(PKM pk)
|
|||
return trainer.SequenceEqual(expect);
|
||||
}
|
||||
|
||||
private int GetNicknameIndex(ReadOnlySpan<char> nickname) => GetIndex(nickname, Nicknames.Span);
|
||||
|
||||
private static int GetIndex(ReadOnlySpan<char> name, ReadOnlySpan<string> arr)
|
||||
private static int DetectLanguage(ReadOnlySpan<char> name, ReadOnlySpan<string> arr, int start = 1)
|
||||
{
|
||||
for (int i = 0; i < arr.Length; i++)
|
||||
for (int i = start; i < arr.Length; i++)
|
||||
{
|
||||
if (i == (int)LanguageID.UNUSED_6)
|
||||
continue;
|
||||
if (name.SequenceEqual(arr[i]))
|
||||
return i;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ public sealed record EncounterTrade2 : IEncounterable, IEncounterMatch, IEncount
|
|||
public bool IsEgg => false;
|
||||
public Ball FixedBall => Ball.Poke;
|
||||
public AbilityPermission Ability => AbilityPermission.OnlyHidden;
|
||||
public Shiny Shiny => Shiny.Random;
|
||||
public bool IsShiny => false;
|
||||
public ushort EggLocation => 0;
|
||||
public bool IsFixedTrainer => true;
|
||||
|
|
@ -33,6 +32,7 @@ public sealed record EncounterTrade2 : IEncounterable, IEncounterMatch, IEncount
|
|||
private readonly ReadOnlyMemory<string> TrainerNames;
|
||||
private readonly ReadOnlyMemory<string> Nicknames;
|
||||
|
||||
public Shiny Shiny { get; init; } = Shiny.Never;
|
||||
public byte Gender { get; init; }
|
||||
public byte OTGender { get; init; }
|
||||
public IndividualValueSet IVs { get; init; }
|
||||
|
|
@ -168,8 +168,8 @@ private int DetectLanguage(PKM pk, ReadOnlySpan<char> trainer, ReadOnlySpan<char
|
|||
return -1;
|
||||
return (int)LanguageID.Korean;
|
||||
}
|
||||
|
||||
for (int i = 2; i < TrainerNames.Length; i++)
|
||||
// Skip languages that are not-transferable to International games.
|
||||
for (int i = 2; i < (int)LanguageID.Korean; i++)
|
||||
{
|
||||
if (i == (int)LanguageID.UNUSED_6)
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -79,14 +79,14 @@ public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
|||
// Get a random PID that matches gender/nature/ability criteria
|
||||
var pi = PersonalTable.E[Species];
|
||||
var gr = pi.Gender;
|
||||
var pid = GetRandomPID(criteria, gr);
|
||||
var pid = GetRandomPID(criteria, gr, tr.ID32);
|
||||
pk.PID = pid;
|
||||
pk.RefreshAbility((int)(pid % 2));
|
||||
|
||||
return pk;
|
||||
}
|
||||
|
||||
private uint GetRandomPID(in EncounterCriteria criteria, byte gr)
|
||||
private uint GetRandomPID(in EncounterCriteria criteria, byte gr, uint id32)
|
||||
{
|
||||
var seed = Util.Rand32();
|
||||
while (true)
|
||||
|
|
@ -109,6 +109,10 @@ private uint GetRandomPID(in EncounterCriteria criteria, byte gr)
|
|||
if (!Daycare3.IsValidProcPID(pid, Version))
|
||||
continue; // 0-value PID is invalid
|
||||
|
||||
var shiny = ShinyUtil.GetIsShiny3(id32, pid);
|
||||
if (criteria.Shiny.IsShiny() != shiny)
|
||||
continue;
|
||||
|
||||
return pid;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
|||
// Get a random PID that matches gender/nature/ability criteria
|
||||
var pi = PersonalTable.HGSS[Species];
|
||||
var gr = pi.Gender;
|
||||
var pid = GetRandomPID(criteria, gr, out var gender);
|
||||
var pid = GetRandomPID(criteria, gr, tr.ID32, out var gender);
|
||||
pk.PID = pid;
|
||||
pk.Gender = gender;
|
||||
pk.RefreshAbility((int)(pid & 1));
|
||||
|
|
@ -92,7 +92,7 @@ public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
|||
return pk;
|
||||
}
|
||||
|
||||
private uint GetRandomPID(in EncounterCriteria criteria, byte gr, out byte gender)
|
||||
private uint GetRandomPID(in EncounterCriteria criteria, byte gr, uint id32, out byte gender)
|
||||
{
|
||||
var seed = Util.Rand32();
|
||||
while (true)
|
||||
|
|
@ -115,6 +115,10 @@ private uint GetRandomPID(in EncounterCriteria criteria, byte gr, out byte gende
|
|||
// PID is rolled forward upon picking up the egg.
|
||||
// Not worth skipping 0-value PIDs. Too rare to be worth trying again, since it can be a valid PID.
|
||||
|
||||
var shiny = ShinyUtil.GetIsShiny3(id32, pid);
|
||||
if (criteria.Shiny.IsShiny() != shiny)
|
||||
continue;
|
||||
|
||||
return pid;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
|||
var pi = PersonalTable.B2W2[Species];
|
||||
var gr = pi.Gender;
|
||||
var ability = criteria.GetAbilityFromNumber(Ability);
|
||||
var pid = GetRandomPID(criteria, gr, out var gender);
|
||||
var pid = GetRandomPID(criteria, gr, tr.ID32, out var gender);
|
||||
pid = (pid & 0xFFFEFFFFu) | (uint)(ability & 1) << 16; // 0x00000000 or 0x00010000
|
||||
pk.PID = pid;
|
||||
pk.Gender = gender;
|
||||
|
|
@ -81,7 +81,7 @@ public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
|||
return pk;
|
||||
}
|
||||
|
||||
private static uint GetRandomPID(in EncounterCriteria criteria, byte gr, out byte gender)
|
||||
private static uint GetRandomPID(in EncounterCriteria criteria, byte gr, uint id32, out byte gender)
|
||||
{
|
||||
var seed = Util.Rand32();
|
||||
while (true)
|
||||
|
|
@ -91,6 +91,9 @@ private static uint GetRandomPID(in EncounterCriteria criteria, byte gr, out byt
|
|||
gender = EntityGender.GetFromPIDAndRatio(pid, gr);
|
||||
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(gender))
|
||||
continue;
|
||||
var shiny = ShinyUtil.GetIsShiny3(id32, pid);
|
||||
if (criteria.Shiny.IsShiny() != shiny)
|
||||
continue;
|
||||
return pid;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ public EncounterTrade5B2W2(ReadOnlySpan<string[]> names, byte index, GameVersion
|
|||
}
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public EncounterTrade5B2W2(string[] names, GameVersion version)
|
||||
public EncounterTrade5B2W2(ReadOnlyMemory<string> names, GameVersion version)
|
||||
{
|
||||
Version = version;
|
||||
Gender = FixedGenderUtil.GenderRandom;
|
||||
|
|
|
|||
|
|
@ -104,6 +104,9 @@ public PK8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
|||
pk.SetMoves(Moves);
|
||||
else
|
||||
EncounterUtil.SetEncounterMoves(pk, version, Level);
|
||||
if (Relearn.HasMoves)
|
||||
pk.SetRelearnMoves(Relearn);
|
||||
|
||||
pk.ResetPartyStats();
|
||||
|
||||
return pk;
|
||||
|
|
|
|||
|
|
@ -199,7 +199,9 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
|
|||
if (pk is not IScaledSize s)
|
||||
return true;
|
||||
|
||||
// 3 of the Alpha statics were mistakenly set as 127 scale. If they enter HOME on 3.0.1, they'll get bumped to 255.
|
||||
// 3 of the Alpha statics were mistakenly set as 127 height/weight/scale.
|
||||
// Depositing them from any game into HOME 3.0.1 or later bumps all 3 values to 255.
|
||||
// The handling is server-sided and checks specifically for origin game, species, form, and Alpha Mark.
|
||||
// Defer scale match to downstream checks; we are sufficiently confident this is the best-match.
|
||||
if (IsAlpha127) // Average Size Alphas
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,10 +15,9 @@ public sealed class EvolutionGroupHOME : IEvolutionGroup
|
|||
|
||||
public IEvolutionGroup? GetNext(PKM pk, EvolutionOrigin enc)
|
||||
{
|
||||
return null; // TODO HOME ZA2: Re-enable when we have more info.
|
||||
// if (pk.Format <= 9 && pk.Context is not EntityContext.Gen9a)
|
||||
// return null;
|
||||
// return EvolutionGroupHOME.Instance;
|
||||
if (pk is { Format: <= 9, Context: not EntityContext.Gen9a })
|
||||
return null;
|
||||
return EvolutionGroupHOME2.Instance;
|
||||
}
|
||||
|
||||
public IEvolutionGroup? GetPrevious(PKM pk, EvolutionOrigin enc)
|
||||
|
|
@ -50,7 +49,16 @@ public void DiscardForOrigin(Span<EvoCriteria> result, PKM pk, EvolutionOrigin e
|
|||
/// </summary>
|
||||
/// <returns>True if we should check all adjacent evolution sources.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool CheckAllAdjacent(PKM pk, EvolutionOrigin enc) => enc.SkipChecks || pk is IHomeTrack { HasTracker: true } || !ParseSettings.IgnoreTransferIfNoTracker;
|
||||
private static bool CheckAllAdjacent(PKM pk, EvolutionOrigin enc)
|
||||
{
|
||||
if (enc.SkipChecks)
|
||||
return true;
|
||||
if (IsOutsideContext(pk.Context))
|
||||
return true; // transferred through HOME already
|
||||
return pk is IHomeTrack { HasTracker: true } || !ParseSettings.IgnoreTransferIfNoTracker;
|
||||
}
|
||||
|
||||
private static bool IsOutsideContext(EntityContext context) => context is not (EntityContext.Gen8 or EntityContext.Gen8a or EntityContext.Gen8b or EntityContext.Gen9);
|
||||
|
||||
public int Devolve(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,15 +15,14 @@ public sealed class EvolutionGroupHOME2 : IEvolutionGroup
|
|||
|
||||
public IEvolutionGroup? GetPrevious(PKM pk, EvolutionOrigin enc)
|
||||
{
|
||||
return null; // TODO HOME ZA2: Re-enable when we have more info.
|
||||
// if (enc.Generation > 9 || enc.Context is EntityContext.Gen9a)
|
||||
// return null;
|
||||
// return EvolutionGroupHOME.Instance;
|
||||
if (enc is { Generation: > 9} or { Context: EntityContext.Gen9a })
|
||||
return null;
|
||||
return EvolutionGroupHOME.Instance;
|
||||
}
|
||||
|
||||
public void DiscardForOrigin(Span<EvoCriteria> result, PKM pk, EvolutionOrigin enc)
|
||||
{
|
||||
if (pk.ZA) // TODO HOME ZA2: did they force realign everything and fix their bug?
|
||||
if (pk.ZA)
|
||||
{
|
||||
var table = PersonalTable.ZA;
|
||||
if (enc.Options.HasFlag(OriginOptions.SkipChecks))
|
||||
|
|
@ -33,8 +32,10 @@ public void DiscardForOrigin(Span<EvoCriteria> result, PKM pk, EvolutionOrigin e
|
|||
}
|
||||
|
||||
// Check if ability was possibly realigned by form change; if not, discard anything that doesn't have the ability.
|
||||
// If touched by HOME, HOME realigns tracker, so we can't discard in that case.
|
||||
|
||||
var index = pk.AbilityNumber >> 1;
|
||||
if (index is 0 or 1)
|
||||
if (index is 0 or 1 && pk is not IHomeTrack { HasTracker: true })
|
||||
{
|
||||
var didRealignAbility = false;
|
||||
var ability = pk.Ability;
|
||||
|
|
|
|||
|
|
@ -138,4 +138,22 @@ private static int GetSpeciesIndex(ReadOnlySpan<EvoCriteria> array, ushort speci
|
|||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public bool HasVisitedExcept(params ReadOnlySpan<EntityContext> context)
|
||||
{
|
||||
if (HasVisitedZA && !context.Contains(EntityContext.Gen9a)) return true;
|
||||
if (HasVisitedGen9 && !context.Contains(EntityContext.Gen9)) return true;
|
||||
if (HasVisitedBDSP && !context.Contains(EntityContext.Gen8a)) return true;
|
||||
if (HasVisitedPLA && !context.Contains(EntityContext.Gen8b)) return true;
|
||||
if (HasVisitedSWSH && !context.Contains(EntityContext.Gen8)) return true;
|
||||
if (HasVisitedLGPE && !context.Contains(EntityContext.Gen7b)) return true;
|
||||
if (HasVisitedGen7 && !context.Contains(EntityContext.Gen7)) return true;
|
||||
if (HasVisitedGen6 && !context.Contains(EntityContext.Gen6)) return true;
|
||||
if (HasVisitedGen5 && !context.Contains(EntityContext.Gen5)) return true;
|
||||
if (HasVisitedGen4 && !context.Contains(EntityContext.Gen4)) return true;
|
||||
if (HasVisitedGen3 && !context.Contains(EntityContext.Gen3)) return true;
|
||||
if (HasVisitedGen2 && !context.Contains(EntityContext.Gen2)) return true;
|
||||
if (HasVisitedGen1 && !context.Contains(EntityContext.Gen1)) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ public static class Legal
|
|||
internal const int MaxAbilityID_9a_MD = MaxAbilityID_9a_IK;
|
||||
|
||||
internal const int MaxBallID_9 = (int)Ball.LAOrigin;
|
||||
internal const GameVersion MaxGameID_HOME = GameVersion.VL; // TODO HOME ZA - Replace with ZA when HOME; if backwards transfer is allowed. If prevented, rename epoch as HOME1.
|
||||
internal const GameVersion MaxGameID_HOME = GameVersion.VL;
|
||||
internal const GameVersion MaxGameID_HOME2 = GameVersion.ZA;
|
||||
|
||||
internal static readonly ushort[] HeldItems_GSC = ItemStorage2.GetAllHeld();
|
||||
|
|
|
|||
|
|
@ -18,451 +18,446 @@ public sealed class LegalityCheckLocalization
|
|||
#region General Strings
|
||||
|
||||
/// <summary>Default text for indicating validity.</summary>
|
||||
public string Valid { get; set; } = "Valid.";
|
||||
public string Valid { get; init; } = "Valid.";
|
||||
|
||||
/// <summary>Default text for indicating legality.</summary>
|
||||
public string Legal { get; set; } = "Legal!";
|
||||
public string Legal { get; init; } = "Legal!";
|
||||
|
||||
/// <summary>Default text for indicating an error has occurred.</summary>
|
||||
public string Error { get; set; } = "Internal error.";
|
||||
public string Error { get; init; } = "Internal error.";
|
||||
|
||||
/// <summary>Analysis not available for the <see cref="PKM"/></summary>
|
||||
public string AnalysisUnavailable { get; set; } = "Analysis not available for this Pokémon.";
|
||||
public string AnalysisUnavailable { get; init; } = "Analysis not available for this Pokémon.";
|
||||
|
||||
/// <summary>Format text for exporting a legality check result.</summary>
|
||||
public string F0_1 { get; set; } = "{0}: {1}";
|
||||
public string F0_1 { get; init; } = "{0}: {1}";
|
||||
|
||||
/// <summary>Severity string for <see cref="Severity.Invalid"/></summary>
|
||||
public string SInvalid { get; set; } = "Invalid";
|
||||
public string SInvalid { get; init; } = "Invalid";
|
||||
|
||||
/// <summary>Severity string for <see cref="Severity.Fishy"/></summary>
|
||||
public string SFishy { get; set; } = "Fishy";
|
||||
public string SFishy { get; init; } = "Fishy";
|
||||
|
||||
/// <summary>Severity string for <see cref="Severity.Valid"/></summary>
|
||||
public string SValid { get; set; } = "Valid";
|
||||
public string SValid { get; init; } = "Valid";
|
||||
|
||||
/// <summary>Severity string for anything not implemented.</summary>
|
||||
public string NotImplemented { get; set; } = "Not Implemented";
|
||||
public string NotImplemented { get; init; } = "Not Implemented";
|
||||
|
||||
public string AbilityCapsuleUsed { get; set; } = "Ability available with Ability Capsule.";
|
||||
public string AbilityPatchUsed { get; set; } = "Ability available with Ability Patch.";
|
||||
public string AbilityPatchRevertUsed { get; set; } = "Ability available with Ability Patch Revert.";
|
||||
public string AbilityFlag { get; set; } = "Ability matches ability number.";
|
||||
public string AbilityHiddenFail { get; set; } = "Hidden Ability mismatch for encounter type.";
|
||||
public string AbilityHiddenUnavailable { get; set; } = "Hidden Ability not available.";
|
||||
public string AbilityMismatch { get; set; } = "Ability mismatch for encounter.";
|
||||
public string AbilityMismatch3 { get; set; } = "Ability does not match Generation 3 species ability.";
|
||||
public string AbilityMismatchFlag { get; set; } = "Ability does not match ability number.";
|
||||
public string AbilityMismatchGift { get; set; } = "Ability does not match Mystery Gift.";
|
||||
public string AbilityMismatchPID { get; set; } = "Ability does not match PID.";
|
||||
public string AbilityUnexpected { get; set; } = "Ability is not valid for species/form.";
|
||||
public string AbilityCapsuleUsed { get; init; } = "Ability available with Ability Capsule.";
|
||||
public string AbilityPatchUsed { get; init; } = "Ability available with Ability Patch.";
|
||||
public string AbilityPatchRevertUsed { get; init; } = "Ability available with Ability Patch Revert.";
|
||||
public string AbilityFlag { get; init; } = "Ability matches ability number.";
|
||||
public string AbilityHiddenFail { get; init; } = "Hidden Ability mismatch for encounter type.";
|
||||
public string AbilityHiddenUnavailable { get; init; } = "Hidden Ability not available.";
|
||||
public string AbilityMismatch { get; init; } = "Ability mismatch for encounter.";
|
||||
public string AbilityMismatch3 { get; init; } = "Ability does not match Generation 3 species ability.";
|
||||
public string AbilityMismatchFlag { get; init; } = "Ability does not match ability number.";
|
||||
public string AbilityMismatchGift { get; init; } = "Ability does not match Mystery Gift.";
|
||||
public string AbilityMismatchPID { get; init; } = "Ability does not match PID.";
|
||||
public string AbilityUnexpected { get; init; } = "Ability is not valid for species/form.";
|
||||
|
||||
public string AwakenedCap { get; set; } = "Individual AV cannot be greater than {0}.";
|
||||
public string AwakenedShouldBeValue { get; set; } = "{1} AV should be greater than {0}.";
|
||||
public string AwakenedCap { get; init; } = "Individual AV cannot be greater than {0}.";
|
||||
public string AwakenedShouldBeValue { get; init; } = "{1} AV should be greater than {0}.";
|
||||
|
||||
public string BallAbility { get; set; } = "Can't obtain Hidden Ability with Ball.";
|
||||
public string BallEggCherish { get; set; } = "Can't have Cherish Ball for regular Egg.";
|
||||
public string BallEggMaster { get; set; } = "Can't have Master Ball for regular Egg.";
|
||||
public string BallEnc { get; set; } = "Correct ball for encounter type.";
|
||||
public string BallEncMismatch { get; set; } = "Can't have ball for encounter type.";
|
||||
public string BallHeavy { get; set; } = "Can't have Heavy Ball for light, low-catch rate species (Gen VII).";
|
||||
public string BallSpecies { get; set; } = "Can't obtain species in Ball.";
|
||||
public string BallSpeciesPass { get; set; } = "Ball possible for species.";
|
||||
public string BallUnavailable { get; set; } = "Ball unobtainable in origin Generation.";
|
||||
public string BallG4Sinnoh { get; set; } = "Ball value for D/P/Pt (0x83) is not within range.";
|
||||
public string BallG4Johto { get; set; } = "Extended Ball value for HG/SS (0x86) is not within range.";
|
||||
public string BallAbility { get; init; } = "Can't obtain Hidden Ability with Ball.";
|
||||
public string BallEggCherish { get; init; } = "Can't have Cherish Ball for regular Egg.";
|
||||
public string BallEggMaster { get; init; } = "Can't have Master Ball for regular Egg.";
|
||||
public string BallEnc { get; init; } = "Correct ball for encounter type.";
|
||||
public string BallEncMismatch { get; init; } = "Can't have ball for encounter type.";
|
||||
public string BallHeavy { get; init; } = "Can't have Heavy Ball for light, low-catch rate species (Gen VII).";
|
||||
public string BallSpecies { get; init; } = "Can't obtain species in Ball.";
|
||||
public string BallSpeciesPass { get; init; } = "Ball possible for species.";
|
||||
public string BallUnavailable { get; init; } = "Ball unobtainable in origin Generation.";
|
||||
public string BallG4Sinnoh { get; init; } = "Ball value for D/P/Pt (0x83) is not within range.";
|
||||
public string BallG4Johto { get; init; } = "Extended Ball value for HG/SS (0x86) is not within range.";
|
||||
|
||||
public string ContestZero { get; set; } = "Contest Stats should be 0.";
|
||||
public string ContestZeroSheen { get; set; } = "Contest Stat Sheen should be 0.";
|
||||
public string ContestSheenGEQ_0 { get; set; } = "Contest Stat Sheen should be >= {0}.";
|
||||
public string ContestSheenLEQ_0 { get; set; } = "Contest Stat Sheen should be <= {0}.";
|
||||
public string ContestZero { get; init; } = "Contest Stats should be 0.";
|
||||
public string ContestZeroSheen { get; init; } = "Contest Stat Sheen should be 0.";
|
||||
public string ContestSheenGEQ_0 { get; init; } = "Contest Stat Sheen should be >= {0}.";
|
||||
public string ContestSheenLEQ_0 { get; init; } = "Contest Stat Sheen should be <= {0}.";
|
||||
|
||||
public string DateCalendarInvalidMet { get; set; } = "Met Date is not a valid calendar date.";
|
||||
public string DateCalendarInvalidEgg { get; set; } = "Egg Met Date is not a valid calendar date.";
|
||||
public string DateLocalInvalidDate { get; set; } = "Local Date is outside of console's local time window.";
|
||||
public string DateLocalInvalidTime { get; set; } = "Local Time is not a valid timestamp.";
|
||||
public string DateOutsideDistributionWindow { get; set; } = "Met Date is outside of distribution window.";
|
||||
public string DateCalendarInvalidMet { get; init; } = "Met Date is not a valid calendar date.";
|
||||
public string DateCalendarInvalidEgg { get; init; } = "Egg Met Date is not a valid calendar date.";
|
||||
public string DateLocalInvalidDate { get; init; } = "Local Date is outside of console's local time window.";
|
||||
public string DateLocalInvalidTime { get; init; } = "Local Time is not a valid timestamp.";
|
||||
public string DateOutsideDistributionWindow { get; init; } = "Met Date is outside of distribution window.";
|
||||
|
||||
public string EggContest { get; set; } = "Cannot increase Contest Stats of an Egg.";
|
||||
public string EggEXP { get; set; } = "Eggs cannot receive experience.";
|
||||
public string EggFMetLevel_0 { get; set; } = "Invalid Met Level, expected {0}.";
|
||||
public string EggHatchCycles { get; set; } = "Invalid Egg hatch cycles.";
|
||||
public string EggLocation { get; set; } = "Able to hatch an Egg at Met Location.";
|
||||
public string EggLocationInvalid { get; set; } = "Can't hatch an Egg at Met Location.";
|
||||
public string EggLocationNone { get; set; } = "Invalid Egg Location, expected none.";
|
||||
public string EggLocationPalPark { get; set; } = "Invalid Met Location, expected Pal Park.";
|
||||
public string EggLocationTrade { get; set; } = "Able to hatch a traded Egg at Met Location.";
|
||||
public string EggLocationTradeFail { get; set; } = "Invalid Egg Location, shouldn't be 'traded' while an Egg.";
|
||||
public string EggMetLocationFail { get; set; } = "Can't obtain Egg from Egg Location.";
|
||||
public string EggNature { get; set; } = "Eggs cannot have their Stat Nature changed.";
|
||||
public string EggPP { get; set; } = "Eggs cannot have modified move PP counts.";
|
||||
public string EggPPUp { get; set; } = "Cannot apply PP Ups to an Egg.";
|
||||
public string EggRelearnFlags { get; set; } = "Expected no Relearn Move Flags.";
|
||||
public string EggShinyPokeStar { get; set; } = "Eggs cannot be a Pokéstar Studios star.";
|
||||
public string EggSpecies { get; set; } = "Can't obtain Egg for this species.";
|
||||
public string EggUnhatched { get; set; } = "Valid un-hatched Egg.";
|
||||
public string EggContest { get; init; } = "Cannot increase Contest Stats of an Egg.";
|
||||
public string EggEXP { get; init; } = "Eggs cannot receive experience.";
|
||||
public string EggFMetLevel_0 { get; init; } = "Invalid Met Level, expected {0}.";
|
||||
public string EggHatchCycles { get; init; } = "Invalid Egg hatch cycles.";
|
||||
public string EggLocation { get; init; } = "Able to hatch an Egg at Met Location.";
|
||||
public string EggLocationInvalid { get; init; } = "Can't hatch an Egg at Met Location.";
|
||||
public string EggLocationNone { get; init; } = "Invalid Egg Location, expected none.";
|
||||
public string EggLocationPalPark { get; init; } = "Invalid Met Location, expected Pal Park.";
|
||||
public string EggLocationTrade { get; init; } = "Able to hatch a traded Egg at Met Location.";
|
||||
public string EggLocationTradeFail { get; init; } = "Invalid Egg Location, shouldn't be 'traded' while an Egg.";
|
||||
public string EggMetLocationFail { get; init; } = "Can't obtain Egg from Egg Location.";
|
||||
public string EggNature { get; init; } = "Eggs cannot have their Stat Nature changed.";
|
||||
public string EggPP { get; init; } = "Eggs cannot have modified move PP counts.";
|
||||
public string EggPPUp { get; init; } = "Cannot apply PP Ups to an Egg.";
|
||||
public string EggRelearnFlags { get; init; } = "Expected no Relearn Move Flags.";
|
||||
public string EggShinyPokeStar { get; init; } = "Eggs cannot be a Pokéstar Studios star.";
|
||||
public string EggSpecies { get; init; } = "Can't obtain Egg for this species.";
|
||||
public string EggUnhatched { get; init; } = "Valid un-hatched Egg.";
|
||||
|
||||
public string EncCondition { get; set; } = "Valid Wild Encounter at location.";
|
||||
public string EncConditionBadRNGFrame { get; set; } = "Unable to match encounter conditions to a possible RNG frame.";
|
||||
public string EncConditionBadSpecies { get; set; } = "Species does not exist in origin game.";
|
||||
public string EncCondition { get; init; } = "Valid Wild Encounter at location.";
|
||||
public string EncConditionBadRNGFrame { get; init; } = "Unable to match encounter conditions to a possible RNG frame.";
|
||||
public string EncConditionBadSpecies { get; init; } = "Species does not exist in origin game.";
|
||||
|
||||
public string EncGift { get; set; } = "Unable to match a gift Egg encounter from origin game.";
|
||||
public string EncGiftEggEvent { get; set; } = "Unable to match an event Egg encounter from origin game.";
|
||||
public string EncGiftIVMismatch { get; set; } = "IVs do not match Mystery Gift Data.";
|
||||
public string EncGiftNicknamed { get; set; } = "Event gift has been nicknamed.";
|
||||
public string EncGiftNotFound { get; set; } = "Unable to match to a Mystery Gift in the database.";
|
||||
public string EncGiftPIDMismatch { get; set; } = "Mystery Gift fixed PID mismatch.";
|
||||
public string EncGiftShinyMismatch { get; set; } = "Mystery Gift shiny mismatch.";
|
||||
public string EncGiftVersionNotDistributed { get; set; } = "Mystery Gift cannot be received by this version.";
|
||||
public string EncGift { get; init; } = "Unable to match a gift Egg encounter from origin game.";
|
||||
public string EncGiftEggEvent { get; init; } = "Unable to match an event Egg encounter from origin game.";
|
||||
public string EncGiftIVMismatch { get; init; } = "IVs do not match Mystery Gift Data.";
|
||||
public string EncGiftNicknamed { get; init; } = "Event gift has been nicknamed.";
|
||||
public string EncGiftNotFound { get; init; } = "Unable to match to a Mystery Gift in the database.";
|
||||
public string EncGiftPIDMismatch { get; init; } = "Mystery Gift fixed PID mismatch.";
|
||||
public string EncGiftShinyMismatch { get; init; } = "Mystery Gift shiny mismatch.";
|
||||
public string EncGiftVersionNotDistributed { get; init; } = "Mystery Gift cannot be received by this version.";
|
||||
|
||||
public string EncInvalid { get; set; } = "Unable to match an encounter from origin game.";
|
||||
public string EncMasteryInitial { get; set; } = "Initial move mastery flags do not match the encounter's expected state.";
|
||||
public string EncInvalid { get; init; } = "Unable to match an encounter from origin game.";
|
||||
public string EncMasteryInitial { get; init; } = "Initial move mastery flags do not match the encounter's expected state.";
|
||||
|
||||
public string EncTradeChangedNickname { get; set; } = "In-game Trade Nickname has been altered.";
|
||||
public string EncTradeChangedOT { get; set; } = "In-game Trade OT has been altered.";
|
||||
public string EncTradeIndexBad { get; set; } = "In-game Trade invalid index?";
|
||||
public string EncTradeMatch { get; set; } = "Valid In-game trade.";
|
||||
public string EncTradeUnchanged { get; set; } = "In-game Trade OT and Nickname have not been altered.";
|
||||
public string EncTradeChangedNickname { get; init; } = "In-game Trade Nickname has been altered.";
|
||||
public string EncTradeChangedOT { get; init; } = "In-game Trade OT has been altered.";
|
||||
public string EncTradeIndexBad { get; init; } = "In-game Trade invalid index?";
|
||||
public string EncTradeMatch { get; init; } = "Valid In-game trade.";
|
||||
public string EncTradeUnchanged { get; init; } = "In-game Trade OT and Nickname have not been altered.";
|
||||
|
||||
public string EncStaticPIDShiny { get; set; } = "Encounter shiny mismatch.";
|
||||
public string EncTypeMatch { get; set; } = "Encounter Type matches encounter.";
|
||||
public string EncTypeMismatch { get; set; } = "Encounter Type does not match encounter.";
|
||||
public string EncUnreleased { get; set; } = "Unreleased event.";
|
||||
public string EncUnreleasedEMewJP { get; set; } = "Non japanese Mew from Faraway Island. Unreleased event.";
|
||||
public string EncStaticPIDShiny { get; init; } = "Encounter shiny mismatch.";
|
||||
public string EncTypeMatch { get; init; } = "Encounter Type matches encounter.";
|
||||
public string EncTypeMismatch { get; init; } = "Encounter Type does not match encounter.";
|
||||
public string EncUnreleased { get; init; } = "Unreleased event.";
|
||||
public string EncUnreleasedEMewJP { get; init; } = "Non japanese Mew from Faraway Island. Unreleased event.";
|
||||
|
||||
public string EReaderAmerica { get; set; } = "American E-Reader Berry in Japanese save file.";
|
||||
public string EReaderInvalid { get; set; } = "Invalid E-Reader Berry.";
|
||||
public string EReaderJapan { get; set; } = "Japanese E-Reader Berry in international save file.";
|
||||
public string EReaderAmerica { get; init; } = "American E-Reader Berry in Japanese save file.";
|
||||
public string EReaderInvalid { get; init; } = "Invalid E-Reader Berry.";
|
||||
public string EReaderJapan { get; init; } = "Japanese E-Reader Berry in international save file.";
|
||||
|
||||
public string Effort2Remaining { get; set; } = "2 EVs remaining.";
|
||||
public string EffortAbove252 { get; set; } = "EVs cannot go above 252.";
|
||||
public string EffortAbove510 { get; set; } = "EV total cannot be above 510.";
|
||||
public string EffortAllEqual { get; set; } = "EVs are all equal.";
|
||||
public string EffortCap100 { get; set; } = "Individual EV for a level 100 encounter in Generation 4 cannot be greater than 100.";
|
||||
public string EffortEgg { get; set; } = "Eggs cannot receive EVs.";
|
||||
public string EffortShouldBeZero { get; set; } = "Cannot receive EVs.";
|
||||
public string EffortEXPIncreased { get; set; } = "All EVs are zero, but leveled above Met Level.";
|
||||
public string EffortUntrainedCap { get; set; } = "Individual EV without changing EXP cannot be greater than {0}.";
|
||||
public string Effort2Remaining { get; init; } = "2 EVs remaining.";
|
||||
public string EffortAbove252 { get; init; } = "EVs cannot go above 252.";
|
||||
public string EffortAbove510 { get; init; } = "EV total cannot be above 510.";
|
||||
public string EffortAllEqual { get; init; } = "EVs are all equal.";
|
||||
public string EffortCap100 { get; init; } = "Individual EV for a level 100 encounter in Generation 4 cannot be greater than 100.";
|
||||
public string EffortEgg { get; init; } = "Eggs cannot receive EVs.";
|
||||
public string EffortShouldBeZero { get; init; } = "Cannot receive EVs.";
|
||||
public string EffortEXPIncreased { get; init; } = "All EVs are zero, but leveled above Met Level.";
|
||||
public string EffortUntrainedCap { get; init; } = "Individual EV without changing EXP cannot be greater than {0}.";
|
||||
|
||||
public string EvoInvalid { get; set; } = "Evolution not valid (or level/trade evolution unsatisfied).";
|
||||
public string EvoTradeReqOutsider { get; set; } = "Outsider {0} should have evolved into {1}.";
|
||||
public string EvoTradeRequired { get; set; } = "Version Specific evolution requires a trade to opposite version. A Handling Trainer is required.";
|
||||
public string EvoInvalid { get; init; } = "Evolution not valid (or level/trade evolution unsatisfied).";
|
||||
public string EvoTradeReqOutsider { get; init; } = "Outsider {0} should have evolved into {1}.";
|
||||
public string EvoTradeRequired { get; init; } = "Version Specific evolution requires a trade to opposite version. A Handling Trainer is required.";
|
||||
|
||||
public string FatefulGiftMissing { get; set; } = "Fateful Encounter with no matching Encounter. Has the Mystery Gift data been contributed?";
|
||||
public string FatefulInvalid { get; set; } = "Fateful Encounter should not be checked.";
|
||||
public string FatefulMissing { get; set; } = "Special In-game Fateful Encounter flag missing.";
|
||||
public string FatefulMystery { get; set; } = "Mystery Gift Fateful Encounter.";
|
||||
public string FatefulMysteryMissing { get; set; } = "Mystery Gift Fateful Encounter flag missing.";
|
||||
public string FatefulGiftMissing { get; init; } = "Fateful Encounter with no matching Encounter. Has the Mystery Gift data been contributed?";
|
||||
public string FatefulInvalid { get; init; } = "Fateful Encounter should not be checked.";
|
||||
public string FatefulMissing { get; init; } = "Special In-game Fateful Encounter flag missing.";
|
||||
public string FatefulMystery { get; init; } = "Mystery Gift Fateful Encounter.";
|
||||
public string FatefulMysteryMissing { get; init; } = "Mystery Gift Fateful Encounter flag missing.";
|
||||
|
||||
public string FavoriteMarkingUnavailable { get; set; } = "Favorite Marking is not available.";
|
||||
public string FavoriteMarkingUnavailable { get; init; } = "Favorite Marking is not available.";
|
||||
|
||||
public string FormArgumentLEQ_0 { get; set; } = "Form argument is too high for current form.";
|
||||
public string FormArgumentGEQ_0 { get; set; } = "Form argument is too low for current form.";
|
||||
public string FormArgumentNotAllowed { get; set; } = "Form argument is not allowed for this encounter.";
|
||||
public string FormArgumentValid { get; set; } = "Form argument is valid.";
|
||||
public string FormArgumentInvalid { get; set; } = "Form argument is not valid.";
|
||||
public string FormBattle { get; set; } = "Form cannot exist outside of a battle.";
|
||||
public string FormEternal { get; set; } = "Valid Eternal Flower encounter.";
|
||||
public string FormEternalInvalid { get; set; } = "Invalid Eternal Flower encounter.";
|
||||
public string FormInvalidGame { get; set; } = "Form cannot be obtained in origin game.";
|
||||
public string FormInvalidNature { get; set; } = "Form cannot have this nature.";
|
||||
public string FormItem { get; set; } = "Held item matches Form.";
|
||||
public string FormItemInvalid { get; set; } = "Held item does not match Form.";
|
||||
public string FormParty { get; set; } = "Form cannot exist outside of Party.";
|
||||
public string FormPikachuCosplay { get; set; } = "Only Cosplay Pikachu can have this form.";
|
||||
public string FormPikachuCosplayInvalid { get; set; } = "Cosplay Pikachu cannot have the default form.";
|
||||
public string FormPikachuEventInvalid { get; set; } = "Event Pikachu cannot have the default form.";
|
||||
public string FormInvalidExpect_0 { get; set; } = "Form is invalid, expected form index {0}.";
|
||||
public string FormValid { get; set; } = "Form is Valid.";
|
||||
public string FormVivillon { get; set; } = "Valid Vivillon pattern.";
|
||||
public string FormVivillonEventPre { get; set; } = "Event Vivillon pattern on pre-evolution.";
|
||||
public string FormVivillonInvalid { get; set; } = "Invalid Vivillon pattern.";
|
||||
public string FormVivillonNonNative { get; set; } = "Non-native Vivillon pattern.";
|
||||
public string FormArgumentLEQ_0 { get; init; } = "Form argument is too high for current form.";
|
||||
public string FormArgumentGEQ_0 { get; init; } = "Form argument is too low for current form.";
|
||||
public string FormArgumentNotAllowed { get; init; } = "Form argument is not allowed for this encounter.";
|
||||
public string FormArgumentValid { get; init; } = "Form argument is valid.";
|
||||
public string FormArgumentInvalid { get; init; } = "Form argument is not valid.";
|
||||
public string FormBattle { get; init; } = "Form cannot exist outside of a battle.";
|
||||
public string FormInvalidGame { get; init; } = "Form cannot be obtained in origin game.";
|
||||
public string FormInvalidNature { get; init; } = "Form cannot have this nature.";
|
||||
public string FormItem { get; init; } = "Held item matches Form.";
|
||||
public string FormItemInvalid { get; init; } = "Held item does not match Form.";
|
||||
public string FormParty { get; init; } = "Form cannot exist outside of Party.";
|
||||
public string FormInvalidExpect_0 { get; init; } = "Form is invalid, expected form index {0}.";
|
||||
public string FormValid { get; init; } = "Form is Valid.";
|
||||
public string FormVivillon { get; init; } = "Valid Vivillon pattern.";
|
||||
public string FormVivillonEventPre { get; init; } = "Event Vivillon pattern on pre-evolution.";
|
||||
public string FormVivillonInvalid { get; init; } = "Invalid Vivillon pattern.";
|
||||
public string FormVivillonNonNative { get; init; } = "Non-native Vivillon pattern.";
|
||||
|
||||
public string G1CatchRateChain { get; set; } = "Catch rate does not match any species from Pokémon evolution chain.";
|
||||
public string G1CatchRateEvo { get; set; } = "Catch rate match species without encounters. Expected a preevolution catch rate.";
|
||||
public string G1CatchRateItem { get; set; } = "Catch rate does not match a valid held item from Generation 2.";
|
||||
public string G1CatchRateMatchPrevious { get; set; } = "Catch Rate matches a species from Pokémon evolution chain.";
|
||||
public string G1CatchRateMatchTradeback { get; set; } = "Catch rate matches a valid held item from Generation 2.";
|
||||
public string G1CatchRateNone { get; set; } = "Catch rate does not match any species from Pokémon evolution chain or any Generation 2 held items.";
|
||||
public string G1CharNick { get; set; } = "Nickname from Generation 1/2 uses unavailable characters.";
|
||||
public string G1CharOT { get; set; } = "OT from Generation 1/2 uses unavailable characters.";
|
||||
public string G1OTGender { get; set; } = "Female OT from Generation 1/2 is invalid.";
|
||||
public string G1Stadium { get; set; } = "Incorrect Stadium OT.";
|
||||
public string G1Type1Fail { get; set; } = "Invalid Type A, does not match species type.";
|
||||
public string G1Type2Fail { get; set; } = "Invalid Type B, does not match species type.";
|
||||
public string G1TypeMatch1 { get; set; } = "Valid Type A, matches species type.";
|
||||
public string G1TypeMatch2 { get; set; } = "Valid Type B, matches species type.";
|
||||
public string G1TypeMatchPorygon { get; set; } = "Porygon with valid Type A and B values.";
|
||||
public string G1TypePorygonFail { get; set; } = "Porygon with invalid Type A and B values. Does not a match a valid type combination.";
|
||||
public string G1TypePorygonFail1 { get; set; } = "Porygon with invalid Type A value.";
|
||||
public string G1TypePorygonFail2 { get; set; } = "Porygon with invalid Type B value.";
|
||||
public string G2InvalidTileTreeNotFound { get; set; } = "Could not find a tree for Crystal headbutt encounter that matches OTID.";
|
||||
public string G2TreeID { get; set; } = "Found a tree for Crystal headbutt encounter that matches OTID.";
|
||||
public string G2OTGender { get; set; } = "OT from Virtual Console games other than Crystal cannot be female.";
|
||||
public string G1CatchRateChain { get; init; } = "Catch rate does not match any species from Pokémon evolution chain.";
|
||||
public string G1CatchRateEvo { get; init; } = "Catch rate match species without encounters. Expected a pre-evolution catch rate.";
|
||||
public string G1CatchRateItem { get; init; } = "Catch rate does not match a valid held item from Generation 2.";
|
||||
public string G1CatchRateMatchPrevious { get; init; } = "Catch Rate matches a species from Pokémon evolution chain.";
|
||||
public string G1CatchRateMatchTradeback { get; init; } = "Catch rate matches a valid held item from Generation 2.";
|
||||
public string G1CatchRateNone { get; init; } = "Catch rate does not match any species from Pokémon evolution chain or any Generation 2 held items.";
|
||||
public string G1CharNick { get; init; } = "Nickname from Generation 1/2 uses unavailable characters.";
|
||||
public string G1CharOT { get; init; } = "OT from Generation 1/2 uses unavailable characters.";
|
||||
public string G1OTGender { get; init; } = "Female OT from Generation 1/2 is invalid.";
|
||||
public string G1Stadium { get; init; } = "Incorrect Stadium OT.";
|
||||
public string G1Type1Fail { get; init; } = "Invalid Type A, does not match species type.";
|
||||
public string G1Type2Fail { get; init; } = "Invalid Type B, does not match species type.";
|
||||
public string G1TypeMatch1 { get; init; } = "Valid Type A, matches species type.";
|
||||
public string G1TypeMatch2 { get; init; } = "Valid Type B, matches species type.";
|
||||
public string G1TypeMatchPorygon { get; init; } = "Porygon with valid Type A and B values.";
|
||||
public string G1TypePorygonFail { get; init; } = "Porygon with invalid Type A and B values. Does not a match a valid type combination.";
|
||||
public string G1TypePorygonFail1 { get; init; } = "Porygon with invalid Type A value.";
|
||||
public string G1TypePorygonFail2 { get; init; } = "Porygon with invalid Type B value.";
|
||||
public string G2InvalidTileTreeNotFound { get; init; } = "Could not find a tree for Crystal headbutt encounter that matches OTID.";
|
||||
public string G2TreeID { get; init; } = "Found a tree for Crystal headbutt encounter that matches OTID.";
|
||||
public string G2OTGender { get; init; } = "OT from Virtual Console games other than Crystal cannot be female.";
|
||||
|
||||
public string G3EReader { get; set; } = "Non Japanese Shadow E-reader Pokémon. Unreleased encounter.";
|
||||
public string G3OTGender { get; set; } = "OT from Colosseum/XD cannot be female.";
|
||||
public string G4InvalidTileR45Surf { get; set; } = "Johto Route 45 surfing encounter. Unreachable Water tiles.";
|
||||
public string G4PartnerMoodEgg { get; set; } = "Eggs cannot have an Mood stat value.";
|
||||
public string G4PartnerMoodZero { get; set; } = "Mood stat value should be zero when not in the player's party.";
|
||||
public string G4ShinyLeafBitsInvalid { get; set; } = "Shiny Leaf/Crown bits are not valid.";
|
||||
public string G4ShinyLeafBitsEgg { get; set; } = "Eggs cannot have Shiny Leaf/Crown.";
|
||||
public string G5IVAll30 { get; set; } = "All IVs of N's Pokémon should be 30.";
|
||||
public string G5PIDShinyGrotto { get; set; } = "Hidden Grotto captures cannot be shiny.";
|
||||
public string G5SparkleInvalid { get; set; } = "Special In-game N's Sparkle flag should not be checked.";
|
||||
public string G5SparkleRequired { get; set; } = "Special In-game N's Sparkle flag missing.";
|
||||
public string G5PokeStarMustBeZero { get; set; } = "Pokéstar Studios fame must be zero, cannot participate.";
|
||||
public string G5PokeStarImpossibleValue { get; set; } = "Pokéstar Studios fame value is unreachable.";
|
||||
public string G7BSocialShouldBe100Spirit { get; set; } = "Spirit should be 100 for Pokémon not in the player's party.";
|
||||
public string G7BSocialShouldBe100Mood { get; set; } = "Mood should be 100 for Pokémon not in the player's party.";
|
||||
public string G3EReader { get; init; } = "Non Japanese Shadow E-reader Pokémon. Unreleased encounter.";
|
||||
public string G3OTGender { get; init; } = "OT from Colosseum/XD cannot be female.";
|
||||
public string G4InvalidTileR45Surf { get; init; } = "Johto Route 45 surfing encounter. Unreachable Water tiles.";
|
||||
public string G4PartnerMoodEgg { get; init; } = "Eggs cannot have an Mood stat value.";
|
||||
public string G4PartnerMoodZero { get; init; } = "Mood stat value should be zero when not in the player's party.";
|
||||
public string G4ShinyLeafBitsInvalid { get; init; } = "Shiny Leaf/Crown bits are not valid.";
|
||||
public string G4ShinyLeafBitsEgg { get; init; } = "Eggs cannot have Shiny Leaf/Crown.";
|
||||
public string G5IVAll30 { get; init; } = "All IVs of N's Pokémon should be 30.";
|
||||
public string G5PIDShinyGrotto { get; init; } = "Hidden Grotto captures cannot be shiny.";
|
||||
public string G5SparkleInvalid { get; init; } = "Special In-game N's Sparkle flag should not be checked.";
|
||||
public string G5SparkleRequired { get; init; } = "Special In-game N's Sparkle flag missing.";
|
||||
public string G5PokeStarMustBeZero { get; init; } = "Pokéstar Studios fame must be zero, cannot participate.";
|
||||
public string G5PokeStarImpossibleValue { get; init; } = "Pokéstar Studios fame value is unreachable.";
|
||||
public string G7BSocialShouldBe100Spirit { get; init; } = "Spirit should be 100 for Pokémon not in the player's party.";
|
||||
public string G7BSocialShouldBe100Mood { get; init; } = "Mood should be 100 for Pokémon not in the player's party.";
|
||||
|
||||
public string GanbaruStatTooHigh { get; set; } = "One or more Ganbaru Value is above the natural limit of (10 - IV bonus).";
|
||||
public string GanbaruStatTooHigh { get; init; } = "One or more Ganbaru Value is above the natural limit of (10 - IV bonus).";
|
||||
|
||||
public string GenderInvalidNone { get; set; } = "Genderless Pokémon should not have a gender.";
|
||||
public string GeoBadOrder { get; set; } = "GeoLocation Memory: Gap/Blank present.";
|
||||
public string GeoHardwareInvalid { get; set; } = "Geolocation: Country is not in 3DS region.";
|
||||
public string GeoHardwareRange { get; set; } = "Invalid Console Region.";
|
||||
public string GeoHardwareValid { get; set; } = "Geolocation: Country is in 3DS region.";
|
||||
public string GeoMemoryMissing { get; set; } = "GeoLocation Memory: Memories should be present.";
|
||||
public string GeoNoCountryHT { get; set; } = "GeoLocation Memory: HT Name present but has no previous Country.";
|
||||
public string GeoNoRegion { get; set; } = "GeoLocation Memory: Region without Country.";
|
||||
public string GenderInvalidNone { get; init; } = "Genderless Pokémon should not have a gender.";
|
||||
public string GeoBadOrder { get; init; } = "GeoLocation Memory: Gap/Blank present.";
|
||||
public string GeoHardwareInvalid { get; init; } = "Geolocation: Country is not in 3DS region.";
|
||||
public string GeoHardwareRange { get; init; } = "Invalid Console Region.";
|
||||
public string GeoHardwareValid { get; init; } = "Geolocation: Country is in 3DS region.";
|
||||
public string GeoMemoryMissing { get; init; } = "GeoLocation Memory: Memories should be present.";
|
||||
public string GeoNoCountryHT { get; init; } = "GeoLocation Memory: HT Name present but has no previous Country.";
|
||||
public string GeoNoRegion { get; init; } = "GeoLocation Memory: Region without Country.";
|
||||
|
||||
public string HyperTrainLevelGEQ_0 { get; set; } = "Can't Hyper Train a Pokémon that isn't level {0}.";
|
||||
public string HyperPerfectAll { get; set; } = "Can't Hyper Train a Pokémon with perfect IVs.";
|
||||
public string HyperPerfectOne { get; set; } = "Can't Hyper Train a perfect IV.";
|
||||
public string HyperPerfectUnavailable { get; set; } = "Can't Hyper Train any IV(s).";
|
||||
public string HyperTrainLevelGEQ_0 { get; init; } = "Can't Hyper Train a Pokémon that isn't level {0}.";
|
||||
public string HyperPerfectAll { get; init; } = "Can't Hyper Train a Pokémon with perfect IVs.";
|
||||
public string HyperPerfectOne { get; init; } = "Can't Hyper Train a perfect IV.";
|
||||
public string HyperPerfectUnavailable { get; init; } = "Can't Hyper Train any IV(s).";
|
||||
|
||||
public string ItemEgg { get; set; } = "Eggs cannot hold items.";
|
||||
public string ItemUnreleased { get; set; } = "Held item is unreleased.";
|
||||
public string ItemEgg { get; init; } = "Eggs cannot hold items.";
|
||||
public string ItemUnreleased { get; init; } = "Held item is unreleased.";
|
||||
|
||||
public string IVAllEqual_0 { get; set; } = "All IVs are {0}.";
|
||||
public string IVNotCorrect { get; set; } = "IVs do not match encounter requirements.";
|
||||
public string IVFlawlessCountGEQ_0 { get; set; } = "Should have at least {0} IVs = 31.";
|
||||
public string IVAllEqual_0 { get; init; } = "All IVs are {0}.";
|
||||
public string IVNotCorrect { get; init; } = "IVs do not match encounter requirements.";
|
||||
public string IVFlawlessCountGEQ_0 { get; init; } = "Should have at least {0} IVs = 31.";
|
||||
|
||||
public string LevelBoostNotZero { get; set; } = "Level Boost should be zero.";
|
||||
public string LevelEXPThreshold { get; set; } = "Current experience matches level threshold.";
|
||||
public string LevelEXPTooHigh { get; set; } = "Current experience exceeds maximum amount for level 100.";
|
||||
public string LevelMetBelow { get; set; } = "Current level is below met level.";
|
||||
public string LevelMetGift { get; set; } = "Met Level does not match Mystery Gift level.";
|
||||
public string LevelMetGiftFail { get; set; } = "Current Level below Mystery Gift level.";
|
||||
public string LevelMetSane { get; set; } = "Current level is not below met level.";
|
||||
public string LevelBoostNotZero { get; init; } = "Level Boost should be zero.";
|
||||
public string LevelEXPThreshold { get; init; } = "Current experience matches level threshold.";
|
||||
public string LevelEXPTooHigh { get; init; } = "Current experience exceeds maximum amount for level 100.";
|
||||
public string LevelMetBelow { get; init; } = "Current level is below met level.";
|
||||
public string LevelMetGift { get; init; } = "Met Level does not match Mystery Gift level.";
|
||||
public string LevelMetGiftFail { get; init; } = "Current Level below Mystery Gift level.";
|
||||
public string LevelMetSane { get; init; } = "Current level is not below met level.";
|
||||
|
||||
public string MarkValueOutOfRange_0 { get; set; } = "Individual marking at index {0} is not within the allowed value range.";
|
||||
public string MarkValueShouldBeZero { get; set; } = "Marking flags cannot be set.";
|
||||
public string MarkValueUnusedBitsPresent { get; set; } = "Marking flags uses bits beyond the accessible range.";
|
||||
public string MarkValueOutOfRange_0 { get; init; } = "Individual marking at index {0} is not within the allowed value range.";
|
||||
public string MarkValueShouldBeZero { get; init; } = "Marking flags cannot be set.";
|
||||
public string MarkValueUnusedBitsPresent { get; init; } = "Marking flags uses bits beyond the accessible range.";
|
||||
|
||||
public string MemoryArgBadCatch_H { get; set; } = "{0} Memory: {0} did not catch this.";
|
||||
public string MemoryArgBadHatch_H { get; set; } = "{0} Memory: {0} did not hatch this.";
|
||||
public string MemoryArgBadHT { get; set; } = "Memory: Can't have Handling Trainer Memory as Egg.";
|
||||
public string MemoryArgBadID_H { get; set; } = "{0} Memory: Can't obtain Memory on {0} Version.";
|
||||
public string MemoryArgBadItem_H1 { get; set; } = "{0} Memory: Species can't hold this item.";
|
||||
public string MemoryArgBadLocation_H { get; set; } = "{0} Memory: Can't obtain Location on {0} Version.";
|
||||
public string MemoryArgBadMove_H1 { get; set; } = "{0} Memory: Species can't learn {1}.";
|
||||
public string MemoryArgBadOTEgg_H { get; set; } = "{0} Memory: Link Trade is not a valid first memory.";
|
||||
public string MemoryArgBadSpecies_H1 { get; set; } = "{0} Memory: Can't capture species in game.";
|
||||
public string MemoryArgSpecies_H { get; set; } = "{0} Memory: Species can be captured in game.";
|
||||
public string MemoryCleared_H { get; set; } = "Memory: Not cleared properly.";
|
||||
public string MemoryValid_H { get; set; } = "{0} Memory is valid.";
|
||||
public string MemoryFeelInvalid_H { get; set; } = "{0} Memory: Invalid Feeling.";
|
||||
public string MemoryHTFlagInvalid { get; set; } = "Untraded: Current handler should not be the Handling Trainer.";
|
||||
public string MemoryHTGender_0 { get; set; } = "HT Gender invalid: {0}";
|
||||
public string MemoryHTLanguage { get; set; } = "HT Language is missing.";
|
||||
public string MemoryArgBadCatch_H { get; init; } = "{0} Memory: {0} did not catch this.";
|
||||
public string MemoryArgBadHatch_H { get; init; } = "{0} Memory: {0} did not hatch this.";
|
||||
public string MemoryArgBadHT { get; init; } = "Memory: Can't have Handling Trainer Memory as Egg.";
|
||||
public string MemoryArgBadID_H { get; init; } = "{0} Memory: Can't obtain Memory on {0} Version.";
|
||||
public string MemoryArgBadItem_H1 { get; init; } = "{0} Memory: Species can't hold this item.";
|
||||
public string MemoryArgBadLocation_H { get; init; } = "{0} Memory: Can't obtain Location on {0} Version.";
|
||||
public string MemoryArgBadMove_H1 { get; init; } = "{0} Memory: Species can't learn {1}.";
|
||||
public string MemoryArgBadOTEgg_H { get; init; } = "{0} Memory: Link Trade is not a valid first memory.";
|
||||
public string MemoryArgBadSpecies_H1 { get; init; } = "{0} Memory: Can't capture species in game.";
|
||||
public string MemoryArgSpecies_H { get; init; } = "{0} Memory: Species can be captured in game.";
|
||||
public string MemoryCleared_H { get; init; } = "Memory: Not cleared properly.";
|
||||
public string MemoryValid_H { get; init; } = "{0} Memory is valid.";
|
||||
public string MemoryFeelInvalid_H { get; init; } = "{0} Memory: Invalid Feeling.";
|
||||
public string MemoryHTFlagInvalid { get; init; } = "Untraded: Current handler should not be the Handling Trainer.";
|
||||
public string MemoryHTGender_0 { get; init; } = "HT Gender invalid: {0}";
|
||||
public string MemoryHTLanguage { get; init; } = "HT Language is missing.";
|
||||
|
||||
public string MemoryIndexArgHT { get; set; } = "Should have a HT Memory TextVar value (somewhere).";
|
||||
public string MemoryIndexFeel_H1 { get; set; } = "{0} Memory: Feeling should be index {1}.";
|
||||
public string MemoryIndexFeelHTLEQ9 { get; set; } = "Should have a HT Memory Feeling value 0-9.";
|
||||
public string MemoryIndexID_H1 { get; set; } = "{0} Memory: Should be index {1}.";
|
||||
public string MemoryIndexIntensity_H1 { get; set; } = "{0} Memory: Intensity should be index {1}.";
|
||||
public string MemoryIndexIntensityHT1 { get; set; } = "Should have a HT Memory Intensity value (1st).";
|
||||
public string MemoryIndexIntensityMin_H1 { get; set; } = "{0} Memory: Intensity should be at least {1}.";
|
||||
public string MemoryIndexLinkHT { get; set; } = "Should have a Link Trade HT Memory.";
|
||||
public string MemoryIndexVar { get; set; } = "{0} Memory: TextVar should be index {1}.";
|
||||
public string MemoryMissingHT { get; set; } = "Memory: Handling Trainer Memory missing.";
|
||||
public string MemoryMissingOT { get; set; } = "Memory: Original Trainer Memory missing.";
|
||||
public string MemoryIndexArgHT { get; init; } = "Should have a HT Memory TextVar value (somewhere).";
|
||||
public string MemoryIndexFeel_H1 { get; init; } = "{0} Memory: Feeling should be index {1}.";
|
||||
public string MemoryIndexFeelHTLEQ9 { get; init; } = "Should have a HT Memory Feeling value 0-9.";
|
||||
public string MemoryIndexID_H1 { get; init; } = "{0} Memory: Should be index {1}.";
|
||||
public string MemoryIndexIntensity_H1 { get; init; } = "{0} Memory: Intensity should be index {1}.";
|
||||
public string MemoryIndexIntensityHT1 { get; init; } = "Should have a HT Memory Intensity value (1st).";
|
||||
public string MemoryIndexIntensityMin_H1 { get; init; } = "{0} Memory: Intensity should be at least {1}.";
|
||||
public string MemoryIndexLinkHT { get; init; } = "Should have a Link Trade HT Memory.";
|
||||
public string MemoryIndexVar { get; init; } = "{0} Memory: TextVar should be index {1}.";
|
||||
public string MemoryMissingHT { get; init; } = "Memory: Handling Trainer Memory missing.";
|
||||
public string MemoryMissingOT { get; init; } = "Memory: Original Trainer Memory missing.";
|
||||
|
||||
public string MemorySocialZero { get; set; } = "Social Stat should be zero.";
|
||||
public string MemoryStatSocialLEQ_0 { get; set; } = "Social Stat should be <= {0}";
|
||||
public string MemorySocialZero { get; init; } = "Social Stat should be zero.";
|
||||
public string MemoryStatSocialLEQ_0 { get; init; } = "Social Stat should be <= {0}";
|
||||
|
||||
public string MemoryStatAffectionHT0 { get; set; } = "Untraded: Handling Trainer Affection should be 0.";
|
||||
public string MemoryStatAffectionOT0 { get; set; } = "OT Affection should be 0.";
|
||||
public string MemoryStatFriendshipHT0 { get; set; } = "Untraded: Handling Trainer Friendship should be 0.";
|
||||
public string MemoryStatFriendshipOTBaseEvent_0 { get; set; } = "Event OT Friendship does not match base friendship ({0}).";
|
||||
public string MemoryStatAffectionHT0 { get; init; } = "Untraded: Handling Trainer Affection should be 0.";
|
||||
public string MemoryStatAffectionOT0 { get; init; } = "OT Affection should be 0.";
|
||||
public string MemoryStatFriendshipHT0 { get; init; } = "Untraded: Handling Trainer Friendship should be 0.";
|
||||
public string MemoryStatFriendshipOTBaseEvent_0 { get; init; } = "Event OT Friendship does not match base friendship ({0}).";
|
||||
|
||||
public string MetDetailTimeOfDay { get; set; } = "Met Time of Day value is not within the expected range.";
|
||||
public string MoveEvoFCombination_0 { get; set; } = "Moves combinations is not compatible with {0} evolution.";
|
||||
public string MoveFExpectSingle_0 { get; set; } = "Expected: {0}";
|
||||
public string MoveKeldeoMismatch { get; set; } = "Keldeo Move/Form mismatch.";
|
||||
public string MovePPExpectHealed_01 { get; set; } = "Move {0} PP is below the amount expected ({1}).";
|
||||
public string MovePPTooHigh_01 { get; set; } = "Move {0} PP is above the amount allowed ({1}).";
|
||||
public string MovePPUpsTooHigh_01 { get; set; } = "Move {0} PP Ups is above the amount allowed ({1}).";
|
||||
public string MetDetailTimeOfDay { get; init; } = "Met Time of Day value is not within the expected range.";
|
||||
public string MoveEvoFCombination_0 { get; init; } = "Moves combinations is not compatible with {0} evolution.";
|
||||
public string MoveFExpectSingle_0 { get; init; } = "Expected: {0}";
|
||||
public string MoveKeldeoMismatch { get; init; } = "Keldeo Move/Form mismatch.";
|
||||
public string MovePPExpectHealed_01 { get; init; } = "Move {0} PP is below the amount expected ({1}).";
|
||||
public string MovePPTooHigh_01 { get; init; } = "Move {0} PP is above the amount allowed ({1}).";
|
||||
public string MovePPUpsTooHigh_01 { get; init; } = "Move {0} PP Ups is above the amount allowed ({1}).";
|
||||
|
||||
public string MoveShopAlphaMoveShouldBeMastered_0 { get; set; } = "Alpha Move should be marked as mastered.";
|
||||
public string MoveShopAlphaMoveShouldBeOther { get; set; } = "Alpha encounter cannot be found with this Alpha Move.";
|
||||
public string MoveShopAlphaMoveShouldBeZero { get; set; } = "Only Alphas may have an Alpha Move set.";
|
||||
public string MoveShopMasterInvalid_0 { get; set; } = "Cannot manually master {0}: not permitted to master.";
|
||||
public string MoveShopMasterNotLearned_0 { get; set; } = "Cannot manually master {0}: not in possible learned level up moves.";
|
||||
public string MoveShopPurchaseInvalid_0 { get; set; } = "Cannot purchase {0} from the move shop.";
|
||||
public string MoveShopAlphaMoveShouldBeMastered_0 { get; init; } = "Alpha Move should be marked as mastered.";
|
||||
public string MoveShopAlphaMoveShouldBeOther { get; init; } = "Alpha encounter cannot be found with this Alpha Move.";
|
||||
public string MoveShopAlphaMoveShouldBeZero { get; init; } = "Only Alphas may have an Alpha Move set.";
|
||||
public string MoveShopMasterInvalid_0 { get; init; } = "Cannot manually master {0}: not permitted to master.";
|
||||
public string MoveShopMasterNotLearned_0 { get; init; } = "Cannot manually master {0}: not in possible learned level up moves.";
|
||||
public string MoveShopPurchaseInvalid_0 { get; init; } = "Cannot purchase {0} from the move shop.";
|
||||
|
||||
public string MoveTechRecordFlagMissing_0 { get; set; } = "Unexpected Technical Record Learned flag: {0}";
|
||||
public string MoveTechRecordFlagMissing_0 { get; init; } = "Unexpected Technical Record Learned flag: {0}";
|
||||
|
||||
public string NickFlagEggNo { get; set; } = "Egg must be not nicknamed.";
|
||||
public string NickFlagEggYes { get; set; } = "Egg must be nicknamed.";
|
||||
public string NickInvalidChar { get; set; } = "Cannot be given this Nickname.";
|
||||
public string NickLengthLong { get; set; } = "Nickname too long.";
|
||||
public string NickLengthShort { get; set; } = "Nickname is empty.";
|
||||
public string NickMatchLanguage { get; set; } = "Nickname matches species name.";
|
||||
public string NickMatchLanguageEgg { get; set; } = "Egg matches language Egg name.";
|
||||
public string NickMatchLanguageEggFail { get; set; } = "Egg name does not match language Egg name.";
|
||||
public string NickMatchLanguageFail { get; set; } = "Nickname does not match species name.";
|
||||
public string NickMatchLanguageFlag { get; set; } = "Nickname flagged, matches species name.";
|
||||
public string NickMatchNoOthers { get; set; } = "Nickname does not match another species name.";
|
||||
public string NickMatchNoOthersFail { get; set; } = "Nickname matches another species name (+language).";
|
||||
public string NickFlagEggNo { get; init; } = "Egg must be not nicknamed.";
|
||||
public string NickFlagEggYes { get; init; } = "Egg must be nicknamed.";
|
||||
public string NickInvalidChar { get; init; } = "Cannot be given this Nickname.";
|
||||
public string NickLengthLong { get; init; } = "Nickname too long.";
|
||||
public string NickLengthShort { get; init; } = "Nickname is empty.";
|
||||
public string NickMatchLanguage { get; init; } = "Nickname matches species name.";
|
||||
public string NickMatchLanguageEgg { get; init; } = "Egg matches language Egg name.";
|
||||
public string NickMatchLanguageEggFail { get; init; } = "Egg name does not match language Egg name.";
|
||||
public string NickMatchLanguageFail { get; init; } = "Nickname does not match species name.";
|
||||
public string NickMatchLanguageFlag { get; init; } = "Nickname flagged, matches species name.";
|
||||
public string NickMatchNoOthers { get; init; } = "Nickname does not match another species name.";
|
||||
public string NickMatchNoOthersFail { get; init; } = "Nickname matches another species name (+language).";
|
||||
|
||||
public string OTLanguage { get; set; } = "Language ID should be {0}, not {1}.";
|
||||
public string OTLong { get; set; } = "OT Name too long.";
|
||||
public string OTShort { get; set; } = "OT Name too short.";
|
||||
public string OTSuspicious { get; set; } = "Suspicious Original Trainer details.";
|
||||
public string OTLanguage { get; init; } = "Language ID should be {0}, not {1}.";
|
||||
public string OTLong { get; init; } = "OT Name too long.";
|
||||
public string OTShort { get; init; } = "OT Name too short.";
|
||||
public string OTSuspicious { get; init; } = "Suspicious Original Trainer details.";
|
||||
|
||||
public string OT_IDEqual { get; set; } = "TID16 and SID16 are equal.";
|
||||
public string OT_IDs0 { get; set; } = "TID16 and SID16 are 0.";
|
||||
public string OT_SID0 { get; set; } = "SID16 is zero.";
|
||||
public string OT_SID0Invalid { get; set; } = "SID16 should be 0.";
|
||||
public string OT_TID0 { get; set; } = "TID16 is zero.";
|
||||
public string OT_IDInvalid { get; set; } = "TID16 and SID16 combination is not possible.";
|
||||
public string OT_IDEqual { get; init; } = "TID16 and SID16 are equal.";
|
||||
public string OT_IDs0 { get; init; } = "TID16 and SID16 are 0.";
|
||||
public string OT_SID0 { get; init; } = "SID16 is zero.";
|
||||
public string OT_SID0Invalid { get; init; } = "SID16 should be 0.";
|
||||
public string OT_TID0 { get; init; } = "TID16 is zero.";
|
||||
public string OT_IDInvalid { get; init; } = "TID16 and SID16 combination is not possible.";
|
||||
|
||||
public string PIDEncryptWurmple { get; set; } = "Wurmple evolution Encryption Constant mismatch.";
|
||||
public string PIDEncryptZero { get; set; } = "Encryption Constant is not set.";
|
||||
public string PIDEqualsEC { get; set; } = "Encryption Constant matches PID.";
|
||||
public string PIDGenderMatch { get; set; } = "Gender matches PID.";
|
||||
public string PIDGenderMismatch { get; set; } = "PID-Gender mismatch.";
|
||||
public string PIDNatureMatch { get; set; } = "Nature matches PID.";
|
||||
public string PIDNatureMismatch { get; set; } = "PID-Nature mismatch.";
|
||||
public string PIDTypeMismatch { get; set; } = "PID+ correlation does not match what was expected for the Encounter's type.";
|
||||
public string PIDZero { get; set; } = "PID is not set.";
|
||||
public string PIDEncryptWurmple { get; init; } = "Wurmple evolution Encryption Constant mismatch.";
|
||||
public string PIDEncryptZero { get; init; } = "Encryption Constant is not set.";
|
||||
public string PIDEqualsEC { get; init; } = "Encryption Constant matches PID.";
|
||||
public string PIDGenderMatch { get; init; } = "Gender matches PID.";
|
||||
public string PIDGenderMismatch { get; init; } = "PID-Gender mismatch.";
|
||||
public string PIDNatureMatch { get; init; } = "Nature matches PID.";
|
||||
public string PIDNatureMismatch { get; init; } = "PID-Nature mismatch.";
|
||||
public string PIDTypeMismatch { get; init; } = "PID+ correlation does not match what was expected for the Encounter's type.";
|
||||
public string PIDZero { get; init; } = "PID is not set.";
|
||||
|
||||
public string PlusMoveAlphaMissing_0 { get; set; } = "Expected to have mastered the move {0} when encountered as an alpha.";
|
||||
public string PlusMoveMultipleInvalid { get; set; } = "Multiple Plus Move flags are invalid.";
|
||||
public string PlusMoveInvalid_0 { get; set; } = "{0} cannot be learned and set as a Plus Move.";
|
||||
public string PlusMoveSufficientLevelMissing_0 { get; set; } = "Plus Move flag for {0} must be set."; // as the Pokémon's current level is above the level it becomes available for use as a Plus Move.
|
||||
public string PlusMoveCountInvalid { get; set; } = "Out of range Plus Move flag index is set.";
|
||||
public string PlusMoveAlphaMissing_0 { get; init; } = "Expected to have mastered the move {0} when encountered as an alpha.";
|
||||
public string PlusMoveMultipleInvalid { get; init; } = "Multiple Plus Move flags are invalid.";
|
||||
public string PlusMoveInvalid_0 { get; init; } = "{0} cannot be learned and set as a Plus Move.";
|
||||
public string PlusMoveSufficientLevelMissing_0 { get; init; } = "Plus Move flag for {0} must be set."; // as the Pokémon's current level is above the level it becomes available for use as a Plus Move.
|
||||
public string PlusMoveCountInvalid { get; init; } = "Out of range Plus Move flag index is set.";
|
||||
|
||||
public string PokerusDaysTooHigh_0 { get; set; } = "Pokérus Days Remaining value is too high; expected <= {0}.";
|
||||
public string PokerusStrainUnobtainable_0 { get; set; } = "Pokérus Strain {0} cannot be obtained.";
|
||||
public string PokerusDaysTooHigh_0 { get; init; } = "Pokérus Days Remaining value is too high; expected <= {0}.";
|
||||
public string PokerusStrainUnobtainable_0 { get; init; } = "Pokérus Strain {0} cannot be obtained.";
|
||||
|
||||
public string RibbonAllValid { get; set; } = "All ribbons accounted for.";
|
||||
public string RibbonEgg { get; set; } = "Can't receive Ribbon(s) as an Egg.";
|
||||
public string RibbonsInvalid_0 { get; set; } = "Invalid Ribbons: {0}";
|
||||
public string RibbonsMissing_0 { get; set; } = "Missing Ribbons: {0}";
|
||||
public string RibbonMarkingInvalid_0 { get; set; } = "Invalid Marking: {0}";
|
||||
public string RibbonMarkingMissing_0 { get; set; } = "Missing Marking: {0}";
|
||||
public string RibbonMarkingAffixed_0 { get; set; } = "Invalid Affixed Ribbon/Marking: {0}";
|
||||
public string RibbonAllValid { get; init; } = "All ribbons accounted for.";
|
||||
public string RibbonEgg { get; init; } = "Can't receive Ribbon(s) as an Egg.";
|
||||
public string RibbonsInvalid_0 { get; init; } = "Invalid Ribbons: {0}";
|
||||
public string RibbonsMissing_0 { get; init; } = "Missing Ribbons: {0}";
|
||||
public string RibbonMarkingInvalid_0 { get; init; } = "Invalid Marking: {0}";
|
||||
public string RibbonMarkingMissing_0 { get; init; } = "Missing Marking: {0}";
|
||||
public string RibbonMarkingAffixed_0 { get; init; } = "Invalid Affixed Ribbon/Marking: {0}";
|
||||
|
||||
public string StatDynamaxInvalid { get; set; } = "Dynamax Level is not within the expected range.";
|
||||
public string StatIncorrectHeight { get; set; } = "Calculated Height does not match stored value.";
|
||||
public string StatIncorrectWeight { get; set; } = "Calculated Weight does not match stored value.";
|
||||
public string StatIncorrectHeightValue_0 { get; set; } = "Height should be {0}.";
|
||||
public string StatIncorrectWeightValue_0 { get; set; } = "Weight should be {0}.";
|
||||
public string StatIncorrectScaleValue_0 { get; set; } = "Scale should be {0}.";
|
||||
public string StatInvalidHeightWeight { get; set; } = "Height / Weight values are statistically improbable.";
|
||||
public string StatIncorrectCP { get; set; } = "Calculated CP does not match stored value.";
|
||||
public string StatGigantamaxInvalid { get; set; } = "Gigantamax Flag mismatch.";
|
||||
public string StatGigantamaxValid { get; set; } = "Gigantamax Flag was changed via Max Soup.";
|
||||
public string StatNatureInvalid { get; set; } = "Stat Nature is not within the expected range.";
|
||||
public string StatBattleVersionInvalid { get; set; } = "Battle Version is not within the expected range.";
|
||||
public string StatNobleInvalid { get; set; } = "Noble Flag mismatch.";
|
||||
public string StatAlphaInvalid { get; set; } = "Alpha Flag mismatch.";
|
||||
public string StatDynamaxInvalid { get; init; } = "Dynamax Level is not within the expected range.";
|
||||
public string StatIncorrectHeight { get; init; } = "Calculated Height does not match stored value.";
|
||||
public string StatIncorrectWeight { get; init; } = "Calculated Weight does not match stored value.";
|
||||
public string StatIncorrectHeightValue_0 { get; init; } = "Height should be {0}.";
|
||||
public string StatIncorrectWeightValue_0 { get; init; } = "Weight should be {0}.";
|
||||
public string StatIncorrectScaleValue_0 { get; init; } = "Scale should be {0}.";
|
||||
public string StatInvalidHeightWeight { get; init; } = "Height / Weight values are statistically improbable.";
|
||||
public string StatIncorrectCP { get; init; } = "Calculated CP does not match stored value.";
|
||||
public string StatGigantamaxInvalid { get; init; } = "Gigantamax Flag mismatch.";
|
||||
public string StatGigantamaxValid { get; init; } = "Gigantamax Flag was changed via Max Soup.";
|
||||
public string StatNatureInvalid { get; init; } = "Stat Nature is not within the expected range.";
|
||||
public string StatBattleVersionInvalid { get; init; } = "Battle Version is not within the expected range.";
|
||||
public string StatNobleInvalid { get; init; } = "Noble Flag mismatch.";
|
||||
public string StatAlphaInvalid { get; init; } = "Alpha Flag mismatch.";
|
||||
|
||||
public string StoredSourceEgg { get; set; } = "Egg must be in Box or Party.";
|
||||
public string StoredSlotSourceInvalid_0 { get; set; } = "Invalid Stored Source: {0}";
|
||||
public string StoredSourceEgg { get; init; } = "Egg must be in Box or Party.";
|
||||
public string StoredSlotSourceInvalid_0 { get; init; } = "Invalid Stored Source: {0}";
|
||||
|
||||
public string SuperComplete { get; set; } = "Super Training complete flag mismatch.";
|
||||
public string SuperDistro { get; set; } = "Distribution Super Training missions are not released.";
|
||||
public string SuperEgg { get; set; } = "Can't Super Train an Egg.";
|
||||
public string SuperNoComplete { get; set; } = "Can't have active Super Training complete flag for origins.";
|
||||
public string SuperNoUnlocked { get; set; } = "Can't have active Super Training unlocked flag for origins.";
|
||||
public string SuperUnavailable { get; set; } = "Super Training missions are not available in games visited.";
|
||||
public string SuperUnused { get; set; } = "Unused Super Training Flag is flagged.";
|
||||
public string G6SuperTrainEggBag { get; set; } = "Egg cannot use a Training Bag.";
|
||||
public string G6SuperTrainEggHits { get; set; } = "Eggs cannot hit Training Bags.";
|
||||
public string G6SuperTrainBagInvalid_0 { get; set; } = "Unrecognized Training Bag ID: {0}";
|
||||
public string G6SuperTrainBagHitsInvalid_012 { get; set; } = "Training bag cannot have {0} hits; expected value within [{1},{2}].";
|
||||
public string SuperComplete { get; init; } = "Super Training complete flag mismatch.";
|
||||
public string SuperDistro { get; init; } = "Distribution Super Training missions are not released.";
|
||||
public string SuperEgg { get; init; } = "Can't Super Train an Egg.";
|
||||
public string SuperNoComplete { get; init; } = "Can't have active Super Training complete flag for origins.";
|
||||
public string SuperNoUnlocked { get; init; } = "Can't have active Super Training unlocked flag for origins.";
|
||||
public string SuperUnavailable { get; init; } = "Super Training missions are not available in games visited.";
|
||||
public string SuperUnused { get; init; } = "Unused Super Training Flag is flagged.";
|
||||
public string G6SuperTrainEggBag { get; init; } = "Egg cannot use a Training Bag.";
|
||||
public string G6SuperTrainEggHits { get; init; } = "Eggs cannot hit Training Bags.";
|
||||
public string G6SuperTrainBagInvalid_0 { get; init; } = "Unrecognized Training Bag ID: {0}";
|
||||
public string G6SuperTrainBagHitsInvalid_012 { get; init; } = "Training bag cannot have {0} hits; expected value within [{1},{2}].";
|
||||
|
||||
public string TeraTypeIncorrect { get; set; } = "Tera Type does not match the expected value.";
|
||||
public string TeraTypeMismatch { get; set; } = "Tera Type does not match either of the default types.";
|
||||
public string TeraTypeIncorrect { get; init; } = "Tera Type does not match the expected value.";
|
||||
public string TeraTypeMismatch { get; init; } = "Tera Type does not match either of the default types.";
|
||||
|
||||
public string TradeNotAvailable { get; set; } = "Encounter cannot be traded to the active trainer.";
|
||||
public string TradeNotAvailable { get; init; } = "Encounter cannot be traded to the active trainer.";
|
||||
|
||||
public string TrainerIDNoSeed { get; set; } = "Trainer ID is not obtainable from any RNG seed.";
|
||||
public string TrainerIDNoSeed { get; init; } = "Trainer ID is not obtainable from any RNG seed.";
|
||||
|
||||
public string TransferBad { get; set; } = "Incorrectly transferred from previous generation.";
|
||||
public string TransferBad { get; init; } = "Incorrectly transferred from previous generation.";
|
||||
|
||||
public string TransferCurrentHandlerInvalid { get; set; } = "Invalid Current handler value, trainer details for save file expected another value.";
|
||||
public string TransferEgg { get; set; } = "Can't transfer Eggs between Generations.";
|
||||
public string TransferEggLocationTransporter { get; set; } = "Invalid Met Location, expected Poké Transfer.";
|
||||
public string TransferEggMetLevel { get; set; } = "Invalid Met Level for transfer.";
|
||||
public string TransferEggVersion { get; set; } = "Can't transfer Eggs to this game.";
|
||||
public string TransferFlagIllegal { get; set; } = "Flagged as illegal by the game (glitch abuse).";
|
||||
public string TransferHTFlagRequired { get; set; } = "Current handler cannot be the OT.";
|
||||
public string TransferHTMismatchName { get; set; } = "Handling trainer does not match the expected trainer name.";
|
||||
public string TransferHTMismatchGender { get; set; } = "Handling trainer does not match the expected trainer gender.";
|
||||
public string TransferHTMismatchLanguage { get; set; } = "Handling trainer does not match the expected trainer language.";
|
||||
public string TransferKoreanGen4 { get; set; } = "Korean Generation 4 games cannot interact with International Generation 4 games.";
|
||||
public string TransferMet { get; set; } = "Invalid Met Location, expected Poké Transfer or Crown.";
|
||||
public string TransferNotPossible { get; set; } = "Unable to transfer into current format from origin format.";
|
||||
public string TransferMetLocation { get; set; } = "Invalid Transfer Met Location.";
|
||||
public string TransferNature { get; set; } = "Invalid Nature for transfer Experience.";
|
||||
public string TransferObedienceLevel { get; set; } = "Invalid Obedience Level.";
|
||||
public string TransferPIDECBitFlip { get; set; } = "PID should be equal to EC [with top bit flipped]!";
|
||||
public string TransferPIDECEquals { get; set; } = "PID should be equal to EC!";
|
||||
public string TransferPIDECXor { get; set; } = "Encryption Constant matches shinyxored PID.";
|
||||
public string TransferTrackerMissing { get; set; } = "Pokémon HOME Transfer Tracker is missing.";
|
||||
public string TransferTrackerShouldBeZero { get; set; } = "Pokémon HOME Transfer Tracker should be 0.";
|
||||
public string TransferCurrentHandlerInvalid { get; init; } = "Invalid Current handler value, trainer details for save file expected another value.";
|
||||
public string TransferEgg { get; init; } = "Can't transfer Eggs between Generations.";
|
||||
public string TransferEggLocationTransporter { get; init; } = "Invalid Met Location, expected Poké Transfer.";
|
||||
public string TransferEggMetLevel { get; init; } = "Invalid Met Level for transfer.";
|
||||
public string TransferEggVersion { get; init; } = "Can't transfer Eggs to this game.";
|
||||
public string TransferFlagIllegal { get; init; } = "Flagged as illegal by the game (glitch abuse).";
|
||||
public string TransferHTFlagRequired { get; init; } = "Current handler cannot be the OT.";
|
||||
public string TransferHTMismatchName { get; init; } = "Handling trainer does not match the expected trainer name.";
|
||||
public string TransferHTMismatchGender { get; init; } = "Handling trainer does not match the expected trainer gender.";
|
||||
public string TransferHTMismatchLanguage { get; init; } = "Handling trainer does not match the expected trainer language.";
|
||||
public string TransferKoreanGen4 { get; init; } = "Korean Generation 4 games cannot interact with International Generation 4 games.";
|
||||
public string TransferMet { get; init; } = "Invalid Met Location, expected Poké Transfer or Crown.";
|
||||
public string TransferNotPossible { get; init; } = "Unable to transfer into current format from origin format.";
|
||||
public string TransferMetLocation { get; init; } = "Invalid Transfer Met Location.";
|
||||
public string TransferNature { get; init; } = "Invalid Nature for transfer Experience.";
|
||||
public string TransferObedienceLevel { get; init; } = "Invalid Obedience Level.";
|
||||
public string TransferPIDECBitFlip { get; init; } = "PID should be equal to EC [with top bit flipped]!";
|
||||
public string TransferPIDECEquals { get; init; } = "PID should be equal to EC!";
|
||||
public string TransferPIDECXor { get; init; } = "Encryption Constant matches shiny-xor'd PID.";
|
||||
public string TransferTrackerMissing { get; init; } = "Pokémon HOME Transfer Tracker is missing.";
|
||||
public string TransferTrackerShouldBeZero { get; init; } = "Pokémon HOME Transfer Tracker should be 0.";
|
||||
|
||||
public string TrashBytesExpected { get; set; } = "Expected Trash Bytes.";
|
||||
public string TrashBytesMismatchInitial { get; set; } = "Expected initial trash bytes to match the encounter.";
|
||||
public string TrashBytesMissingTerminatorFinal { get; set; } = "Final terminator missing.";
|
||||
public string TrashBytesShouldBeEmpty { get; set; } = "Trash Bytes should be cleared.";
|
||||
public string TrashBytesResetViaTransfer { get; set; } = "Trash Bytes were reset via transfer.";
|
||||
public string TrashBytesExpected { get; init; } = "Expected Trash Bytes.";
|
||||
public string TrashBytesMismatchInitial { get; init; } = "Expected initial trash bytes to match the encounter.";
|
||||
public string TrashBytesMissingTerminatorFinal { get; init; } = "Final terminator missing.";
|
||||
public string TrashBytesShouldBeEmpty { get; init; } = "Trash Bytes should be cleared.";
|
||||
public string TrashBytesResetViaTransfer { get; init; } = "Trash Bytes were reset via transfer.";
|
||||
|
||||
#endregion
|
||||
|
||||
public string EncTradeShouldHaveEvolvedToSpecies_0 { get; set; } = "Trade Encounter should have evolved to species: {0}.";
|
||||
public string EncGiftLanguageNotDistributed { get; set; } = "Gift Encounter was never distributed with this language.";
|
||||
public string EncGiftRegionNotDistributed { get; set; } = "Gift Encounter was never distributed to this Console Region.";
|
||||
public string FormInvalidRangeLEQ_0F { get; set; } = "Form Count is out of range. Expected <= {0}, got {1}.";
|
||||
public string MovesShouldMatchRelearnMoves { get; set; } = "Moves should exactly match Relearn Moves.";
|
||||
public string MemoryStatEnjoyment_0 { get; set; } = "Enjoyment should be {0}.";
|
||||
public string MemoryStatFullness_0 { get; set; } = "Fullness should be {0}.";
|
||||
public string MemoryStatFullnessLEQ_0 { get; set; } = "Fullness should be <= {0}.";
|
||||
public string OTLanguageShouldBe_0 { get; set; } = "Language ID should be {0}, not {1}.";
|
||||
public string OTLanguageShouldBe_0or1 { get; set; } = "Language ID should be {0} or {1}, not {2}.";
|
||||
public string OTLanguageShouldBeLeq_0 { get; set; } = "Language ID should be <= {0}, not {1}.";
|
||||
public string OTLanguageCannotPlayOnVersion_0 { get; set; } = "Language ID {0} cannot be played on this version.";
|
||||
public string OTLanguageCannotTransferToConsoleRegion_0 { get; set; } = "Language ID {0} cannot be transferred to this Console Region.";
|
||||
public string EncTradeShouldHaveEvolvedToSpecies_0 { get; init; } = "Trade Encounter should have evolved to species: {0}.";
|
||||
public string EncGiftLanguageNotDistributed { get; init; } = "Gift Encounter was never distributed with this language.";
|
||||
public string EncGiftRegionNotDistributed { get; init; } = "Gift Encounter was never distributed to this Console Region.";
|
||||
public string FormInvalidRangeLEQ_0F { get; init; } = "Form Count is out of range. Expected <= {0}, got {1}.";
|
||||
public string MovesShouldMatchRelearnMoves { get; init; } = "Moves should exactly match Relearn Moves.";
|
||||
public string MemoryStatEnjoyment_0 { get; init; } = "Enjoyment should be {0}.";
|
||||
public string MemoryStatFullness_0 { get; init; } = "Fullness should be {0}.";
|
||||
public string MemoryStatFullnessLEQ_0 { get; init; } = "Fullness should be <= {0}.";
|
||||
public string OTLanguageShouldBe_0 { get; init; } = "Language ID should be {0}, not {1}.";
|
||||
public string OTLanguageShouldBe_0or1 { get; init; } = "Language ID should be {0} or {1}, not {2}.";
|
||||
public string OTLanguageShouldBeLeq_0 { get; init; } = "Language ID should be <= {0}, not {1}.";
|
||||
public string OTLanguageCannotPlayOnVersion_0 { get; init; } = "Language ID {0} cannot be played on this version.";
|
||||
public string OTLanguageCannotTransferToConsoleRegion_0 { get; init; } = "Language ID {0} cannot be transferred to this Console Region.";
|
||||
|
||||
public string WordFilterInvalidCharacter_0 { get; set; } = "Word Filter: Invalid character '{0}' (0x{1}).";
|
||||
public string WordFilterFlaggedPattern_01 { get; set; } = "Word Filter ({1}): Flagged pattern '{0}'.";
|
||||
public string WordFilterTooManyNumbers_0 { get; set; } = "Word Filter: Too many numbers (>{0}).";
|
||||
public string BulkCloneDetectedDetails { get; set; } = "Clone detected (Details).";
|
||||
public string BulkCloneDetectedTracker { get; set; } = "Clone detected (Duplicate Tracker).";
|
||||
public string HintEvolvesToSpecies_0 { get; set; } = "Evolves to species: {0}.";
|
||||
public string HintEvolvesToRareForm_0 { get; set; } = "Evolves to rare form: {0}.";
|
||||
public string BulkSharingEncryptionConstantGenerationDifferent { get; set; } = "Detected sharing of Encryption Constant across generations.";
|
||||
public string BulkSharingEncryptionConstantGenerationSame { get; set; } = "Detected sharing of Encryption Constant.";
|
||||
public string BulkSharingEncryptionConstantRNGType { get; set; } = "Detected sharing of Encryption Constant sharing for different RNG encounters.";
|
||||
public string BulkSharingPIDGenerationDifferent { get; set; } = "Detected sharing of PID across generations.";
|
||||
public string BulkSharingPIDGenerationSame { get; set; } = "Detected sharing of PID.";
|
||||
public string BulkSharingPIDRNGType { get; set; } = "Detected sharing of PID for different RNG encounters.";
|
||||
public string BulkDuplicateMysteryGiftEggReceived { get; set; } = "Detected multiple redemptions of the same non-repeatable Mystery Gift Egg.";
|
||||
public string BulkSharingTrainerID { get; set; } = "Detected sharing of Trainer ID across multiple trainer names.";
|
||||
public string BulkSharingTrainerVersion { get; set; } = "Detected sharing of Trainer ID across multiple versions.";
|
||||
public string BulkDuplicateFusionSlot { get; set; } = "Detected multiple fusions of the same fusion stored slot species.";
|
||||
public string BulkHeldItemInventoryAssignedNoneHeld_0 { get; set; } = "{0} is marked as held player inventory, but no Pokémon found in slots checked.";
|
||||
public string BulkHeldItemInventoryMultipleSlots_0 { get; set; } = "{0} is a unique item and cannot be held by multiple Pokémon.";
|
||||
public string BulkHeldItemInventoryNotAcquired_0 { get; set; } = "{0} has not been acquired in player inventory.";
|
||||
public string BulkHeldItemInventoryUnassigned_0 { get; set; } = "{0} is not marked as assigned in player inventory.";
|
||||
public string BulkFusionSourceInvalid { get; set; } = "The subsumed Species-Form stored in the save file does not match the expected Species-Form of the fused slot.";
|
||||
public string WordFilterInvalidCharacter_0 { get; init; } = "Word Filter: Invalid character '{0}' (0x{1}).";
|
||||
public string WordFilterFlaggedPattern_01 { get; init; } = "Word Filter ({1}): Flagged pattern '{0}'.";
|
||||
public string WordFilterTooManyNumbers_0 { get; init; } = "Word Filter: Too many numbers (>{0}).";
|
||||
public string BulkCloneDetectedDetails { get; init; } = "Clone detected (Details).";
|
||||
public string BulkCloneDetectedTracker { get; init; } = "Clone detected (Duplicate Tracker).";
|
||||
public string HintEvolvesToSpecies_0 { get; init; } = "Evolves to species: {0}.";
|
||||
public string HintEvolvesToRareForm_0 { get; init; } = "Evolves to rare form: {0}.";
|
||||
public string BulkSharingEncryptionConstantGenerationDifferent { get; init; } = "Detected sharing of Encryption Constant across generations.";
|
||||
public string BulkSharingEncryptionConstantGenerationSame { get; init; } = "Detected sharing of Encryption Constant.";
|
||||
public string BulkSharingEncryptionConstantRNGType { get; init; } = "Detected sharing of Encryption Constant sharing for different RNG encounters.";
|
||||
public string BulkSharingPIDGenerationDifferent { get; init; } = "Detected sharing of PID across generations.";
|
||||
public string BulkSharingPIDGenerationSame { get; init; } = "Detected sharing of PID.";
|
||||
public string BulkSharingPIDRNGType { get; init; } = "Detected sharing of PID for different RNG encounters.";
|
||||
public string BulkDuplicateMysteryGiftEggReceived { get; init; } = "Detected multiple redemptions of the same non-repeatable Mystery Gift Egg.";
|
||||
public string BulkSharingTrainerID { get; init; } = "Detected sharing of Trainer ID across multiple trainer names.";
|
||||
public string BulkSharingTrainerVersion { get; init; } = "Detected sharing of Trainer ID across multiple versions.";
|
||||
public string BulkDuplicateFusionSlot { get; init; } = "Detected multiple fusions of the same fusion stored slot species.";
|
||||
public string BulkHeldItemInventoryAssignedNoneHeld_0 { get; init; } = "{0} is marked as held player inventory, but no Pokémon found in slots checked.";
|
||||
public string BulkHeldItemInventoryMultipleSlots_0 { get; init; } = "{0} is a unique item and cannot be held by multiple Pokémon.";
|
||||
public string BulkHeldItemInventoryNotAcquired_0 { get; init; } = "{0} has not been acquired in player inventory.";
|
||||
public string BulkHeldItemInventoryUnassigned_0 { get; init; } = "{0} is not marked as assigned in player inventory.";
|
||||
public string BulkFusionSourceInvalid { get; init; } = "The subsumed Species-Form stored in the save file does not match the expected Species-Form of the fused slot.";
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(LegalityCheckLocalization))]
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ public static class LegalityCheckResultCodeExtensions
|
|||
|
||||
// Evolution
|
||||
EvoInvalid => localization.EvoInvalid,
|
||||
EvoTradeReqOutsider_0 => localization.EvoTradeReqOutsider,
|
||||
EvoTradeReqOutsider_01 => localization.EvoTradeReqOutsider,
|
||||
EvoTradeRequired => localization.EvoTradeRequired,
|
||||
|
||||
// Form
|
||||
|
|
@ -144,16 +144,11 @@ public static class LegalityCheckResultCodeExtensions
|
|||
FormArgumentValid => localization.FormArgumentValid,
|
||||
FormArgumentInvalid => localization.FormArgumentInvalid,
|
||||
FormBattle => localization.FormBattle,
|
||||
FormEternal => localization.FormEternal,
|
||||
FormEternalInvalid => localization.FormEternalInvalid,
|
||||
FormInvalidGame => localization.FormInvalidGame,
|
||||
FormInvalidNature => localization.FormInvalidNature,
|
||||
FormItemMatches => localization.FormItem,
|
||||
FormItemInvalid => localization.FormItemInvalid,
|
||||
FormParty => localization.FormParty,
|
||||
FormPikachuCosplay => localization.FormPikachuCosplay,
|
||||
FormPikachuCosplayInvalid => localization.FormPikachuCosplayInvalid,
|
||||
FormPikachuEventInvalid => localization.FormPikachuEventInvalid,
|
||||
FormInvalidExpect_0 => localization.FormInvalidExpect_0,
|
||||
FormValid => localization.FormValid,
|
||||
FormVivillon => localization.FormVivillon,
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ private string GetMemory(CheckResult chk, string template, LegalityCheckResultCo
|
|||
EncTradeShouldHaveEvolvedToSpecies_0 => string.Format(format, GetSpeciesName(chk.Argument)),
|
||||
MoveEvoFCombination_0 => string.Format(format, GetSpeciesName(chk.Argument)),
|
||||
HintEvolvesToSpecies_0 => string.Format(format, GetSpeciesName(chk.Argument)),
|
||||
EvoTradeReqOutsider_01 => string.Format(format, GetSpeciesName(chk.Argument), GetSpeciesName(chk.Argument2)),
|
||||
|
||||
RibbonMarkingInvalid_0 => string.Format(format, GetRibbonName((RibbonIndex)chk.Argument)),
|
||||
RibbonMarkingMissing_0 => string.Format(format, GetRibbonName((RibbonIndex)chk.Argument)),
|
||||
|
|
|
|||
|
|
@ -68,14 +68,15 @@ public static void Generate(PK5 pk, in EncounterCriteria criteria, byte gr, ulon
|
|||
if (type is Shiny.Never)
|
||||
{
|
||||
if (ShinyUtil.GetIsShiny3(id32, pid))
|
||||
pid ^= 0x1000_0000; // force not shiny. the wild xor is never true.
|
||||
pid ^= 0x1000_0000; // force not shiny. the wild xor never undoes this fixing.
|
||||
}
|
||||
else if (type is Shiny.Always)
|
||||
{
|
||||
// inlined GetShinyPID without ability force (done later)
|
||||
var gval = (byte)pid;
|
||||
var trainerXor = (id32 >> 16) ^ (ushort)id32;
|
||||
pid = ((gval ^ trainerXor) << 16) | gval;
|
||||
// retain the random gender value, then ensure top16 bits yield a shiny.
|
||||
// this results in xor = 0.
|
||||
pid &= 0xFF;
|
||||
pid |= ShinyUtil.GetShinyXor(pid, id32) << 16;
|
||||
}
|
||||
|
||||
if (isSingleAbility)
|
||||
|
|
@ -89,8 +90,11 @@ public static void Generate(PK5 pk, in EncounterCriteria criteria, byte gr, ulon
|
|||
|
||||
if (wildXor)
|
||||
{
|
||||
// great job checking the wrong bits to give 2x shiny chance for wild stuff.
|
||||
var corr = (pid >> 31) ^ (pid & 1) ^ bitXor;
|
||||
// Attempt to nullify the top bit of the shiny xor?
|
||||
// It seems this was an attempt by the game developer to give 2x shiny chance for wild encounters...
|
||||
// Checks and flips bits inconsistently?
|
||||
// Possibly due to ability being moved to bit16 from bit0, thus shifting the anti-shiny to msb?
|
||||
var corr = (pid >> 31) ^ (pid & 1) ^ bitXor; // msb, lsb, lsb of tid^sid
|
||||
if (corr != 0)
|
||||
pid ^= 0x8000_0000;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,7 +193,12 @@ private static int GetIsMatchEnd(PKM pk, Xoroshiro128Plus xoro, int start = 0, i
|
|||
continue;
|
||||
var weight = copy.NextInt(0x81) + copy.NextInt(0x80);
|
||||
if (s.WeightScalar != weight)
|
||||
{
|
||||
if (height == 0 && s.WeightScalar == 0 && HomeQuirks.HasEnteredSetZeroScale(pk))
|
||||
{ } // OK
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
return iv_count;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,9 +137,14 @@ public static bool Verify(PKM pk, ulong seed, Span<int> ivs, in GenerateParam8 p
|
|||
if (s.HeightScalar != height)
|
||||
return false;
|
||||
if (s.WeightScalar != weight)
|
||||
{
|
||||
if (height == 0 && s.WeightScalar == 0 && HomeQuirks.HasEnteredSetZeroScale(pk))
|
||||
{ } // OK
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -291,8 +291,13 @@ public static bool TryApplyFromSeed(PA8 pk, in EncounterCriteria criteria, in Ov
|
|||
if (!isFixedH && s.HeightScalar != height)
|
||||
return false;
|
||||
if (!isFixedW && s.WeightScalar != weight)
|
||||
{
|
||||
if (height == 0 && s.WeightScalar == 0 && HomeQuirks.HasEnteredSetZeroScale(pk))
|
||||
{ } // OK
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,6 +139,8 @@ public static bool TryApplyFromSeed(PB8 pk, in EncounterCriteria criteria, Shiny
|
|||
// Remainder
|
||||
pk.HeightScalar = (byte)(xors.NextUInt(0x81) + xors.NextUInt(0x80));
|
||||
pk.WeightScalar = (byte)(xors.NextUInt(0x81) + xors.NextUInt(0x80));
|
||||
// Note: HOME can end up resetting Weight if Height is 0; however, the reversibility of PKM=>seed is not implemented/possible.
|
||||
// If this ever (impossible) happens, be sure to add that check similar to SW/SH's scale matching logic.
|
||||
|
||||
// Item, don't care
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -220,9 +220,17 @@ public static bool IsMatch(PKM pk, in GenerateParam9 enc, in ulong seed)
|
|||
if (enc.Weight == 0)
|
||||
{
|
||||
var value = (byte)(rand.NextInt(0x81) + rand.NextInt(0x80));
|
||||
if (pk is IScaledSize s && s.WeightScalar != value)
|
||||
if (pk is IScaledSize s)
|
||||
{
|
||||
var actual = s.WeightScalar;
|
||||
if (actual != value)
|
||||
{
|
||||
var isHomeZeroed = actual != 0 && s.HeightScalar == 0 && HomeQuirks.HasEnteredSetZeroScale(pk);
|
||||
if (!isHomeZeroed)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Scale
|
||||
{
|
||||
var value = enc.ScaleType.GetSizeValue(enc.Scale, ref rand);
|
||||
|
|
@ -242,18 +250,11 @@ public static bool IsMatch(PKM pk, in GenerateParam9 enc, in ulong seed)
|
|||
|
||||
public static bool IsHeightMatchSV(PKM pk, byte value)
|
||||
{
|
||||
// HOME copies Scale to Height. Untouched by HOME must match the value.
|
||||
// Viewing the save file in HOME will alter it too. Tracker definitely indicates it was viewed.
|
||||
if (pk is not (IScaledSize s2 and IScaledSize3 s3))
|
||||
return true;
|
||||
|
||||
// Viewed in HOME.
|
||||
if (s2.HeightScalar == s3.Scale)
|
||||
return true;
|
||||
if (pk is IHomeTrack { HasTracker: true })
|
||||
return false;
|
||||
|
||||
return s2.HeightScalar == value;
|
||||
return HomeQuirks.IsTouchedScaleCopiedOrUntouched(pk, value, s2, s3);
|
||||
}
|
||||
|
||||
private static uint GetAdaptedPID(ref Xoroshiro128Plus rand, PKM pk, in GenerateParam9 enc)
|
||||
|
|
|
|||
130
PKHeX.Core/Legality/Restrictions/HomeQuirks.cs
Normal file
130
PKHeX.Core/Legality/Restrictions/HomeQuirks.cs
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic to centralize some of the behaviors of HOME that cause changes to the data when you move it in, and how to determine if those changes have occurred.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <seealso cref="EncounterStatic8a.IsAlpha127"/>
|
||||
/// <seealso cref="WB7.IsHeightWeightFixed"/>
|
||||
/// <seealso cref="WC8.IsHOMEGift"/>
|
||||
/// <seealso cref="WA8.IsHOMEGift"/>
|
||||
/// </remarks>
|
||||
public static class HomeQuirks
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the entity has entered HOME after 3.0.0, which started setting the Alpha mark.
|
||||
/// </summary>
|
||||
/// <param name="pk">Entity to check.</param>
|
||||
/// <returns>True if the entity has the Alpha mark that indicates it was definitely ingested by HOME 3.0.0 or later, false otherwise.</returns>
|
||||
public static bool HasEnteredSetAlphaMark(PKM pk)
|
||||
{
|
||||
// Mark is only set by HOME ingesting the data for the first time.
|
||||
// Before HOME 3.0.0, this mark was never set.
|
||||
// Could be okay as a Gen8* format -- don't bother checking for "must have visited HOME 3.0.0+".
|
||||
if (pk.LA && pk is PK8 or PB8 or PA8)
|
||||
return false; // Could have been moved prior to the HOME 3.0.0 update.
|
||||
|
||||
// Before HOME 4.0.0, this mark was only set when you moved it in for the first time.
|
||||
// In HOME 4.0.0, the mark is set by HOME opening your save data and saving, modifying properties without you touching them.
|
||||
if (pk.ZA && pk is IScaledSize { HeightScalar: 0 }) // Alphas would update to 255-255-255 scale.
|
||||
return false; // Might not have touched HOME yet.
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool HasEnteredReachingZA(PA9 pk, EntityContext originalContext)
|
||||
{
|
||||
if (originalContext != EntityContext.Gen9a)
|
||||
return true;
|
||||
|
||||
var scale = pk.Scale;
|
||||
if (pk.HeightScalar == scale && pk.WeightScalar == scale)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool HasEnteredSetZeroScale(ushort species, byte form)
|
||||
{
|
||||
// Entering HOME 4.0.0 from a format that exposes IScaledSize3 with a Scale of 0 will clear Height and Weight to 0.
|
||||
// Originally noticed on entities with the Mini mark, but isn't required.
|
||||
|
||||
// Check if the species can enter any of the games with format inheriting IScaledSize3.
|
||||
if (PersonalTable.SV.IsPresentInGame(species, form))
|
||||
return true; // PK9
|
||||
if (PersonalTable.ZA.IsPresentInGame(species, form))
|
||||
return true; // PA9
|
||||
if (PersonalTable.LA.IsPresentInGame(species, form))
|
||||
return true; // PA8
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When HOME opens a game, it converts entities into <see cref="PKH"/> entities. When saved, it converts them back to their original format.
|
||||
/// During this process, it will copy the Scale value into the <see cref="IScaledSize.HeightScalar"/> value.
|
||||
/// If the entity has a HOME tracker, we can be sure that it was modified by HOME.
|
||||
/// </summary>
|
||||
/// <param name="pk"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="s2"></param>
|
||||
/// <param name="s3"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsTouchedScaleCopiedOrUntouched(PKM pk, byte value, IScaledSize s2, IScaledSize3 s3)
|
||||
{
|
||||
// HOME copies Scale to Height. Untouched by HOME must match the value.
|
||||
// Viewing the save file in HOME will alter it too. Tracker definitely indicates it was viewed.
|
||||
if (s2.HeightScalar == s3.Scale)
|
||||
return true;
|
||||
if (pk is IHomeTrack { HasTracker: true })
|
||||
return false;
|
||||
return s2.HeightScalar == value;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the <see cref="WC9"/> Hisuian Zoroark was erroneously updated by HOME to have 255-255 scale values.
|
||||
/// </summary>
|
||||
/// <param name="pk">Entity to check.</param>
|
||||
/// <param name="s">Scale accessor of the entity to check.</param>
|
||||
/// <param name="cardID">WC9 card ID of the entity to check.</param>
|
||||
/// <returns>>True if the entity has the scale values that indicate it was updated by HOME, false otherwise.</returns>
|
||||
public static bool IsGlitchedHisuianZoroarkSV(PKM pk, IScaledSize s, int cardID)
|
||||
{
|
||||
if (pk is IHomeTrack { HasTracker: false })
|
||||
return false;
|
||||
|
||||
if (cardID != 1513)
|
||||
return false;
|
||||
|
||||
// Hisiuan Zoroark was locked to 128 but could be bumped to 255 via HOME misapplying PLA's fix for Cavern alphas.
|
||||
// This is an OK case of mismatch.
|
||||
// This was fixed on HOME 4.0.0 update (2026, a few years after this event distribution ended), and samples could be deposited afterward (thus not requiring 255-all).
|
||||
|
||||
// Check for the forced-max scale values.
|
||||
if (s is { HeightScalar: 255, WeightScalar: 255 })
|
||||
{
|
||||
// if Scale is also present, should be 255.
|
||||
return pk is not IScaledSize3 { Scale: not 255 };
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool HasEnteredSetZeroScale(PKM pk)
|
||||
{
|
||||
// preconditions: height & weight are both 0; only call this method if 0-0 violates correlations AND we need to check if this activated.
|
||||
|
||||
if (pk is IHomeTrack { HasTracker: false })
|
||||
return false;
|
||||
|
||||
if (pk is IScaledSize3 { Scale: not 0 })
|
||||
return false;
|
||||
|
||||
// On HOME 4.0.0, entering a game with scale properties without having a scale assigned (on origin game):
|
||||
// To populate Scale, HOME copies the HeightScalar value. If it is 0, it destructively overwrites the WeightScalar to 0 as well, even if it was not 0 on the origin game.
|
||||
|
||||
// Ensure we are able to enter a game that can zero out the scale. If not, then we don't need to worry about this quirk.
|
||||
if (!HasEnteredSetZeroScale(pk.Species, pk.Form))
|
||||
return false;
|
||||
|
||||
// Quirk occurred.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ public static bool IsRequired(IEncounterTemplate enc, EntityContext current)
|
|||
WB8 { IsHOMEGift: true } => true,
|
||||
WA8 { IsHOMEGift: true } => true,
|
||||
WC9 { IsHOMEGift: true } => true,
|
||||
WA9 { IsHOMEGift: true } => true,
|
||||
_ => enc.Generation < 8,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,9 +20,8 @@ public static MemorySource GetPossibleSources(EvolutionHistory history)
|
|||
sources |= MemorySource.Bank; // Trade encounters from Gen7 also come with hardcoded memories.
|
||||
if (history.HasVisitedSWSH)
|
||||
sources |= MemorySource.Gen8;
|
||||
if (history.HasVisitedGen9)
|
||||
if (history.HasVisitedGen9 || history.HasVisitedZA)
|
||||
sources |= MemorySource.Deleted;
|
||||
// TODO HOME ZA
|
||||
return sources;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -138,16 +138,11 @@ public enum LegalityCheckResultCode : ushort
|
|||
FormArgumentValid,
|
||||
FormArgumentInvalid,
|
||||
FormBattle,
|
||||
FormEternal,
|
||||
FormEternalInvalid,
|
||||
FormInvalidGame,
|
||||
FormInvalidNature,
|
||||
FormItemMatches,
|
||||
FormItemInvalid,
|
||||
FormParty,
|
||||
FormPikachuCosplay,
|
||||
FormPikachuCosplayInvalid,
|
||||
FormPikachuEventInvalid,
|
||||
FormValid,
|
||||
FormVivillon,
|
||||
FormVivillonEventPre,
|
||||
|
|
@ -393,7 +388,6 @@ public enum LegalityCheckResultCode : ushort
|
|||
ContestSheenLEQ_0,
|
||||
EggFMetLevel_0,
|
||||
EffortUntrainedCap_0,
|
||||
EvoTradeReqOutsider_0,
|
||||
FormArgumentLEQ_0,
|
||||
FormArgumentGEQ_0,
|
||||
FormInvalidExpect_0,
|
||||
|
|
@ -479,6 +473,7 @@ public enum LegalityCheckResultCode : ushort
|
|||
EncTradeShouldHaveEvolvedToSpecies_0, // species
|
||||
MoveEvoFCombination_0, // species
|
||||
HintEvolvesToSpecies_0, // species
|
||||
EvoTradeReqOutsider_01, // species, species
|
||||
|
||||
RibbonMarkingInvalid_0, // ribbon
|
||||
RibbonMarkingMissing_0, // ribbon
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ public static bool IsFormChangeable(ushort species, byte oldForm, byte newForm,
|
|||
{
|
||||
EntityContext.Gen6 => false,
|
||||
EntityContext.Gen7 => newForm >= 2 || (oldForm == 1 && newForm == 0),
|
||||
EntityContext.Gen9a => newForm >= 2,
|
||||
EntityContext.Gen9a when origin is EntityContext.Gen9a => newForm >= 2,
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
|
|
@ -420,7 +420,7 @@ public static bool IsLordForm(ushort species, byte form, EntityContext context)
|
|||
/// <param name="pi">Game specific personal info</param>
|
||||
/// <param name="species"><see cref="Species"/> ID</param>
|
||||
/// <param name="format"><see cref="PKM.Form"/> ID</param>
|
||||
/// <returns>True if it has forms that can be provided by <see cref="FormConverter.GetFormList"/>, otherwise false for none.</returns>
|
||||
/// <returns>True if it has forms that can be provided by <see cref="FormConverter"/>, otherwise false for none.</returns>
|
||||
public static bool HasFormSelection(IPersonalFormInfo pi, ushort species, byte format)
|
||||
{
|
||||
if (format <= 3 && species != (int)Unown)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using static PKHeX.Core.LegalityCheckResultCode;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
|
@ -472,6 +473,8 @@ private CheckResult VerifyBirthAbility(LegalityAnalysis data, PA9 pa9)
|
|||
var index = bitNum >> 1;
|
||||
var species = pa9.Species;
|
||||
var ability = pa9.Ability;
|
||||
|
||||
// In-battle forms allow mismatching for the current form.
|
||||
if (FormInfo.HasMegaForm(species) || species is (int)Species.Aegislash)
|
||||
{
|
||||
var form = pa9.Form;
|
||||
|
|
@ -492,10 +495,62 @@ private CheckResult VerifyBirthAbility(LegalityAnalysis data, PA9 pa9)
|
|||
}
|
||||
}
|
||||
|
||||
// If deposited in HOME, it will realign to the deposited species-form ability.
|
||||
// If you transfer back into ZA then evolve it, you can disjoint it again.
|
||||
// If it hasn't evolved, then it must have been realigned by HOME.
|
||||
if (pa9 is IHomeTrack { HasTracker: true })
|
||||
{
|
||||
var form = pa9.Form;
|
||||
var actual = PersonalTable.ZA[species, form];
|
||||
var require = actual.GetAbilityAtIndex(index);
|
||||
if (ability == require)
|
||||
return VALID;
|
||||
if (enc.Species == species || IsAnyMoveSourceNotZA_Head(data.Info.Moves)) // Not evolved after; must match.
|
||||
return GetInvalid(AbilityMismatch);
|
||||
}
|
||||
|
||||
// Otherwise, we expect the form's personal info to match the original encounter's ability.
|
||||
var expect = pi.GetAbilityAtIndex(index);
|
||||
if (ability != expect)
|
||||
return GetInvalid(AbilityMismatch);
|
||||
|
||||
return VerifyFinalState(data, enc, index);
|
||||
}
|
||||
|
||||
private CheckResult VerifyFinalState(LegalityAnalysis data, IEncounterTemplate enc, int currentIndex)
|
||||
{
|
||||
if (currentIndex == 2) // Not normally obtainable. Has to visit another game where it can be switched.
|
||||
{
|
||||
if (AbilityChangeRules.IsAbilityPatchPossible(data.Info.EvoChainsAllGens))
|
||||
return GetValid(AbilityPatchUsed);
|
||||
return GetInvalid(AbilityHiddenUnavailable);
|
||||
}
|
||||
|
||||
if (!enc.Ability.IsSingleValue(out var bit) || bit == currentIndex) // Any/Unchanged
|
||||
return VALID;
|
||||
|
||||
var history = data.Info.EvoChainsAllGens;
|
||||
if (bit == 2 && AbilityChangeRules.IsAbilityPatchRevertPossible(history, currentIndex))
|
||||
return GetValid(AbilityPatchRevertUsed);
|
||||
if (bit != 2 && AbilityChangeRules.IsAbilityCapsuleAvailable(history))
|
||||
return GetValid(AbilityCapsuleUsed);
|
||||
|
||||
// Can't change to current state.
|
||||
return GetInvalid(AbilityMismatch);
|
||||
}
|
||||
|
||||
private static bool IsAnyMoveSourceNotZA_Head(ReadOnlySpan<MoveResult> infoMoves)
|
||||
{
|
||||
foreach (var info in infoMoves)
|
||||
{
|
||||
if (info.EvoStage != 0) // pre-evo
|
||||
continue;
|
||||
if (info.Context == EntityContext.Gen9a)
|
||||
continue;
|
||||
|
||||
// move verifier doesn't check for realignment lockout scenario; disable check.
|
||||
// return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,20 +31,8 @@ private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f)
|
|||
|
||||
return (Species)pk.Species switch
|
||||
{
|
||||
// Transfer Edge Cases -- Bank wipes the form but keeps old FormArgument value.
|
||||
Furfrou when pk is { Context: EntityContext.Gen7, Form: 0 } &&
|
||||
((enc.Generation == 6 && f.FormArgument <= byte.MaxValue) || IsFormArgumentDayCounterValid(f, 5, true))
|
||||
=> GetValid(FormArgumentValid),
|
||||
|
||||
Furfrou when pk.Form != 0 => !IsFormArgumentDayCounterValid(f, 5, true) ? GetInvalid(FormArgumentInvalid) : GetValid(FormArgumentValid),
|
||||
Hoopa when pk.Form == 1 => data.Info.EvoChainsAllGens switch
|
||||
{
|
||||
{ HasVisitedZA: true } when arg == 0 => GetValid(FormArgumentValid), // Value not applied on form change, and reset when reverted.
|
||||
{ HasVisitedGen9: true } when arg == 0 => GetValid(FormArgumentValid), // Value not applied on form change, and reset when reverted.
|
||||
{ HasVisitedGen6: true } when IsFormArgumentDayCounterValid(f, 3) => GetValid(FormArgumentValid), // 0-3 via OR/AS
|
||||
{ HasVisitedGen7: true } when IsFormArgumentDayCounterValid(f, 3) && f.FormArgumentRemain != 0 => GetValid(FormArgumentValid), // 1-3 via Gen7
|
||||
_ => GetInvalid(FormArgumentInvalid),
|
||||
},
|
||||
Furfrou => CheckFurfrou(pk, enc, f),
|
||||
Hoopa when pk.Form == 1 => CheckHoopa(data, f, arg),
|
||||
Yamask when pk.Form == 1 => arg switch
|
||||
{
|
||||
not 0 when pk.IsEgg => GetInvalid(FormArgumentNotAllowed),
|
||||
|
|
@ -74,22 +62,8 @@ private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f)
|
|||
> 9_999 => GetInvalid(FormArgumentLEQ_0, 9999),
|
||||
_ => arg == 0 || HasVisitedSV(data, Bisharp) ? GetValid(FormArgumentValid) : GetInvalid(FormArgumentNotAllowed),
|
||||
},
|
||||
Gimmighoul => arg switch
|
||||
{
|
||||
// Z-A evolutions do not set form argument to Gimmighoul.
|
||||
0 when data.Info.EvoChainsAllGens.HasVisitedZA => GetValid(FormArgumentValid),
|
||||
|
||||
// When leveled up, the game copies the save file's current coin count to the arg (clamped to <=999). If >=999, evolution is triggered.
|
||||
// Without being leveled up at least once, it cannot have a form arg value.
|
||||
>= 999 => GetInvalid(FormArgumentLEQ_0, 999),
|
||||
0 => GetValid(FormArgumentValid),
|
||||
|
||||
// S/V sets form argument to match coin count.
|
||||
_ when !data.Info.EvoChainsAllGens.HasVisitedGen9 => GetInvalid(FormArgumentInvalid),
|
||||
_ => pk.CurrentLevel != pk.MetLevel ? GetValid(FormArgumentValid) : GetInvalid(FormArgumentNotAllowed),
|
||||
},
|
||||
Gholdengo when !data.Info.EvoChainsAllGens.HasVisitedGen9 => arg == 0 ? GetValid(FormArgumentValid) : GetInvalid(FormArgumentInvalid),
|
||||
Gholdengo => VerifyFormArgumentRange(enc.Species, Gholdengo, arg, 999, 999),
|
||||
Gimmighoul => CheckGimmighoul(data.Info.EvoChainsAllGens, arg, pk),
|
||||
Gholdengo => CheckGholdengo(data.Info.EvoChainsAllGens, arg, enc, pk),
|
||||
|
||||
Runerigus => VerifyFormArgumentRange(enc.Species, Runerigus, arg, 49, 9999),
|
||||
Alcremie => VerifyFormArgumentRange(enc.Species, Alcremie, arg, 0, (ushort)AlcremieDecoration.Ribbon),
|
||||
|
|
@ -119,6 +93,179 @@ private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f)
|
|||
};
|
||||
}
|
||||
|
||||
private CheckResult CheckHoopa(LegalityAnalysis data, IFormArgument f, uint arg)
|
||||
{
|
||||
var history = data.Info.EvoChainsAllGens;
|
||||
if (arg == 0)
|
||||
{
|
||||
if (history.HasVisitedZA) // Value not applied on form change, and reset when reverted.
|
||||
return GetValid(FormArgumentValid);
|
||||
if (history.HasVisitedGen9) // Value not applied on form change, and reset when reverted.
|
||||
return GetValid(FormArgumentValid);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (history.HasVisitedGen7 && IsFormArgumentDayCounterValid(f, 3)) // 1-3 via Gen7
|
||||
return GetValid(FormArgumentValid);
|
||||
}
|
||||
|
||||
var pk = data.Entity;
|
||||
if (pk is PK6 pk6)
|
||||
{
|
||||
// 0-3 via OR/AS
|
||||
if (pk6.FormArgument != 0) // 0x3C not used (elapsed streak)
|
||||
return GetInvalid(FormArgumentNotAllowed);
|
||||
var elapsed = pk6.FormArgumentElapsed;
|
||||
var remain = pk6.FormArgumentRemain;
|
||||
var sum = elapsed + remain;
|
||||
if (sum != 3)
|
||||
return GetInvalid(FormArgumentInvalid);
|
||||
return GetValid(FormArgumentValid);
|
||||
}
|
||||
|
||||
return GetInvalid(FormArgumentInvalid);
|
||||
}
|
||||
|
||||
private CheckResult CheckFurfrou(PKM pk, IEncounterTemplate enc, IFormArgument f)
|
||||
{
|
||||
// Transfer Edge Cases -- Bank wipes the form but keeps old FormArgument value.
|
||||
// Gen6: Reverts when deposited.
|
||||
// Gen7: Reverts form & arg when withdrawn, reverts form (NOT arg) when deposited in Bank.
|
||||
// Gen9a: Doesn't decrease, always 5.
|
||||
if (pk is PK6 pk6)
|
||||
return CheckFurfrou6(pk6);
|
||||
if (pk is PK7 pk7)
|
||||
return CheckFurfrou7(pk7, enc);
|
||||
if (pk is { GO_HOME: true } && f.FormArgument == 0)
|
||||
return GetValid(FormArgumentValid); // GO transfers forget to set Form Argument.
|
||||
if (pk is PA9 pa9)
|
||||
return CheckFurfrou9a(pa9, enc);
|
||||
|
||||
// Only legal pathways are via methods above.
|
||||
return GetInvalid(FormArgumentInvalid);
|
||||
}
|
||||
|
||||
private CheckResult CheckFurfrou9a(PA9 pk, IEncounterTemplate enc)
|
||||
{
|
||||
// Gen6=>Gen7 transfer edge case: Form argument is not cleared when depositing in Bank, but form is.
|
||||
if (enc.Generation == 6 && pk is { Form: 0, FormArgument: <= byte.MaxValue })
|
||||
return GetValid(FormArgumentValid);
|
||||
|
||||
// Z-A trims set to 5.
|
||||
if ((pk.FormArgument == 5) == (pk.Form != 0))
|
||||
return GetValid(FormArgumentValid);
|
||||
|
||||
// Bank=>HOME with form 0 forgets to wipe, but Form is reverted.
|
||||
if (pk.Form == 0 && enc.Generation <= 7 && IsFormArgumentDayCounterValid(pk, 5, true))
|
||||
return GetValid(FormArgumentValid);
|
||||
|
||||
return GetInvalid(FormArgumentInvalid);
|
||||
}
|
||||
|
||||
private CheckResult CheckFurfrou7(PK7 pk, IEncounterTemplate enc)
|
||||
{
|
||||
// Gen6=>Gen7 transfer edge case: Form argument is not cleared when depositing in Bank, but form is.
|
||||
if (enc.Generation == 6 && pk is { Form: 0, FormArgument: <= byte.MaxValue })
|
||||
return GetValid(FormArgumentValid);
|
||||
|
||||
if (pk is { Form: 0, FormArgument: 0 })
|
||||
return GetValid(FormArgumentValid);
|
||||
|
||||
// Depositing into box no longer clears form; they only wipe it when you withdraw.
|
||||
// Storing in Bank will revert the form, so any form is valid as long as the day counter values are valid for any trim.
|
||||
if (!IsFormArgumentDayCounterValid(pk, 5, true))
|
||||
return GetInvalid(FormArgumentInvalid);
|
||||
|
||||
return GetValid(FormArgumentValid);
|
||||
}
|
||||
|
||||
private CheckResult CheckFurfrou6(PK6 pk)
|
||||
{
|
||||
// Can only exist in party.
|
||||
// 0x3C: Current streak
|
||||
// 0xED: Remaining days
|
||||
// 0xEE: Elapsed days (same as current streak)
|
||||
var arg = pk.FormArgument;
|
||||
// Argument can be anything; depositing drops the form and party stats and forgets to clear the arg.
|
||||
if (arg > byte.MaxValue)
|
||||
return GetInvalid(FormArgumentLEQ_0, byte.MaxValue);
|
||||
|
||||
// Form can only exist inside party. Checked elsewhere.
|
||||
var remain = pk.FormArgumentRemain;
|
||||
var elapsed = pk.FormArgumentElapsed;
|
||||
if (pk.Form != 0)
|
||||
{
|
||||
var sum = remain + elapsed;
|
||||
if (sum < 5)
|
||||
return GetInvalid(FormArgumentInvalid);
|
||||
if (elapsed != arg)
|
||||
return GetInvalid(FormArgumentInvalid);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Party stat values must be zero.
|
||||
if (remain != 0 || elapsed != 0)
|
||||
return GetInvalid(FormArgumentNotAllowed);
|
||||
}
|
||||
|
||||
return GetValid(FormArgumentValid);
|
||||
}
|
||||
|
||||
private CheckResult CheckGimmighoul(EvolutionHistory history, uint arg, PKM pk)
|
||||
{
|
||||
if (arg == 0)
|
||||
return GetValid(FormArgumentValid);
|
||||
|
||||
// The only game we can assign a form argument value is in S/V.
|
||||
// Z-A evolutions do not set form argument to Gimmighoul.
|
||||
if (history.HasVisitedGen9)
|
||||
{
|
||||
// When leveled up, the game copies the save file's current coin count to the arg (clamped to <=999). If >=999, evolution is triggered (can cancel).
|
||||
// Without being leveled up at least once, it cannot have a form arg value.
|
||||
if (arg > 999)
|
||||
return GetInvalid(FormArgumentLEQ_0, 999);
|
||||
if (pk.CurrentLevel == pk.MetLevel)
|
||||
return GetInvalid(FormArgumentNotAllowed);
|
||||
|
||||
return GetValid(FormArgumentValid);
|
||||
}
|
||||
|
||||
return GetInvalid(FormArgumentNotAllowed);
|
||||
}
|
||||
|
||||
private CheckResult CheckGholdengo(EvolutionHistory history, uint arg, IEncounterable enc, PKM pk)
|
||||
{
|
||||
if (enc.Species == (ushort)Gholdengo)
|
||||
{
|
||||
if (arg == 0)
|
||||
return GetValid(FormArgumentValid);
|
||||
return GetInvalid(FormArgumentNotAllowed);
|
||||
}
|
||||
|
||||
// Gimmighoul evolved.
|
||||
// The only game we can assign a form argument value is in S/V.
|
||||
// Z-A evolutions do not set form argument to Gimmighoul.
|
||||
var hasVisitedNoArgGame = history.HasVisitedZA;
|
||||
if (hasVisitedNoArgGame && arg == 0)
|
||||
return GetValid(FormArgumentValid);
|
||||
|
||||
if (history.HasVisitedGen9)
|
||||
{
|
||||
// When leveled up, the game copies the save file's current coin count to the arg (clamped to <=999). If >=999, evolution is triggered (can cancel).
|
||||
// Without being leveled up at least once, it cannot have a form arg value.
|
||||
if (arg > 999)
|
||||
return GetInvalid(FormArgumentLEQ_0, 999);
|
||||
if (pk.CurrentLevel == pk.MetLevel)
|
||||
return GetInvalid(FormArgumentNotAllowed);
|
||||
|
||||
if (!hasVisitedNoArgGame && arg != 999) // Evolving without visiting a less-restricted game requires 999.
|
||||
return GetInvalid(FormArgumentGEQ_0, 999);
|
||||
return GetValid(FormArgumentValid);
|
||||
}
|
||||
|
||||
return GetInvalid(FormArgumentNotAllowed);
|
||||
}
|
||||
|
||||
private CheckResult CheckPrimeape(LegalityAnalysis data, PKM pk, uint arg, IEncounterTemplate enc)
|
||||
{
|
||||
if (arg == 0)
|
||||
|
|
|
|||
|
|
@ -43,15 +43,7 @@ private CheckResult VerifyForm(LegalityAnalysis data)
|
|||
switch ((Species)species)
|
||||
{
|
||||
case Pikachu when enc.Generation == 6: // Cosplay
|
||||
if (enc is not EncounterStatic6 s6)
|
||||
{
|
||||
if (form == 0)
|
||||
break; // Regular Pikachu, OK.
|
||||
return GetInvalid(FormPikachuCosplay);
|
||||
}
|
||||
if (form != s6.Form)
|
||||
return GetInvalid(FormPikachuCosplayInvalid);
|
||||
if (pk.Format != 6)
|
||||
if (form != 0 && pk.Format != 6) // Regular Pikachu, OK.
|
||||
return GetInvalid(TransferBad); // Can't transfer.
|
||||
break;
|
||||
|
||||
|
|
@ -60,16 +52,6 @@ private CheckResult VerifyForm(LegalityAnalysis data)
|
|||
case Eevee when form is not 0 && ParseSettings.ActiveTrainer is SAV7b {Version:GameVersion.GP}:
|
||||
return GetInvalid(FormBattle);
|
||||
|
||||
case Pikachu when enc.Generation >= 7: // Cap
|
||||
var expectForm = enc is EncounterInvalid or IEncounterEgg ? 0 : enc.Form;
|
||||
if (form != expectForm)
|
||||
{
|
||||
bool gift = enc is MysteryGift g && g.Form != form;
|
||||
var msg = gift ? FormPikachuEventInvalid : FormInvalidGame;
|
||||
return GetInvalid(msg);
|
||||
}
|
||||
break;
|
||||
|
||||
case Unown when enc.Generation == 2 && form >= 26:
|
||||
return GetInvalid(FormInvalidRangeLEQ_0F, 25);
|
||||
case Unown when enc.Generation == 3:
|
||||
|
|
@ -101,12 +83,9 @@ private CheckResult VerifyForm(LegalityAnalysis data)
|
|||
case Genesect:
|
||||
var genesect = FormItem.GetFormGenesect(pk.HeldItem);
|
||||
return genesect != form ? GetInvalid(FormItemInvalid) : GetValid(FormItemMatches);
|
||||
case Greninja:
|
||||
if (form > 1) // Ash Battle Bond active
|
||||
return GetInvalid(FormBattle);
|
||||
if (form != 0 && enc is not MysteryGift) // Form can not be bred for, MysteryGift already checked
|
||||
return GetInvalid(FormInvalidRangeLEQ_0F, 0);
|
||||
break;
|
||||
|
||||
case Furfrou when pk.Context == EntityContext.Gen6 && form != 0 && !data.IsStoredSlot(StorageSlotType.Party):
|
||||
return GetInvalid(FormParty);
|
||||
|
||||
case Scatterbug or Spewpa or Vivillon when enc.Context is EntityContext.Gen9:
|
||||
if (form > 18 && enc.Form != form) // Pokéball
|
||||
|
|
@ -139,10 +118,6 @@ private CheckResult VerifyForm(LegalityAnalysis data)
|
|||
data.AddLine(Get(Severity.Fishy, FormVivillonNonNative));
|
||||
break;
|
||||
|
||||
case Floette when form == 5: // Eternal Flower Floette - not released until Pokémon Legends: Z-A
|
||||
if (enc is not EncounterGift9a)
|
||||
return GetInvalid(FormEternalInvalid);
|
||||
return GetValid(FormEternal);
|
||||
case Meowstic when (form & 1) != pk.Gender:
|
||||
return GetInvalid(GenderInvalidNone);
|
||||
|
||||
|
|
|
|||
|
|
@ -299,8 +299,10 @@ public static bool GetCanOTHandle(IEncounterTemplate enc, PKM pk, byte generatio
|
|||
WB8 wb8 when wb8.GetHasOT(pk.Language) => false,
|
||||
WA8 wa8 when wa8.GetHasOT(pk.Language) => false,
|
||||
WC9 wc9 when wc9.GetHasOT(pk.Language) => false,
|
||||
WA9 wa9 when wa9.GetHasOT(pk.Language) => false,
|
||||
WC8 {IsHOMEGift: true} => false,
|
||||
WC9 {IsHOMEGift: true} => false,
|
||||
WA9 {IsHOMEGift: true} => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -31,10 +31,9 @@ private void CheckLearnset(LegalityAnalysis data, PA9 pa)
|
|||
if (moveCount == 4)
|
||||
return;
|
||||
|
||||
// TODO ZA HOME
|
||||
// // Flag move slots that are empty.
|
||||
// if (pa.Tracker != 0 || !ParseSettings.IgnoreTransferIfNoTracker)
|
||||
// return; // Can delete moves in PA9 moveset via HOME.
|
||||
// Flag move slots that are empty.
|
||||
if (pa is IHomeTrack { HasTracker: true } || !ParseSettings.IgnoreTransferIfNoTracker)
|
||||
return; // Can delete moves in PA9 moveset via HOME.
|
||||
|
||||
if (e9a.Species is (int)Rotom && moveCount == 3 && pa.Form == 0)
|
||||
{
|
||||
|
|
@ -216,7 +215,8 @@ private void CheckFlagsPlus(LegalityAnalysis la, PA9 pk)
|
|||
// Trade evolutions forget to set the Plus flags, unlike triggered evolutions.
|
||||
// If the move is not present as a previous-evolution learnset move, and the head species is a Trade evo, skip the error.
|
||||
// Assume the best case -- evolved at current level, so none would get set.
|
||||
if (IsTradeEvoSkip(la.Info.EvoChainsAllGens.Gen9a, move))
|
||||
var evos = la.Info.EvoChainsAllGens;
|
||||
if (IsTradeEvoSkip(evos.Gen9a, move))
|
||||
continue;
|
||||
|
||||
if (WasPossiblyObtainedBeforeDLC(pk, la.EncounterMatch) && IsPermittedUnsetPlusMove((Species)pk.Species, (Move)move))
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ public void VerifyG1(LegalityAnalysis data)
|
|||
{
|
||||
// Pokémon has been traded illegally between games without evolving.
|
||||
// Trade evolution species IDs for Gen1 are sequential dex numbers.
|
||||
data.AddLine(GetInvalid(EvoTradeReqOutsider_0, enc.Species + 1u));
|
||||
data.AddLine(GetInvalid(EvoTradeReqOutsider_01, enc.Species, (ushort)(enc.Species + 1u)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,8 @@ private void VerifyAffixedRibbonMark(LegalityAnalysis data, IRibbonIndex m)
|
|||
return;
|
||||
|
||||
var affix = (RibbonIndex)affixValue;
|
||||
var max = MarkRules.GetMaxAffixValue(data.Info.EvoChainsAllGens);
|
||||
var evos = data.Info.EvoChainsAllGens;
|
||||
var max = MarkRules.GetMaxAffixValue(evos);
|
||||
if ((sbyte)max == -1 || affix > max)
|
||||
{
|
||||
data.AddLine(GetInvalid(RibbonMarkingAffixed_0, (ushort)affix));
|
||||
|
|
@ -88,11 +89,19 @@ private void VerifyAffixedRibbonMark(LegalityAnalysis data, IRibbonIndex m)
|
|||
if (m is not PKM pk)
|
||||
return;
|
||||
|
||||
if (MarkRules.IsEncounterMarkLost(data.EncounterOriginal, data.Entity))
|
||||
var enc = data.EncounterOriginal;
|
||||
if (MarkRules.IsEncounterMarkLost(enc, data.Entity))
|
||||
{
|
||||
VerifyShedinjaAffixed(data, affix, pk, m);
|
||||
return;
|
||||
}
|
||||
|
||||
// Some games cannot affix ribbon unless it transfers to a game that can affix it.
|
||||
if (enc.Context is EntityContext.Gen8a or EntityContext.Gen9a && pk.Context == enc.Context)
|
||||
{
|
||||
if (!evos.HasVisitedExcept(enc.Context))
|
||||
data.AddLine(GetInvalid(RibbonMarkingAffixed_0, (ushort)affix));
|
||||
}
|
||||
EnsureHasRibbon(data, m, affix);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,8 @@ private void VerifyHTLanguage(LegalityAnalysis data, MemorySource source)
|
|||
private static bool GetIsHTLanguageValid(IEncounterTemplate enc, PKM pk, byte language, MemorySource source)
|
||||
{
|
||||
// Bounds check.
|
||||
if (language > (int)LanguageID.ChineseT)
|
||||
var max = Legal.GetMaxLanguageID(pk.Format, pk.Context);
|
||||
if (language > max)
|
||||
return false;
|
||||
|
||||
// Gen6 and Bank don't have the HT language flag.
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ private void VerifyRandomCorrelationSwitch(LegalityAnalysis data, PKM pk, IEncou
|
|||
{
|
||||
VerifyCorrelation8b(data, s8b, pk);
|
||||
}
|
||||
else if (enc is ISeedCorrelation64<PKM> s64)
|
||||
else if (enc is ISeedCorrelation64<PKM> s64 && !(enc is WA9 { IsHOMEGift: true }))
|
||||
{
|
||||
var pidiv = s64.TryGetSeed(pk, out var seed);
|
||||
if (pidiv == SeedCorrelationResult.Success)
|
||||
|
|
|
|||
|
|
@ -29,11 +29,14 @@ private static void VerifyHeightWeight(LegalityAnalysis data, PKM pk, IEncounter
|
|||
}
|
||||
else if (enc.Context is EntityContext.Gen9a)
|
||||
{
|
||||
// TODO HOME ZA
|
||||
if (s2.HeightScalar != 0)
|
||||
data.AddLine(GetInvalid(Encounter, StatIncorrectHeightValue_0, 0));
|
||||
if (s2.WeightScalar != 0)
|
||||
data.AddLine(GetInvalid(Encounter, StatIncorrectWeightValue_0, 0));
|
||||
// By default, Gen9a does not apply height/weight properties, so 0-0 is expected.
|
||||
// If touched by HOME, Scale is copied to both Height and Weight properties.
|
||||
// Thus, only n-n is valid. Scale we'll check separately if the current object's format supports it.
|
||||
var height = s2.HeightScalar;
|
||||
var weight = s2.WeightScalar;
|
||||
|
||||
if (height != weight)
|
||||
data.AddLine(GetInvalid(Encounter, StatIncorrectWeightValue_0, height));
|
||||
}
|
||||
else if (CheckHeightWeightOdds(enc))
|
||||
{
|
||||
|
|
@ -50,7 +53,7 @@ private void VerifyScale(LegalityAnalysis data, PKM pk, IEncounterTemplate enc,
|
|||
// PLA static Alphas have potential for 127 scale; this is already checked explicitly in the matching check.
|
||||
// Ensure all Alphas have 255 scale.
|
||||
// Otherwise, ensure scale matches height scalar if required.
|
||||
if (enc is IAlphaReadOnly { IsAlpha: true })
|
||||
if (enc is { Context: EntityContext.Gen8a } and IAlphaReadOnly { IsAlpha: true })
|
||||
{
|
||||
byte expect = enc switch
|
||||
{
|
||||
|
|
@ -59,6 +62,27 @@ private void VerifyScale(LegalityAnalysis data, PKM pk, IEncounterTemplate enc,
|
|||
};
|
||||
if (s3.Scale != expect)
|
||||
data.AddLine(GetInvalid(StatIncorrectScaleValue_0, expect));
|
||||
|
||||
return;
|
||||
}
|
||||
if (enc is { Context: EntityContext.Gen9a })
|
||||
{
|
||||
// Height != Weight already checked.
|
||||
var scale = s3.Scale;
|
||||
var height = s2.HeightScalar;
|
||||
if (scale == 0)
|
||||
{
|
||||
// If scale is 0, then the only legal value for height/weight is also 0.
|
||||
if (height != 0)
|
||||
data.AddLine(GetInvalid(StatIncorrectHeightValue_0, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If scale is nonzero, then height/weight must be 0 or equal to scale.
|
||||
// If it was definitely touched by HOME, then they can only be equal to scale, since HOME copies scale to height/weight.
|
||||
if ((height != 0 || IsHeightScaleMatchRequired(pk)) && height != scale)
|
||||
data.AddLine(GetInvalid(StatIncorrectHeightValue_0, height));
|
||||
}
|
||||
}
|
||||
else if (IsHeightScaleMatchRequired(pk) && s2.HeightScalar != s3.Scale)
|
||||
{
|
||||
|
|
@ -72,8 +96,6 @@ private static bool CheckHeightWeightOdds(IEncounterTemplate enc)
|
|||
{
|
||||
if (enc.Generation < 8)
|
||||
return false;
|
||||
if (enc.Context is EntityContext.Gen9a) // TODO HOME ZA
|
||||
return true;
|
||||
if (enc is WC8 { IsHOMEGift: true })
|
||||
return false;
|
||||
if (enc is WC9) // fixed values (usually 0 or 128)
|
||||
|
|
|
|||
|
|
@ -123,7 +123,8 @@ private static void VerifyTrashINT(LegalityAnalysis data, PK3 pk)
|
|||
// OT name from save file is copied byte-for-byte. All 8 bytes are initialized to FF on new game.
|
||||
if (!TrashByteRules3.IsTerminatedFFZero(trash, 7))
|
||||
{
|
||||
if (!TrashByteRules3.IsTrashPatternDefaultTrainer(trash, pk.Version, (LanguageID)pk.Language))
|
||||
var isDefaultVersion = IsDefaultTrainer(data, pk, trash);
|
||||
if (isDefaultVersion == NoDefaultTrainerNameMatch)
|
||||
data.AddLine(GetInvalid(Trainer, TrashBytesMissingTerminatorFinal));
|
||||
}
|
||||
// Nickname can be all FF's (nicknamed) or whatever random garbage is in the buffer before filling. Unsure if we can reliably check this, but it should be "dirty" usually.
|
||||
|
|
@ -131,18 +132,118 @@ private static void VerifyTrashINT(LegalityAnalysis data, PK3 pk)
|
|||
FlagIsNicknameClean(data, pk);
|
||||
}
|
||||
|
||||
private const GameVersion NoDefaultTrainerNameMatch = 0;
|
||||
|
||||
private static GameVersion IsDefaultTrainer(LegalityAnalysis data, PK3 pk, Span<byte> trash)
|
||||
{
|
||||
var language = (LanguageID)pk.Language;
|
||||
// Only non-Japanese are affected by trash patterns, so if Japanese, no match.
|
||||
if (language == LanguageID.Japanese)
|
||||
return NoDefaultTrainerNameMatch;
|
||||
|
||||
var version = pk.Version;
|
||||
// FR/LG and others are not impacted.
|
||||
if (version is not (GameVersion.R or GameVersion.S or GameVersion.E))
|
||||
return NoDefaultTrainerNameMatch;
|
||||
|
||||
// Check if it matches any of the default trainer name patterns for the original version and language.
|
||||
// If it does, then it's valid as a default trainer name with expected trash.
|
||||
var gender = pk.OriginalTrainerGender;
|
||||
var location = pk.MetLocation;
|
||||
var eggEncounter = data.EncounterOriginal.IsEgg;
|
||||
var isEgg = pk.IsEgg;
|
||||
if (TrashByteRules3.IsTrashPatternDefaultTrainer(trash, version, language, gender))
|
||||
{
|
||||
if (!eggEncounter || isEgg || EggHatchLocation3.IsValidMet3(location, version))
|
||||
return version;
|
||||
}
|
||||
|
||||
// If it was a traded egg, possibly?
|
||||
// Unhatched eggs must match the original version, already checked above.
|
||||
// Non-eggs cannot change OT name. Must be hatched to change the OT name.
|
||||
if (!eggEncounter || isEgg)
|
||||
return NoDefaultTrainerNameMatch;
|
||||
|
||||
// Check the other versions.
|
||||
for (var hatchVersion = GameVersion.S; hatchVersion <= GameVersion.E; hatchVersion++)
|
||||
{
|
||||
if (hatchVersion == version)
|
||||
continue; // already checked.
|
||||
if (!TrashByteRules3.IsTrashPatternDefaultTrainer(trash, hatchVersion, language, gender))
|
||||
continue; // doesn't match this version's pattern, skip.
|
||||
|
||||
// Ensure it can be hatched at a valid location for this version. Probably don't need to do this...
|
||||
if (EggHatchLocation3.IsValidMet3(location, hatchVersion))
|
||||
return hatchVersion;
|
||||
}
|
||||
|
||||
// None match.
|
||||
return NoDefaultTrainerNameMatch;
|
||||
}
|
||||
|
||||
private static void FlagIsNicknameClean(LegalityAnalysis data, PK3 pk)
|
||||
{
|
||||
if (!pk.IsNicknamed || pk.IsEgg)
|
||||
if (pk.IsEgg)
|
||||
return;
|
||||
|
||||
if (IsNicknamedByPlayer(data, pk))
|
||||
{
|
||||
// Japanese only fills the first 5+1 bytes; everything else is trash.
|
||||
// International games are 10 chars (full buffer) max; implicit terminator if full.
|
||||
var nick = pk.GetNicknamePrefillRegion();
|
||||
if (!TrashByteRules3.IsTerminatedFF(nick))
|
||||
if (TrashByteRules3.IsTerminatedFF(nick))
|
||||
return; // Matches the manually nicknamed pattern.
|
||||
|
||||
if (!TrashByteRules3.IsTerminatedFFZero(nick)) // Wasn't cleared by transferring between C/XD.
|
||||
data.AddLine(GetInvalid(Trainer, TrashBytesMismatchInitial));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsNicknamedByPlayer(LegalityAnalysis data, PK3 pk)
|
||||
{
|
||||
Span<char> name = stackalloc char[10];
|
||||
var len = pk.LoadString(pk.NicknameTrash, name);
|
||||
name = name[..len];
|
||||
|
||||
if (!SpeciesName.IsNicknamed(pk.Species, name, pk.Language, 3))
|
||||
return false;
|
||||
|
||||
// Okay, we aren't matching the expected name.
|
||||
// Only way to do that is by manually nicknaming OR evolving in another language game.
|
||||
// Check for the language evolution.
|
||||
|
||||
// Each game only contains the strings for its own localization, so it won't know of others.
|
||||
// The game evaluates "is nicknamed" based on the text not matching its species name.
|
||||
// Trade to another language then evolve: will treat it like a nickname, without actually filling with FF.
|
||||
if (IsNicknameLanguageEvolution(data, name))
|
||||
return false;
|
||||
|
||||
// No other explanation; must be manually nicknamed.
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsNicknameLanguageEvolution(LegalityAnalysis data, ReadOnlySpan<char> name)
|
||||
{
|
||||
var chain = data.Info.EvoChainsAllGens.Gen3;
|
||||
if (chain.Length == 1)
|
||||
{
|
||||
// Hasn't evolved.
|
||||
data.AddLine(GetInvalid(Trainer, TrashBytesMismatchInitial));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the nickname matches any pre-evolution on any language.
|
||||
// Skip head (current species), check pre-evolutions.
|
||||
for (int i = 1; i < chain.Length; i++)
|
||||
{
|
||||
var isDefaultName = SpeciesName.IsNicknamedAnyLanguage(chain[i].Species, name, EntityContext.Gen3);
|
||||
if (isDefaultName)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TrashByteRules3
|
||||
{
|
||||
// PK3 stores u8[length] for OT name and Nickname.
|
||||
|
|
@ -153,7 +254,7 @@ public static class TrashByteRules3
|
|||
// - - Japanese OT names are 5 chars, international is 7 chars. Manually entered strings are FF terminated to max length + 1.
|
||||
// - - Default OT (Japanese) names were padded with FF to len=6, so they always match manually entered names (no trash).
|
||||
// - - Default OT (International) names from the character select screen can have trash bytes due to being un-padded (single FF end of string, saves ROM space).
|
||||
// - - verification of Default OTs todo (if OT dirty, check if is default with expected trash pattern)
|
||||
// - - verification of Default OTs
|
||||
// - Nickname: the buffer has garbage RAM data leftover in the nickname field, thus it should be "dirty" usually.
|
||||
// - Nicknamed: when nicknamed, the game fills the buffer with FFs then applies the nickname.
|
||||
// For event encounters from GameCube:
|
||||
|
|
@ -192,9 +293,15 @@ public static bool IsTerminatedFF(ReadOnlySpan<byte> data)
|
|||
var first = TrashBytes8.GetTerminatorIndex(data);
|
||||
if (first == -1 || first >= data.Length - 1)
|
||||
return true;
|
||||
return !data[(first + 1)..].ContainsAnyExcept<byte>(0xFF);
|
||||
return !data[(first + 1)..].ContainsAnyExcept(Terminator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the <see cref="data"/> matches the pattern of a pre-filled array with terminators of count <see cref="preFill"/>.
|
||||
/// </summary>
|
||||
/// <param name="data">Raw text string to check</param>
|
||||
/// <param name="preFill">Count of chars filled with terminator.</param>
|
||||
/// <returns><see langword="true"/> if the text matches the pre-fill pattern.</returns>
|
||||
public static bool IsTerminatedFFZero(ReadOnlySpan<byte> data, int preFill = 0)
|
||||
{
|
||||
if (preFill == 0)
|
||||
|
|
@ -211,17 +318,27 @@ public static bool IsTerminatedFFZero(ReadOnlySpan<byte> data, int preFill = 0)
|
|||
if (inner.ContainsAnyExcept(Terminator))
|
||||
return false;
|
||||
first = preFill;
|
||||
first++;
|
||||
if (first >= data.Length - 1)
|
||||
if (first >= data.Length)
|
||||
return true;
|
||||
}
|
||||
return !data[first..].ContainsAnyExcept<byte>(0);
|
||||
}
|
||||
|
||||
// TRASH BYTES: New Game Default OTs
|
||||
// Default OT names in International (not JPN) Gen3 mainline games memcpy 7 chars and FF from the "default OT name" table, regardless of strlen.
|
||||
// Copied strings therefore contain trash from the next string entry that is encoded into the rom's string table.
|
||||
// Below is a list of possible (version, language, trash) default OTs, as initialized by the game. An `*` is used to denote the terminator.
|
||||
// Default OT names in International (not JPN) Gen3 mainline R/S/E games memcpy exactly 7 chars then FF from the "default OT name" table, regardless of the entry's strlen.
|
||||
// - Japanese has every default OT name padded with FF's (to strlen=6), and is thus not affected.
|
||||
// - FireRed/LeafGreen uses different logic (Oak Speech) which writes until EOS (0xFF) then filling the rest with 0xFF (thus is entirely clean).
|
||||
// Copied strings therefore contain "trash" from the next string entry encoded into the ROM's string table.
|
||||
// Below is a list of possible (version, language, trash) default OTs, as initialized by the game. An `*` is used to denote the terminator, with the following chars from next entry.
|
||||
|
||||
// Sequential entries are provided for documentation purposes; entries that result in no difference from a manually entered name are commented out.
|
||||
// If it is a default name, it must match the associated gender; otherwise, it must have been manually entered for the other gender.
|
||||
|
||||
// Potential optimization: Entries could be encoded into a single ulong via:
|
||||
// 7 bytes trash,
|
||||
// 1 byte: version (2bit) - gender (1bit), language (3bit).
|
||||
// Then, a simple sorted-array lookup could quickly check presence via binary search (147 entries, 5 pivot checks).
|
||||
// However, this is such a low-traffic method that such an optimization (sacrificing code documentation) isn't worth it.
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the specified trash byte pattern matches a default trainer name pattern for the given game <see cref="version"/> and <see cref="language"/>.
|
||||
|
|
@ -229,23 +346,143 @@ public static bool IsTerminatedFFZero(ReadOnlySpan<byte> data, int preFill = 0)
|
|||
/// <remarks>Default trainer names in certain Generation 3 Pokémon games may include trailing bytes ("trash") due to how names are stored in the game's ROM.
|
||||
/// This method checks if the provided pattern matches any of these known default patterns for the specified version and language.
|
||||
/// </remarks>
|
||||
public static bool IsTrashPatternDefaultTrainer(ReadOnlySpan<byte> trash, GameVersion version, LanguageID language) => version switch
|
||||
public static bool IsTrashPatternDefaultTrainer(ReadOnlySpan<byte> trash, GameVersion version, LanguageID language, byte gender) => version switch
|
||||
{
|
||||
GameVersion.R or GameVersion.S => IsTrashPatternDefaultTrainerRS(trash, language),
|
||||
GameVersion.E => IsTrashPatternDefaultTrainerE(trash, language),
|
||||
GameVersion.FR => IsTrashPatternDefaultTrainerFR(trash, language),
|
||||
GameVersion.LG => IsTrashPatternDefaultTrainerLG(trash, language),
|
||||
GameVersion.R => IsTrashPatternDefaultTrainerR(trash, language, gender),
|
||||
GameVersion.S => IsTrashPatternDefaultTrainerS(trash, language, gender),
|
||||
GameVersion.E => IsTrashPatternDefaultTrainerE(trash, language, gender),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
|
||||
/// <remarks>Default OT names present in <see cref="GameVersion.R"/> and <see cref="GameVersion.S"/> based on the language of the game.</remarks>
|
||||
public static bool IsTrashPatternDefaultTrainerRS(ReadOnlySpan<byte> trash, LanguageID language) => language switch
|
||||
/// <remarks>Default OT names present in <see cref="GameVersion.R"/> based on the language of the game.</remarks>
|
||||
public static bool IsTrashPatternDefaultTrainerR(ReadOnlySpan<byte> trash, LanguageID language, byte gender) => language switch
|
||||
{
|
||||
// TODO
|
||||
LanguageID.English => trash switch
|
||||
{
|
||||
[0xCD, 0xBB, 0xCC, 0xBB, 0xFF, 0xCE, 0xDC] => true, // SARA*Th
|
||||
// [0xC6, 0xBB, 0xC8, 0xBE, 0xC9, 0xC8, 0xFF] => gender == 0, // LANDON*
|
||||
[0xCE, 0xBF, 0xCC, 0xCC, 0xD3, 0xFF, 0xCD] => gender == 0, // TERRY*S
|
||||
[0xCD, 0xBF, 0xCE, 0xC2, 0xFF, 0xCE, 0xC9] => gender == 0, // SETH*TO
|
||||
[0xCE, 0xC9, 0xC7, 0xFF, 0xCE, 0xBF, 0xCC] => gender == 0, // TOM*TER
|
||||
[0xCE, 0xBF, 0xCC, 0xCC, 0xBB, 0xFF, 0xC5] => gender == 1, // TERRA*K
|
||||
[0xC5, 0xC3, 0xC7, 0xC7, 0xD3, 0xFF, 0xC8] => gender == 1, // KIMMY*N
|
||||
// [0xC8, 0xC3, 0xBD, 0xC9, 0xC6, 0xBB, 0xFF] => gender == 1, // NICOLA*
|
||||
[0xCD, 0xBB, 0xCC, 0xBB, 0xFF, 0xCE, 0xDC] => gender == 1, // SARA*Th
|
||||
_ => false,
|
||||
},
|
||||
LanguageID.French => trash switch
|
||||
{
|
||||
// [0xCE, 0xC2, 0xC3, 0xBF, 0xCC, 0xCC, 0xD3] => gender == 0, // THIERRY
|
||||
// [0xCE, 0xC2, 0xC9, 0xC7, 0xBB, 0xCD, 0xFF] => gender == 0, // THOMAS*
|
||||
// [0xBE, 0xBB, 0xC8, 0xC3, 0xBF, 0xC6, 0xFF] => gender == 0, // DANIEL*
|
||||
[0xCD, 0xBF, 0xBC, 0xFF, 0xCD, 0xC9, 0xC6] => gender == 0, // SEB*SOL
|
||||
// [0xCD, 0xC9, 0xC6, 0xBF, 0xC8, 0xBF, 0xFF] => gender == 1, // SOLENE*
|
||||
[0xBB, 0xC1, 0xC8, 0xBF, 0xCD, 0xFF, 0xBD] => gender == 1, // AGNES*C
|
||||
// [0xBD, 0xC6, 0xBB, 0xC3, 0xCC, 0xBF, 0xFF] => gender == 1, // CLAIRE*
|
||||
// [0xCD, 0xC9, 0xCA, 0xC2, 0xC3, 0xBF, 0xFF] => gender == 1, // SOPHIE*
|
||||
_ => false,
|
||||
},
|
||||
LanguageID.Italian => trash switch
|
||||
{
|
||||
// [0xC6, 0xBB, 0xC8, 0xBE, 0xC9, 0xC8, 0xFF] => gender == 0, // LANDON*
|
||||
[0xC7, 0xBB, 0xCC, 0xBD, 0xC9, 0xFF, 0xCA] => gender == 0, // MARCO*P
|
||||
[0xCA, 0xBB, 0xC9, 0xC6, 0xC9, 0xFF, 0xC6] => gender == 0, // PAOLO*L
|
||||
[0xC6, 0xCF, 0xBD, 0xC3, 0xC9, 0xFF, 0xCE] => gender == 0, // LUCIO*T
|
||||
// [0xCE, 0xBF, 0xCC, 0xBF, 0xCD, 0xBB, 0xFF] => gender == 1, // TERESA*
|
||||
[0xBB, 0xC8, 0xC8, 0xC3, 0xBF, 0xFF, 0xBF] => gender == 1, // ANNIE*E
|
||||
[0xBF, 0xC6, 0xC3, 0xCD, 0xBB, 0xFF, 0xCD] => gender == 1, // ELISA*S
|
||||
[0xCD, 0xBB, 0xCC, 0xBB, 0xFF, 0xCB, 0xE9] => gender == 1, // SARA*Qu
|
||||
_ => false,
|
||||
},
|
||||
LanguageID.German => trash switch
|
||||
{
|
||||
// [0xCC, 0xC9, 0xC6, 0xBB, 0xC8, 0xBE, 0xFF] => gender == 0, // ROLAND*
|
||||
// [0xBE, 0xBB, 0xC8, 0xC3, 0xBF, 0xC6, 0xFF] => gender == 0, // DANIEL*
|
||||
[0xC2, 0xBF, 0xC6, 0xC1, 0xBF, 0xFF, 0xC4] => gender == 0, // HELGE*J
|
||||
[0xC4, 0xBB, 0xC8, 0xFF, 0xCA, 0xBF, 0xCE] => gender == 0, // JAN*PET
|
||||
[0xCA, 0xBF, 0xCE, 0xCC, 0xBB, 0xFF, 0xCE] => gender == 1, // PETRA*T
|
||||
[0xCE, 0xBB, 0xC8, 0xC4, 0xBB, 0xFF, 0xBB] => gender == 1, // TANJA*A
|
||||
// [0xBB, 0xC8, 0xBE, 0xCC, 0xBF, 0xBB, 0xFF] => gender == 1, // ANDREA*
|
||||
[0xCD, 0xBB, 0xCC, 0xBB, 0xFF, 0xBE, 0xDD] => gender == 1, // SARA*Di
|
||||
_ => false,
|
||||
},
|
||||
LanguageID.Spanish => trash switch
|
||||
{
|
||||
[0xCE, 0xBF, 0xCC, 0xBF, 0xC8, 0xFF, 0xCB] => gender == 0, // TEREN*Q
|
||||
[0xCB, 0xCF, 0xC3, 0xC7, 0xC3, 0xFF, 0xCC] => gender == 0, // QUIMI*R
|
||||
[0xCC, 0xCF, 0xC0, 0xC9, 0xFF, 0xBB, 0xCC] => gender == 0, // RUFO*AR
|
||||
// [0xBB, 0xCC, 0xCE, 0xCF, 0xCC, 0xC9, 0xFF] => gender == 0, // ARTURO*
|
||||
// [0xCE, 0xBF, 0xCC, 0xBF, 0xCD, 0xBB, 0xFF] => gender == 1, // TERESA*
|
||||
// [0xCC, 0xBB, 0xCB, 0xCF, 0xBF, 0xC6, 0xFF] => gender == 1, // RAQUEL*
|
||||
// [0xC7, 0xBB, 0xCC, 0xC3, 0xBB, 0xCF, 0xFF] => gender == 1, // MARIAU*
|
||||
[0xCD, 0xBB, 0xCC, 0xBB, 0xFF, 0xBB, 0xE5] => gender == 1, // SARA*Aq
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
|
||||
/// <remarks>Default OT names present in <see cref="GameVersion.S"/> based on the language of the game.</remarks>
|
||||
public static bool IsTrashPatternDefaultTrainerS(ReadOnlySpan<byte> trash, LanguageID language, byte gender) => language switch
|
||||
{
|
||||
LanguageID.English => trash switch
|
||||
{
|
||||
[0xCD, 0xBF, 0xBB, 0xC8, 0xFF, 0xCE, 0xBF] => gender == 0, // SEAN*TE
|
||||
[0xCE, 0xBF, 0xCC, 0xCC, 0xD3, 0xFF, 0xCD] => gender == 0, // TERRY*S
|
||||
[0xCD, 0xBF, 0xCE, 0xC2, 0xFF, 0xCE, 0xC9] => gender == 0, // SETH*TO
|
||||
[0xCE, 0xC9, 0xC7, 0xFF, 0xC7, 0xBB, 0xCC] => gender == 0, // TOM*MAR
|
||||
// [0xC7, 0xBB, 0xCC, 0xC3, 0xC8, 0xBB, 0xFF] => gender == 1, // MARINA*
|
||||
[0xC5, 0xC3, 0xC7, 0xC7, 0xD3, 0xFF, 0xC8] => gender == 1, // KIMMY*N
|
||||
// [0xC8, 0xC3, 0xBD, 0xC9, 0xC6, 0xBB, 0xFF] => gender == 1, // NICOLA*
|
||||
[0xCD, 0xBB, 0xCC, 0xBB, 0xFF, 0xCE, 0xDC] => gender == 1, // SARA*Th
|
||||
_ => false,
|
||||
},
|
||||
LanguageID.French => trash switch
|
||||
{
|
||||
// [0xC7, 0xBB, 0xCC, 0xCE, 0xC3, 0xBB, 0xC6] => gender == 0, // MARTIAL
|
||||
// [0xCE, 0xC2, 0xC9, 0xC7, 0xBB, 0xCD, 0xFF] => gender == 0, // THOMAS*
|
||||
// [0xBE, 0xBB, 0xC8, 0xC3, 0xBF, 0xC6, 0xFF] => gender == 0, // DANIEL*
|
||||
[0xCD, 0xBF, 0xBC, 0xFF, 0xC7, 0xBB, 0xCC] => gender == 0, // SEB*MAR
|
||||
// [0xC7, 0xBB, 0xCC, 0xC3, 0xC8, 0xBF, 0xFF] => gender == 1, // MARINE*
|
||||
[0xBB, 0xC1, 0xC8, 0xBF, 0xCD, 0xFF, 0xBD] => gender == 1, // AGNES*C
|
||||
// [0xBD, 0xC6, 0xBB, 0xC3, 0xCC, 0xBF, 0xFF] => gender == 1, // CLAIRE*
|
||||
// [0xCD, 0xC9, 0xCA, 0xC2, 0xC3, 0xBF, 0xFF] => gender == 1, // SOPHIE*
|
||||
_ => false,
|
||||
},
|
||||
LanguageID.Italian => trash switch
|
||||
{
|
||||
// [0xC7, 0xBB, 0xCC, 0xCE, 0xC3, 0xC8, 0xFF] => gender == 0, // MARTIN*
|
||||
[0xC7, 0xBB, 0xCC, 0xBD, 0xC9, 0xFF, 0xCA] => gender == 0, // MARCO*P
|
||||
[0xCA, 0xBB, 0xC9, 0xC6, 0xC9, 0xFF, 0xC6] => gender == 0, // PAOLO*L
|
||||
[0xC6, 0xCF, 0xBD, 0xC3, 0xC9, 0xFF, 0xC7] => gender == 0, // LUCIO*M
|
||||
// [0xC7, 0xBB, 0xCC, 0xC3, 0xC8, 0xBB, 0xFF] => gender == 1, // MARINA*
|
||||
[0xBB, 0xC8, 0xC8, 0xC3, 0xBF, 0xFF, 0xBF] => gender == 1, // ANNIE*E
|
||||
[0xBF, 0xC6, 0xC3, 0xCD, 0xBB, 0xFF, 0xCD] => gender == 1, // ELISA*S
|
||||
[0xCD, 0xBB, 0xCC, 0xBB, 0xFF, 0xCB, 0xE9] => gender == 1, // SARA*Qu
|
||||
_ => false,
|
||||
},
|
||||
LanguageID.German => trash switch
|
||||
{
|
||||
// [0xCD, 0xBF, 0xBC, 0xC9, 0xC6, 0xBE, 0xFF] => gender == 0, // SEBOLD*
|
||||
// [0xBE, 0xBB, 0xC8, 0xC3, 0xBF, 0xC6, 0xFF] => gender == 0, // DANIEL*
|
||||
[0xC2, 0xBF, 0xC6, 0xC1, 0xBF, 0xFF, 0xC4] => gender == 0, // HELGE*J
|
||||
[0xC4, 0xBB, 0xC8, 0xFF, 0xC7, 0xBB, 0xCC] => gender == 0, // JAN*MAR
|
||||
// [0xC7, 0xBB, 0xCC, 0xCE, 0xC3, 0xC8, 0xBB] => gender == 1, // MARTINA
|
||||
[0xCE, 0xBB, 0xC8, 0xC4, 0xBB, 0xFF, 0xBB] => gender == 1, // TANJA*A
|
||||
// [0xBB, 0xC8, 0xBE, 0xCC, 0xBF, 0xBB, 0xFF] => gender == 1, // ANDREA*
|
||||
[0xCD, 0xBB, 0xCC, 0xBB, 0xFF, 0xBE, 0xDD] => gender == 1, // SARA*Di
|
||||
_ => false,
|
||||
},
|
||||
LanguageID.Spanish => trash switch
|
||||
{
|
||||
// [0xC7, 0xBB, 0xCC, 0xC3, 0xC8, 0xC9, 0xFF] => gender == 0, // MARINO*
|
||||
[0xCB, 0xCF, 0xC3, 0xC7, 0xC3, 0xFF, 0xCC] => gender == 0, // QUIMI*R
|
||||
[0xCC, 0xCF, 0xC0, 0xC9, 0xFF, 0xBB, 0xCC] => gender == 0, // RUFO*AR
|
||||
// [0xBB, 0xCC, 0xCE, 0xCF, 0xCC, 0xC9, 0xFF] => gender == 0, // ARTURO*
|
||||
// [0xC7, 0xBB, 0xCC, 0xC3, 0xC8, 0xBB, 0xFF] => gender == 1, // MARINA*
|
||||
// [0xCC, 0xBB, 0xCB, 0xCF, 0xBF, 0xC6, 0xFF] => gender == 1, // RAQUEL*
|
||||
// [0xC7, 0xBB, 0xCC, 0xC3, 0xBB, 0xCF, 0xFF] => gender == 1, // MARIAU*
|
||||
[0xCD, 0xBB, 0xCC, 0xBB, 0xFF, 0xBB, 0xE5] => gender == 1, // SARA*Aq
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
|
|
@ -253,25 +490,228 @@ public static bool IsTerminatedFFZero(ReadOnlySpan<byte> data, int preFill = 0)
|
|||
|
||||
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
|
||||
/// <remarks>Default OT names present in <see cref="GameVersion.E"/> based on the language of the game.</remarks>
|
||||
public static bool IsTrashPatternDefaultTrainerE(ReadOnlySpan<byte> trash, LanguageID language) => language switch
|
||||
public static bool IsTrashPatternDefaultTrainerE(ReadOnlySpan<byte> trash, LanguageID language, byte gender) => language switch
|
||||
{
|
||||
// TODO
|
||||
LanguageID.English => trash switch
|
||||
{
|
||||
[0xCD, 0xCE, 0xCF, 0xFF, 0xC7, 0xC3, 0xC6] => gender == 0, // STU*MIL
|
||||
// [0xC7, 0xC3, 0xC6, 0xCE, 0xC9, 0xC8, 0xFF] => gender == 0, // MILTON*
|
||||
[0xCE, 0xC9, 0xC7, 0xFF, 0xC5, 0xBF, 0xC8] => gender == 0, // TOM*KEN
|
||||
[0xC5, 0xBF, 0xC8, 0xC8, 0xD3, 0xFF, 0xCC] => gender == 0, // KENNY*R
|
||||
[0xCC, 0xBF, 0xC3, 0xBE, 0xFF, 0xC4, 0xCF] => gender == 0, // REID*JU
|
||||
[0xC4, 0xCF, 0xBE, 0xBF, 0xFF, 0xC4, 0xBB] => gender == 0, // JUDE*JA
|
||||
// [0xC4, 0xBB, 0xD2, 0xCD, 0xC9, 0xC8, 0xFF] => gender == 0, // JAXSON*
|
||||
// [0xBF, 0xBB, 0xCD, 0xCE, 0xC9, 0xC8, 0xFF] => gender == 0, // EASTON*
|
||||
// [0xD1, 0xBB, 0xC6, 0xC5, 0xBF, 0xCC, 0xFF] => gender == 0, // WALKER*
|
||||
[0xCE, 0xBF, 0xCC, 0xCF, 0xFF, 0xC4, 0xC9] => gender == 0, // TERU*JO
|
||||
// [0xC4, 0xC9, 0xC2, 0xC8, 0xC8, 0xD3, 0xFF] => gender == 0, // JOHNNY*
|
||||
[0xBC, 0xCC, 0xBF, 0xCE, 0xCE, 0xFF, 0xCD] => gender == 0, // BRETT*S
|
||||
[0xCD, 0xBF, 0xCE, 0xC2, 0xFF, 0xCE, 0xBF] => gender == 0, // SETH*TE
|
||||
[0xCE, 0xBF, 0xCC, 0xCC, 0xD3, 0xFF, 0xBD] => gender == 0, // TERRY*C
|
||||
[0xBD, 0xBB, 0xCD, 0xBF, 0xD3, 0xFF, 0xBE] => gender == 0, // CASEY*D
|
||||
// [0xBE, 0xBB, 0xCC, 0xCC, 0xBF, 0xC8, 0xFF] => gender == 0, // DARREN*
|
||||
// [0xC6, 0xBB, 0xC8, 0xBE, 0xC9, 0xC8, 0xFF] => gender == 0, // LANDON*
|
||||
// [0xBD, 0xC9, 0xC6, 0xC6, 0xC3, 0xC8, 0xFF] => gender == 0, // COLLIN*
|
||||
// [0xCD, 0xCE, 0xBB, 0xC8, 0xC6, 0xBF, 0xD3] => gender == 0, // STANLEY
|
||||
// [0xCB, 0xCF, 0xC3, 0xC8, 0xBD, 0xD3, 0xFF] => gender == 0, // QUINCY*
|
||||
[0xC5, 0xC3, 0xC7, 0xC7, 0xD3, 0xFF, 0xCE] => gender == 1, // KIMMY*T
|
||||
[0xCE, 0xC3, 0xBB, 0xCC, 0xBB, 0xFF, 0xBC] => gender == 1, // TIARA*B
|
||||
[0xBC, 0xBF, 0xC6, 0xC6, 0xBB, 0xFF, 0xC4] => gender == 1, // BELLA*J
|
||||
[0xC4, 0xBB, 0xD3, 0xC6, 0xBB, 0xFF, 0xBB] => gender == 1, // JAYLA*A
|
||||
[0xBB, 0xC6, 0xC6, 0xC3, 0xBF, 0xFF, 0xC6] => gender == 1, // ALLIE*L
|
||||
// [0xC6, 0xC3, 0xBB, 0xC8, 0xC8, 0xBB, 0xFF] => gender == 1, // LIANNA*
|
||||
[0xCD, 0xBB, 0xCC, 0xBB, 0xFF, 0xC7, 0xC9] => gender == 1, // SARA*MO
|
||||
// [0xC7, 0xC9, 0xC8, 0xC3, 0xBD, 0xBB, 0xFF] => gender == 1, // MONICA*
|
||||
// [0xBD, 0xBB, 0xC7, 0xC3, 0xC6, 0xBB, 0xFF] => gender == 1, // CAMILA*
|
||||
// [0xBB, 0xCF, 0xBC, 0xCC, 0xBF, 0xBF, 0xFF] => gender == 1, // AUBREE*
|
||||
// [0xCC, 0xCF, 0xCE, 0xC2, 0xC3, 0xBF, 0xFF] => gender == 1, // RUTHIE*
|
||||
[0xC2, 0xBB, 0xD4, 0xBF, 0xC6, 0xFF, 0xC8] => gender == 1, // HAZEL*N
|
||||
// [0xC8, 0xBB, 0xBE, 0xC3, 0xC8, 0xBF, 0xFF] => gender == 1, // NADINE*
|
||||
[0xCE, 0xBB, 0xC8, 0xC4, 0xBB, 0xFF, 0xD3] => gender == 1, // TANJA*Y
|
||||
// [0xD3, 0xBB, 0xCD, 0xC7, 0xC3, 0xC8, 0xFF] => gender == 1, // YASMIN*
|
||||
// [0xC8, 0xC3, 0xBD, 0xC9, 0xC6, 0xBB, 0xFF] => gender == 1, // NICOLA*
|
||||
// [0xC6, 0xC3, 0xC6, 0xC6, 0xC3, 0xBF, 0xFF] => gender == 1, // LILLIE*
|
||||
[0xCE, 0xBF, 0xCC, 0xCC, 0xBB, 0xFF, 0xC6] => gender == 1, // TERRA*L
|
||||
[0xC6, 0xCF, 0xBD, 0xD3, 0xFF, 0xC2, 0xBB] => gender == 1, // LUCY*HA
|
||||
[0xC2, 0xBB, 0xC6, 0xC3, 0xBF, 0xFF, 0xCE] => gender == 1, // HALIE*T
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
|
||||
/// <remarks>Default OT names present in <see cref="GameVersion.FR"/> based on the language of the game.</remarks>
|
||||
public static bool IsTrashPatternDefaultTrainerFR(ReadOnlySpan<byte> trash, LanguageID language) => language switch
|
||||
},
|
||||
LanguageID.French => trash switch
|
||||
{
|
||||
// TODO
|
||||
[0xCD, 0xCE, 0xBF, 0xC0, 0xFF, 0xC7, 0xBB] => gender == 0, // STEF*MA
|
||||
// [0xC7, 0xBB, 0xC8, 0xCF, 0xBF, 0xC6, 0xFF] => gender == 0, // MANUEL*
|
||||
[0xCD, 0xBF, 0xBC, 0xFF, 0xC1, 0xD1, 0xBF] => gender == 0, // SEB*GWE
|
||||
[0xC1, 0xD1, 0xBF, 0xC8, 0xC8, 0xFF, 0xBB] => gender == 0, // GWENN*A
|
||||
[0xBB, 0xCC, 0xC8, 0xC9, 0xFF, 0xC4, 0xCF] => gender == 0, // ARNO*JU
|
||||
[0xC4, 0xCF, 0xC6, 0xBF, 0xCD, 0xFF, 0xC4] => gender == 0, // JULES*J
|
||||
// [0xC4, 0xC9, 0xC2, 0xBB, 0xC8, 0xC8, 0xFF] => gender == 0, // JOHANN*
|
||||
// [0xCE, 0xC2, 0xC3, 0xBC, 0xBB, 0xCF, 0xBE] => gender == 0, // THIBAUD
|
||||
[0xBB, 0xC6, 0xBF, 0xBD, 0xFF, 0xC1, 0xC3] => gender == 0, // ALEC*GI
|
||||
[0xC1, 0xC3, 0xBC, 0xCF, 0xCD, 0xFF, 0xC4] => gender == 0, // GIBUS*J
|
||||
// [0xC4, 0xC9, 0xC2, 0xC8, 0xC8, 0xD3, 0xFF] => gender == 0, // JOHNNY*
|
||||
// [0xC0, 0xBB, 0xBC, 0xCC, 0xC3, 0xBD, 0xBF] => gender == 0, // FABRICE
|
||||
// [0xBE, 0xBB, 0xC8, 0xC3, 0xBF, 0xC6, 0xFF] => gender == 0, // DANIEL*
|
||||
// [0xCE, 0xC2, 0xC9, 0xC7, 0xBB, 0xCD, 0xFF] => gender == 0, // THOMAS*
|
||||
[0xC1, 0xBB, 0xCC, 0xD3, 0xFF, 0xCC, 0xCF] => gender == 0, // GARY*RU
|
||||
[0xCC, 0xCF, 0xBE, 0xBE, 0xD3, 0xFF, 0xCE] => gender == 0, // RUDDY*T
|
||||
// [0xCE, 0xC2, 0xC3, 0xBF, 0xCC, 0xCC, 0xD3] => gender == 0, // THIERRY
|
||||
[0xBD, 0xC9, 0xC6, 0xC3, 0xC8, 0xFF, 0xCD] => gender == 0, // COLIN*S
|
||||
[0xCD, 0xCE, 0xBB, 0xC8, 0xFF, 0xCD, 0xBF] => gender == 0, // STAN*SE
|
||||
// [0xCD, 0xBF, 0xD0, 0xBF, 0xCC, 0xC3, 0xC8] => gender == 0, // SEVERIN
|
||||
[0xBB, 0xC1, 0xC8, 0xBF, 0xCD, 0xFF, 0xBB] => gender == 1, // AGNES*A
|
||||
// [0xBB, 0xCC, 0xC3, 0xBB, 0xC8, 0xBF, 0xFF] => gender == 1, // ARIANE*
|
||||
[0xBC, 0xBF, 0xC6, 0xC6, 0xBB, 0xFF, 0xC7] => gender == 1, // BELLA*M
|
||||
[0xC7, 0xBB, 0xBF, 0xD0, 0xBB, 0xFF, 0xCA] => gender == 1, // MAEVA*P
|
||||
// [0xCA, 0xBB, 0xCF, 0xC6, 0xC3, 0xC8, 0xBF] => gender == 1, // PAULINE
|
||||
[0xBD, 0xC3, 0xC8, 0xBE, 0xD3, 0xFF, 0xCD] => gender == 1, // CINDY*S
|
||||
// [0xCD, 0xC9, 0xCA, 0xC2, 0xC3, 0xBF, 0xFF] => gender == 1, // SOPHIE*
|
||||
// [0xC7, 0xC9, 0xC8, 0xC3, 0xBD, 0xBB, 0xFF] => gender == 1, // MONICA*
|
||||
[0xBD, 0xBB, 0xCE, 0xC2, 0xD3, 0xFF, 0xC0] => gender == 1, // CATHY*F
|
||||
[0xC0, 0xBB, 0xC8, 0xC8, 0xD3, 0xFF, 0xCC] => gender == 1, // FANNY*R
|
||||
// [0xCC, 0xC9, 0xD2, 0xBB, 0xC8, 0xBF, 0xFF] => gender == 1, // ROXANE*
|
||||
[0xBF, 0xBE, 0xC3, 0xCE, 0xC2, 0xFF, 0xC8] => gender == 1, // EDITH*N
|
||||
// [0xC8, 0xBB, 0xBE, 0xC3, 0xC8, 0xBF, 0xFF] => gender == 1, // NADINE*
|
||||
[0xCE, 0xBB, 0xC8, 0xC3, 0xBB, 0xFF, 0xC4] => gender == 1, // TANIA*J
|
||||
// [0xC4, 0xBB, 0xC8, 0xD3, 0xBD, 0xBF, 0xFF] => gender == 1, // JANYCE*
|
||||
// [0xBD, 0xC6, 0xBB, 0xC3, 0xCC, 0xBF, 0xFF] => gender == 1, // CLAIRE*
|
||||
[0xC6, 0xC3, 0xC6, 0xC6, 0xD3, 0xFF, 0xCD] => gender == 1, // LILLY*S
|
||||
// [0xCD, 0xC9, 0xC6, 0xBF, 0xC8, 0xBF, 0xFF] => gender == 1, // SOLENE*
|
||||
// [0xBD, 0xD3, 0xC8, 0xCE, 0xC2, 0xC3, 0xBB] => gender == 1, // CYNTHIA
|
||||
[0xC7, 0xBB, 0xCF, 0xBE, 0xFF, 0xD0, 0xE3] => gender == 1, // MAUD*Vo
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
|
||||
/// <remarks>Default OT names present in <see cref="GameVersion.LG"/> based on the language of the game.</remarks>
|
||||
public static bool IsTrashPatternDefaultTrainerLG(ReadOnlySpan<byte> trash, LanguageID language) => language switch
|
||||
},
|
||||
LanguageID.Italian => trash switch
|
||||
{
|
||||
// TODO
|
||||
// [0xC0, 0xCC, 0xBB, 0xC8, 0xBD, 0xD3, 0xFF] => gender == 0, // FRANCY*
|
||||
// [0xC1, 0xC3, 0xC9, 0xCC, 0xC1, 0xC3, 0xC9] => gender == 0, // GIORGIO
|
||||
[0xC6, 0xCF, 0xBD, 0xC3, 0xC9, 0xFF, 0xC0] => gender == 0, // LUCIO*F
|
||||
[0xC0, 0xBB, 0xBC, 0xD3, 0xFF, 0xBB, 0xC8] => gender == 0, // FABY*AN
|
||||
// [0xBB, 0xC8, 0xBE, 0xCC, 0xBF, 0xBB, 0xFF] => gender == 0, // ANDREA*
|
||||
// [0xBE, 0xBB, 0xC8, 0xC3, 0xBF, 0xC6, 0xBF] => gender == 0, // DANIELE
|
||||
// [0xC7, 0xC3, 0xBD, 0xC2, 0xBF, 0xC6, 0xBF] => gender == 0, // MICHELE
|
||||
[0xCC, 0xBF, 0xC8, 0xD4, 0xC9, 0xFF, 0xBF] => gender == 0, // RENZO*E
|
||||
// [0xBF, 0xCF, 0xC1, 0xBF, 0xC8, 0xC3, 0xC9] => gender == 0, // EUGENIO
|
||||
[0xBF, 0xC6, 0xC3, 0xBB, 0xFF, 0xCD, 0xBB] => gender == 0, // ELIA*SA
|
||||
// [0xCD, 0xBB, 0xC8, 0xBE, 0xCC, 0xC9, 0xFF] => gender == 0, // SANDRO*
|
||||
// [0xCA, 0xC3, 0xBF, 0xCE, 0xCC, 0xC9, 0xFF] => gender == 0, // PIETRO*
|
||||
[0xCA, 0xBB, 0xC9, 0xC6, 0xC9, 0xFF, 0xC7] => gender == 0, // PAOLO*M
|
||||
[0xC7, 0xBB, 0xCC, 0xBD, 0xC9, 0xFF, 0xBB] => gender == 0, // MARCO*A
|
||||
// [0xBB, 0xC6, 0xBC, 0xBF, 0xCC, 0xCE, 0xC9] => gender == 0, // ALBERTO
|
||||
// [0xC0, 0xC3, 0xC6, 0xC3, 0xCA, 0xCA, 0xC9] => gender == 0, // FILIPPO
|
||||
// [0xC6, 0xBB, 0xC8, 0xBE, 0xC9, 0xC8, 0xFF] => gender == 0, // LANDON*
|
||||
[0xC1, 0xC3, 0xC8, 0xC9, 0xFF, 0xBD, 0xBF] => gender == 0, // GINO*CE
|
||||
[0xBD, 0xBF, 0xBD, 0xBD, 0xC9, 0xFF, 0xC7] => gender == 0, // CECCO*M
|
||||
[0xC7, 0xBB, 0xCC, 0xC3, 0xC9, 0xFF, 0xBB] => gender == 0, // MARIO*A
|
||||
[0xBB, 0xC8, 0xC8, 0xC3, 0xBF, 0xFF, 0xBD] => gender == 1, // ANNIE*C
|
||||
[0xBD, 0xBB, 0xCE, 0xC3, 0xBB, 0xFF, 0xBC] => gender == 1, // CATIA*B
|
||||
[0xBC, 0xBF, 0xC6, 0xC6, 0xBB, 0xFF, 0xCA] => gender == 1, // BELLA*P
|
||||
[0xCA, 0xBB, 0xC9, 0xC6, 0xBB, 0xFF, 0xC6] => gender == 1, // PAOLA*L
|
||||
[0xC6, 0xCF, 0xC3, 0xCD, 0xBB, 0xFF, 0xC1] => gender == 1, // LUISA*G
|
||||
// [0xC1, 0xCC, 0xBB, 0xD4, 0xC3, 0xBB, 0xFF] => gender == 1, // GRAZIA*
|
||||
[0xCD, 0xBB, 0xCC, 0xBB, 0xFF, 0xC7, 0xC9] => gender == 1, // SARA*MO
|
||||
// [0xC7, 0xC9, 0xC8, 0xC3, 0xBD, 0xBB, 0xFF] => gender == 1, // MONICA*
|
||||
[0xC7, 0xBB, 0xCC, 0xCE, 0xBB, 0xFF, 0xCA] => gender == 1, // MARTA*P
|
||||
[0xCA, 0xC3, 0xBB, 0xFF, 0xCC, 0xC3, 0xCE] => gender == 1, // PIA*RIT
|
||||
[0xCC, 0xC3, 0xCE, 0xBB, 0xFF, 0xBF, 0xCC] => gender == 1, // RITA*ER
|
||||
[0xBF, 0xCC, 0xC3, 0xBD, 0xBB, 0xFF, 0xCC] => gender == 1, // ERICA*R
|
||||
[0xCC, 0xC9, 0xCD, 0xBB, 0xFF, 0xC7, 0xBF] => gender == 1, // ROSA*ME
|
||||
// [0xC7, 0xBF, 0xC6, 0xC3, 0xCD, 0xCD, 0xBB] => gender == 1, // MELISSA
|
||||
// [0xC7, 0xBB, 0xCC, 0xC3, 0xC8, 0xBB, 0xFF] => gender == 1, // MARINA*
|
||||
[0xBF, 0xC6, 0xC3, 0xCD, 0xBB, 0xFF, 0xC6] => gender == 1, // ELISA*L
|
||||
[0xC6, 0xC3, 0xC8, 0xBB, 0xFF, 0xCE, 0xBF] => gender == 1, // LINA*TE
|
||||
// [0xCE, 0xBF, 0xCC, 0xBF, 0xCD, 0xBB, 0xFF] => gender == 1, // TERESA*
|
||||
// [0xC6, 0xCF, 0xBD, 0xBF, 0xCE, 0xCE, 0xBB] => gender == 1, // LUCETTA
|
||||
[0xC6, 0xCF, 0xBD, 0xC3, 0xBB, 0xFF, 0xCB] => gender == 1, // LUCIA*Q
|
||||
_ => false,
|
||||
},
|
||||
LanguageID.German => trash switch
|
||||
{
|
||||
// [0xCD, 0xCE, 0xBF, 0xC0, 0xBB, 0xC8, 0xFF] => gender == 0, // STEFAN*
|
||||
// [0xC0, 0xC6, 0xC9, 0xCC, 0xC3, 0xBB, 0xC8] => gender == 0, // FLORIAN
|
||||
[0xC4, 0xBB, 0xC8, 0xFF, 0xBF, 0xCC, 0xC3] => gender == 0, // JAN*ERI
|
||||
[0xBF, 0xCC, 0xC3, 0xC5, 0xFF, 0xCE, 0xC2] => gender == 0, // ERIK*TH
|
||||
// [0xCE, 0xC2, 0xC9, 0xC7, 0xBB, 0xCD, 0xFF] => gender == 0, // THOMAS*
|
||||
// [0xC7, 0xBB, 0xCC, 0xCE, 0xC3, 0xC8, 0xFF] => gender == 0, // MARTIN*
|
||||
// [0xC7, 0xBB, 0xCC, 0xC5, 0xCF, 0xCD, 0xFF] => gender == 0, // MARKUS*
|
||||
[0xC5, 0xC6, 0xBB, 0xCF, 0xCD, 0xFF, 0xCA] => gender == 0, // KLAUS*P
|
||||
[0xCA, 0xBB, 0xCF, 0xC6, 0xFF, 0xCC, 0xC9] => gender == 0, // PAUL*RO
|
||||
[0xCC, 0xC9, 0xC6, 0xC0, 0xFF, 0xC4, 0xF2] => gender == 0, // ROLF*JÖ
|
||||
[0xC4, 0xF2, 0xCC, 0xC1, 0xFF, 0xC2, 0xBB] => gender == 0, // JÖRG*HA
|
||||
[0xC2, 0xBB, 0xC3, 0xC5, 0xC9, 0xFF, 0xC2] => gender == 0, // HAIKO*H
|
||||
[0xC2, 0xBF, 0xC6, 0xC1, 0xBF, 0xFF, 0xBE] => gender == 0, // HELGE*D
|
||||
// [0xBE, 0xBB, 0xC8, 0xC3, 0xBF, 0xC6, 0xFF] => gender == 0, // DANIEL*
|
||||
// [0xC7, 0xC3, 0xBD, 0xC2, 0xBB, 0xBF, 0xC6] => gender == 0, // MICHAEL
|
||||
[0xBE, 0xBB, 0xD0, 0xC3, 0xBE, 0xFF, 0xCC] => gender == 0, // DAVID*R
|
||||
// [0xCC, 0xC9, 0xC6, 0xBB, 0xC8, 0xBE, 0xFF] => gender == 0, // ROLAND*
|
||||
// [0xC4, 0xC9, 0xC2, 0xBB, 0xC8, 0xC8, 0xFF] => gender == 0, // JOHANN*
|
||||
// [0xBE, 0xC3, 0xBF, 0xCE, 0xBF, 0xCC, 0xFF] => gender == 0, // DIETER*
|
||||
// [0xBB, 0xC8, 0xCD, 0xBF, 0xC6, 0xC7, 0xFF] => gender == 0, // ANSELM*
|
||||
[0xCE, 0xBB, 0xC8, 0xC4, 0xBB, 0xFF, 0xC7] => gender == 1, // TANJA*M
|
||||
// [0xC7, 0xC3, 0xCC, 0xC4, 0xBB, 0xC7, 0xFF] => gender == 1, // MIRJAM*
|
||||
// [0xC7, 0xBB, 0xCC, 0xCE, 0xC3, 0xC8, 0xBB] => gender == 1, // MARTINA
|
||||
[0xC4, 0xBB, 0xC7, 0xC3, 0xBF, 0xFF, 0xBD] => gender == 1, // JAMIE*C
|
||||
// [0xBD, 0xBB, 0xCC, 0xC9, 0xC6, 0xC3, 0xC8] => gender == 1, // CAROLIN
|
||||
// [0xCD, 0xC3, 0xC7, 0xC9, 0xC8, 0xBF, 0xFF] => gender == 1, // SIMONE*
|
||||
[0xCD, 0xBB, 0xCC, 0xBB, 0xFF, 0xBD, 0xC6] => gender == 1, // SARA*CL
|
||||
// [0xBD, 0xC6, 0xBB, 0xCF, 0xBE, 0xC3, 0xBB] => gender == 1, // CLAUDIA
|
||||
// [0xC4, 0xBB, 0xCD, 0xC7, 0xC3, 0xC8, 0xFF] => gender == 1, // JASMIN*
|
||||
// [0xBE, 0xBF, 0xC8, 0xC3, 0xCD, 0xBF, 0xFF] => gender == 1, // DENISE*
|
||||
// [0xC5, 0xBB, 0xCE, 0xCC, 0xC3, 0xC8, 0xFF] => gender == 1, // KATRIN*
|
||||
// [0xC5, 0xBF, 0xCC, 0xCD, 0xCE, 0xC3, 0xC8] => gender == 1, // KERSTIN
|
||||
// [0xCD, 0xD0, 0xBF, 0xC8, 0xC4, 0xBB, 0xFF] => gender == 1, // SVENJA*
|
||||
[0xBC, 0xBF, 0xBB, 0xCE, 0xBF, 0xFF, 0xC7] => gender == 1, // BEATE*M
|
||||
[0xC7, 0xBF, 0xC3, 0xC5, 0xBF, 0xFF, 0xBB] => gender == 1, // MEIKE*A
|
||||
// [0xBB, 0xC8, 0xBE, 0xCC, 0xBF, 0xBB, 0xFF] => gender == 1, // ANDREA*
|
||||
[0xBF, 0xD0, 0xBB, 0xFF, 0xCA, 0xBF, 0xCE] => gender == 1, // EVA*PET
|
||||
[0xCA, 0xBF, 0xCE, 0xCC, 0xBB, 0xFF, 0xC1] => gender == 1, // PETRA*G
|
||||
[0xC1, 0xBB, 0xBC, 0xC3, 0xFF, 0xC8, 0xBB] => gender == 1, // GABI*NA
|
||||
// [0xC8, 0xBB, 0xBE, 0xC3, 0xC8, 0xBF, 0xFF] => gender == 1, // NADINE*
|
||||
_ => false,
|
||||
},
|
||||
LanguageID.Spanish => trash switch
|
||||
{
|
||||
[0xBF, 0xC6, 0xBF, 0xC8, 0xC9, 0xFF, 0xC6] => gender == 0, // ELENO*L
|
||||
[0xC6, 0xBB, 0xCC, 0xBF, 0xC9, 0xFF, 0xBB] => gender == 0, // LAREO*A
|
||||
// [0xBB, 0xCC, 0xCE, 0xCF, 0xCC, 0xC9, 0xFF] => gender == 0, // ARTURO*
|
||||
[0xBD, 0xBB, 0xCC, 0xC6, 0xC9, 0xFF, 0xC7] => gender == 0, // CARLO*M
|
||||
[0xC7, 0xBB, 0xCF, 0xCC, 0xC3, 0xFF, 0xBE] => gender == 0, // MAURI*D
|
||||
// [0xBE, 0xBB, 0xC8, 0xC3, 0xBF, 0xC6, 0xFF] => gender == 0, // DANIEL*
|
||||
// [0xC7, 0xBB, 0xCC, 0xBD, 0xBF, 0xC6, 0xC9] => gender == 0, // MARCELO
|
||||
// [0xCC, 0xC9, 0xBC, 0xBF, 0xCC, 0xCE, 0xC9] => gender == 0, // ROBERTO
|
||||
[0xBB, 0xC3, 0xCE, 0xC9, 0xCC, 0xFF, 0xC4] => gender == 0, // AITOR*J
|
||||
[0xC4, 0xCF, 0xC6, 0xC3, 0xFF, 0xC8, 0xBB] => gender == 0, // JULI*NA
|
||||
// [0xC8, 0xBB, 0xCC, 0xBD, 0xC3, 0xCD, 0xC9] => gender == 0, // NARCISO
|
||||
[0xC6, 0xCF, 0xC3, 0xCD, 0xFF, 0xCC, 0xCF] => gender == 0, // LUIS*RU
|
||||
[0xCC, 0xCF, 0xC0, 0xC9, 0xFF, 0xCB, 0xCF] => gender == 0, // RUFO*QU
|
||||
[0xCB, 0xCF, 0xC3, 0xC7, 0xC3, 0xFF, 0xC4] => gender == 0, // QUIMI*J
|
||||
// [0xC4, 0xBF, 0xCD, 0xCF, 0xCD, 0xC9, 0xFF] => gender == 0, // JESUSO*
|
||||
[0xC7, 0xBB, 0xCC, 0xBD, 0xC9, 0xFF, 0xCE] => gender == 0, // MARCO*T
|
||||
[0xCE, 0xBF, 0xCC, 0xBF, 0xC8, 0xFF, 0xC7] => gender == 0, // TEREN*M
|
||||
[0xC7, 0xBB, 0xCC, 0xC3, 0xC9, 0xFF, 0xCA] => gender == 0, // MARIO*P
|
||||
[0xCA, 0xBF, 0xBE, 0xCC, 0xC9, 0xFF, 0xBF] => gender == 0, // PEDRO*E
|
||||
// [0xBF, 0xC8, 0xCC, 0xC3, 0xCB, 0xCF, 0xBF] => gender == 0, // ENRIQUE
|
||||
// [0xCC, 0xBB, 0xCB, 0xCF, 0xBF, 0xC6, 0xFF] => gender == 1, // RAQUEL*
|
||||
[0xBF, 0xC6, 0xBF, 0xC8, 0xBB, 0xFF, 0xCA] => gender == 1, // ELENA*P
|
||||
[0xCA, 0xBB, 0xC6, 0xC7, 0xBB, 0xFF, 0xC6] => gender == 1, // PALMA*L
|
||||
[0xC6, 0xBB, 0xCC, 0xBB, 0xFF, 0xBD, 0xBB] => gender == 1, // LARA*CA
|
||||
// [0xBD, 0xBB, 0xCC, 0xC6, 0xC9, 0xCE, 0xBB] => gender == 1, // CARLOTA
|
||||
[0xC7, 0xC9, 0xC8, 0xBB, 0xFF, 0xCD, 0xBB] => gender == 1, // MONA*SA
|
||||
[0xCD, 0xBB, 0xCC, 0xBB, 0xFF, 0xBE, 0xBB] => gender == 1, // SARA*DA
|
||||
// [0xBE, 0xBB, 0xC8, 0xC3, 0xBF, 0xC6, 0xBB] => gender == 1, // DANIELA
|
||||
// [0xC9, 0xC6, 0xC3, 0xC7, 0xCA, 0xC3, 0xBB] => gender == 1, // OLIMPIA
|
||||
// [0xC7, 0xBB, 0xCC, 0xBD, 0xBF, 0xC6, 0xBB] => gender == 1, // MARCELA
|
||||
// [0xCC, 0xC9, 0xBC, 0xBF, 0xCC, 0xCE, 0xBB] => gender == 1, // ROBERTA
|
||||
// [0xBB, 0xCC, 0xBB, 0xC8, 0xBD, 0xC2, 0xBB] => gender == 1, // ARANCHA
|
||||
// [0xC4, 0xCF, 0xC6, 0xC3, 0xBF, 0xCE, 0xBB] => gender == 1, // JULIETA
|
||||
// [0xC8, 0xC9, 0xBF, 0xC6, 0xC3, 0xBB, 0xFF] => gender == 1, // NOELIA*
|
||||
// [0xC6, 0xCF, 0xBD, 0xC3, 0xCE, 0xBB, 0xFF] => gender == 1, // LUCITA*
|
||||
// [0xC7, 0xBB, 0xCC, 0xC3, 0xBB, 0xCF, 0xFF] => gender == 1, // MARIAU*
|
||||
[0xCA, 0xBB, 0xC9, 0xC6, 0xBB, 0xFF, 0xCE] => gender == 1, // PAOLA*T
|
||||
// [0xCE, 0xBF, 0xCC, 0xBF, 0xCD, 0xBB, 0xFF] => gender == 1, // TERESA*
|
||||
[0xC8, 0xCF, 0xCC, 0xC3, 0xBB, 0xFF, 0xC6] => gender == 1, // NURIA*L
|
||||
[0xC6, 0xC3, 0xC8, 0xBB, 0xFF, 0xBB, 0xE5] => gender == 1, // LINA*Aq
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,10 @@ internal void Verify(LegalityAnalysis data, PA9 pa9)
|
|||
data.AddLine(GetInvalid(StatBattleVersionInvalid));
|
||||
if (!MiscVerifierHelpers.IsObedienceLevelValid(pa9, pa9.ObedienceLevel, pa9.MetLevel))
|
||||
data.AddLine(GetInvalid(TransferObedienceLevel));
|
||||
|
||||
if (pa9.IsAlpha != data.EncounterMatch is IAlphaReadOnly { IsAlpha: true })
|
||||
data.AddLine(GetInvalid(StatAlphaInvalid));
|
||||
else if (pa9.IsAlpha && pa9 is IHomeTrack { HasTracker: true } && (pa9.HeightScalar != 255 || pa9.WeightScalar != 255))
|
||||
data.AddLine(GetInvalid(StatAlphaInvalid)); // HOME inbound forces 255-255-255 if Alpha.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using static PKHeX.Core.LegalityCheckResultCode;
|
||||
using static PKHeX.Core.MoveHealState;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
|
@ -66,68 +67,113 @@ private void VerifyEntity(LegalityAnalysis data)
|
|||
}
|
||||
}
|
||||
|
||||
var expectHeal = Legal.IsPPUnused(pk) || IsPPHealed(data, pk);
|
||||
var allowedStates = GetPermittedStatePP(data, pk);
|
||||
|
||||
for (int i = 0; i < pp.Length; i++)
|
||||
{
|
||||
// Sometimes the PP count will exceed (such as VC=>Bank); just flag it as invalid so the user knows they need to heal them.
|
||||
// Technically that case is legal (game bug) only if they never move it from the box, but we want to inform the user.
|
||||
var expect = pk.GetMovePP(moves[i], ups[i]);
|
||||
var healed = pk.GetMovePP(moves[i], ups[i]);
|
||||
var value = pp[i];
|
||||
if (value > expect)
|
||||
if (value > healed)
|
||||
{
|
||||
data.AddLine(GetInvalid(MovePPTooHigh_01, (ushort)(i + 1), (ushort)value));
|
||||
else if (expectHeal && value != expect)
|
||||
data.AddLine(GetInvalid(MovePPExpectHealed_01, (ushort)(i + 1), (ushort)value));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (allowedStates == Any)
|
||||
continue;
|
||||
if (value == healed && (allowedStates == OnlyHealed || allowedStates.HasFlag(AllowHealed)))
|
||||
continue;
|
||||
if (value == 0 && (allowedStates == Only0 || allowedStates.HasFlag(Allow0)))
|
||||
continue;
|
||||
|
||||
// Not Valid. Add a flag.
|
||||
var (message, expect) = allowedStates switch
|
||||
{
|
||||
OnlyHealed => (MovePPExpectHealed_01, healed),
|
||||
Only0 => (MovePPTooHigh_01, 0),
|
||||
_ => (MovePPExpectHealed_01, healed), // just pick one of the expected states; heal is safe default.
|
||||
};
|
||||
data.AddLine(GetInvalid(message, (ushort)(i + 1), (ushort)expect));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPPHealed(LegalityAnalysis data, PKM pk)
|
||||
private static MoveHealState GetPermittedStatePP(LegalityAnalysis data, PKM pk)
|
||||
{
|
||||
if (Legal.IsPPUnused(pk))
|
||||
{
|
||||
if (pk is PA9 pa9 && HomeQuirks.HasEnteredReachingZA(pa9, data.EncounterOriginal.Context))
|
||||
return AllowHealedOr0; // HOME sets 0 PP for all moves. Healing / reassigning moves in ZA will heal individual indexes.
|
||||
|
||||
return OnlyHealed;
|
||||
}
|
||||
|
||||
if (data.IsStoredSlot(StorageSlotType.Party))
|
||||
return false;
|
||||
return Any;
|
||||
|
||||
return data.SlotOrigin switch
|
||||
{
|
||||
StorageSlotType.Box or StorageSlotType.GTS or StorageSlotType.BattleBox => GetIsStoredHealed(pk, data.EncounterOriginal),
|
||||
_ => false, // Deposited slots pass through party.
|
||||
_ => Any, // Deposited slots pass through party.
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the format is expected to have the Pokémon healed to full PP.
|
||||
/// </summary>
|
||||
private static bool GetIsStoredHealed(PKM pk, IEncounterTemplate enc) => pk switch
|
||||
private static MoveHealState GetIsStoredHealed(PKM pk, IEncounterTemplate enc) => pk switch
|
||||
{
|
||||
// Boxes accessible from anywhere; retain HP and PP
|
||||
PK9 => false,
|
||||
PK8 or PA8 or PB8 => false,
|
||||
PB7 => false,
|
||||
PK9 => Any,
|
||||
PK8 or PA8 or PB8 => Any,
|
||||
PB7 => Any,
|
||||
// Don't heal PP when deposited
|
||||
PK1 or PK2 => false,
|
||||
PK6 or PK7 => false,
|
||||
PK1 or PK2 => Any,
|
||||
PK6 or PK7 => Any,
|
||||
|
||||
// Do heal after capture/deposit
|
||||
SK2 => true,
|
||||
CK3 or XK3 => true,
|
||||
PK4 or RK4 or BK4 or PK5 => true,
|
||||
SK2 => OnlyHealed,
|
||||
CK3 or XK3 => OnlyHealed,
|
||||
PK4 or RK4 or BK4 or PK5 => OnlyHealed,
|
||||
|
||||
// Check if the encounter has left the boxes after being acquired by the player
|
||||
// only reachable by PK3?
|
||||
_ => HasLeftBoxAfterAcquisition(pk, enc),
|
||||
};
|
||||
|
||||
private static bool HasLeftBoxAfterAcquisition(PKM pk, IEncounterTemplate enc)
|
||||
private static MoveHealState HasLeftBoxAfterAcquisition(PKM pk, IEncounterTemplate enc)
|
||||
{
|
||||
if (enc.Context != pk.Context)
|
||||
return true; // Different context, assume it was traded and thus is not a wild->box
|
||||
return OnlyHealed; // Different context, assume it was traded and thus is not a wild->box
|
||||
if (pk.EVTotal != 0)
|
||||
return true; // EVs are not possible direct from wild encounters
|
||||
return OnlyHealed; // EVs are not possible direct from wild encounters
|
||||
|
||||
if (!Experience.IsAtLevelThreshold(pk.EXP, pk.PersonalInfo.EXPGrowth, out var current))
|
||||
return true; // gained experience
|
||||
return OnlyHealed; // gained experience
|
||||
|
||||
// Only scenario is if it was leveled up AND matches that exp threshold
|
||||
if (pk.Format >= 3) // has met level
|
||||
return pk.MetLevel != current;
|
||||
return !enc.IsLevelWithinRange(current);
|
||||
return pk.MetLevel != current ? OnlyHealed : Any;
|
||||
return !enc.IsLevelWithinRange(current) ? OnlyHealed : Any;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum MoveHealState
|
||||
{
|
||||
None, // Invalid result.
|
||||
|
||||
// Intermixed states for individual move indexes:
|
||||
Allow0 = 1 << 0, // A zero PP value is allowed.
|
||||
AllowHealed = 1 << 1, // Expect PP to be fully healed.
|
||||
AllowUsed = 1 << 2, // Any value in-between is allowed.
|
||||
|
||||
AllowHealedOr0 = Allow0 | AllowHealed, // Expect PP to be either fully healed or 0.
|
||||
|
||||
// Overall states for all move indexes:
|
||||
OnlyHealed = 1 << 3, // Expect PP of all indexes to be fully healed.
|
||||
Only0 = 1 << 4, // Expect PP of all indexes to be 0.
|
||||
|
||||
Any = Allow0 | AllowHealed | AllowUsed, // No expectation on PP values.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,9 +207,9 @@ public static bool GetTransferEC(PKM pk, out uint ec)
|
|||
return false;
|
||||
}
|
||||
|
||||
// get the current shiny xor value, check against the previous 1:8192 scheme
|
||||
var pid = pk.PID;
|
||||
var tmp = pid ^ pk.ID32;
|
||||
var XOR = (ushort)(tmp ^ (tmp >> 16));
|
||||
var XOR = ShinyUtil.GetShinyXor(pid, pk.ID32);
|
||||
|
||||
// Ensure we don't have a shiny.
|
||||
if (XOR >> 3 == 1) // Illegal, fix. (not 16<XOR>=8)
|
||||
|
|
|
|||
|
|
@ -147,29 +147,17 @@ public static bool IsMarkValidAlpha(PKM pk, bool wasAlpha)
|
|||
return true;
|
||||
if (!wasAlpha)
|
||||
return !m.RibbonMarkAlpha; // Shouldn't have the flag.
|
||||
if (!HasEnteredHOME300(pk))
|
||||
if (!HomeQuirks.HasEnteredSetAlphaMark(pk))
|
||||
return true; // Can be either state -- only HOME sets the flag.
|
||||
return m.RibbonMarkAlpha; // Should have the flag.
|
||||
}
|
||||
|
||||
private static bool HasEnteredHOME300(PKM pk)
|
||||
{
|
||||
// Mark is only set by HOME ingesting the data for the first time.
|
||||
// Before HOME 3.0.0, this mark was never set.
|
||||
// Could be okay as Gen8 format -- don't bother checking for "must have visited HOME 3.0.0+".
|
||||
if (pk is IHomeTrack { HasTracker: false })
|
||||
return false; // Hasn't been transferred to HOME yet.
|
||||
if (pk.LA && pk is PK8 or PB8 or PA8)
|
||||
return false; // Could have been moved prior to the HOME 3.0.0 update.
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the input can have the <see cref="IRibbonSetMark9.RibbonMarkAlpha"/> mark.
|
||||
/// </summary>
|
||||
public static bool IsMarkValidAlpha(IEncounterTemplate enc, PKM pk)
|
||||
{
|
||||
var expect = enc is IAlphaReadOnly { IsAlpha: true } && enc.Context != EntityContext.Gen9a; // TODO ZA HOME
|
||||
var expect = enc is IAlphaReadOnly { IsAlpha: true };
|
||||
return IsMarkValidAlpha(pk, expect);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -184,9 +184,6 @@ private void VerifyHOMETracker(LegalityAnalysis data, PKM pk)
|
|||
// - Transfer a 0-Tracker pk to HOME to get assigned a valid Tracker via the game it originated from.
|
||||
// - Don't make one up.
|
||||
}
|
||||
|
||||
if (pk.ZA != pk is PA9) // TODO: ZA HOME Compatibility - flag in/out transfers for now.
|
||||
data.AddLine(GetInvalid(TransferBad));
|
||||
}
|
||||
|
||||
public void VerifyVCEncounter(PKM pk, IEncounterTemplate original, EncounterTransfer7 transfer, LegalityAnalysis data)
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ public override ReadOnlySpan<byte> Write()
|
|||
{
|
||||
// Ensure PGT content is encrypted
|
||||
var clone = new PCD(Data.ToArray());
|
||||
clone.Gift.VerifyPKEncryption();
|
||||
clone.Gift.VerifyGiftEncryption();
|
||||
return clone.Data;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,17 +45,16 @@ public override byte Ball
|
|||
public int ItemSubID { get => ReadInt32LittleEndian(Data[0x8..]); set => WriteInt32LittleEndian(Data[0x8..], value); }
|
||||
public int PokewalkerCourseID { get => Data[0x4]; set => Data[0x4] = (byte)value; }
|
||||
|
||||
private Span<byte> DataGift => Data.Slice(8, PokeCrypto.SIZE_4PARTY);
|
||||
|
||||
public PK4 PK
|
||||
{
|
||||
get => field ??= new PK4(Data.Slice(8, PokeCrypto.SIZE_4PARTY).ToArray());
|
||||
get => field ??= new PK4(DataGift.ToArray());
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
var data = value.Data;
|
||||
bool zero = !data.ContainsAnyExcept<byte>(0); // all zero
|
||||
if (!zero)
|
||||
data = PokeCrypto.EncryptArray45(data);
|
||||
data.CopyTo(Data[8..]);
|
||||
field = value.Clone(); // cache the PK4 for future use
|
||||
value.Data.CopyTo(DataGift);
|
||||
VerifyGiftEncryption();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -63,29 +62,27 @@ public override ReadOnlySpan<byte> Write()
|
|||
{
|
||||
// Ensure PGT content is encrypted
|
||||
var clone = new PGT(Data.ToArray());
|
||||
clone.VerifyPKEncryption();
|
||||
clone.VerifyGiftEncryption();
|
||||
return clone.Data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Double-checks the encryption of the gift data for Pokémon data.
|
||||
/// Double-checks the encryption of the gift data.
|
||||
/// </summary>
|
||||
/// <returns>True if data was encrypted, false if the data was not modified.</returns>
|
||||
public bool VerifyPKEncryption()
|
||||
public bool VerifyGiftEncryption()
|
||||
{
|
||||
if (GiftType is not (Pokémon or PokémonEgg))
|
||||
return false; // not encrypted
|
||||
if (ReadUInt32LittleEndian(Data[(0x64 + 8)..]) != 0)
|
||||
return false; // already encrypted (unused PK4 field, zero)
|
||||
EncryptPK();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void EncryptPK()
|
||||
{
|
||||
var span = Data.Slice(8, PokeCrypto.SIZE_4PARTY);
|
||||
var ekdata = PokeCrypto.EncryptArray45(span);
|
||||
ekdata.CopyTo(span);
|
||||
var gift = DataGift;
|
||||
var isEmpty = !gift.ContainsAnyExcept<byte>(0); // all zero
|
||||
if (isEmpty) // shouldn't ever be empty, just return if so.
|
||||
return false;
|
||||
if (PokeCrypto.IsEncrypted45(gift)) // unused PK4 ribbon bits
|
||||
return false;
|
||||
PokeCrypto.Encrypt45(gift);
|
||||
return true;
|
||||
}
|
||||
|
||||
public GiftType4 GiftType { get => (GiftType4)CardType; set => CardType = (byte)value; }
|
||||
|
|
|
|||
|
|
@ -39,6 +39,13 @@ public override int CardID
|
|||
set => WriteUInt16LittleEndian(Data[0x8..], (ushort)value);
|
||||
}
|
||||
|
||||
public bool IsHOMEGift => CardID >= 9000;
|
||||
public int HomeBaseIV => CardID switch
|
||||
{
|
||||
>= 9031 and <= 9033 => 20,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
public byte RestrictVersion { get => Data[0xE]; set => Data[0xE] = value; } // 0x01 = ZA only (only one game in this Context, so always ZA).
|
||||
public byte CardFlags { get => Data[0x10]; set => Data[0x10] = value; }
|
||||
public GiftType CardType { get => (GiftType)Data[0x11]; set => Data[0x11] = (byte)value; }
|
||||
|
|
@ -302,7 +309,7 @@ public bool CanBeAnyLanguage()
|
|||
|
||||
public bool CanHaveLanguage(int language)
|
||||
{
|
||||
if (language is < (int)LanguageID.Japanese or > (int)LanguageID.ChineseT)
|
||||
if (language is < (int)LanguageID.Japanese or > (int)LanguageID.SpanishL)
|
||||
return false;
|
||||
|
||||
if (CanBeAnyLanguage())
|
||||
|
|
@ -326,7 +333,7 @@ public bool CanHaveLanguage(int language)
|
|||
private static int GetLanguageIndex(int language)
|
||||
{
|
||||
var lang = (LanguageID) language;
|
||||
if (lang is < LanguageID.Japanese or LanguageID.UNUSED_6 or > LanguageID.ChineseT)
|
||||
if (lang is < LanguageID.Japanese or LanguageID.UNUSED_6 or > LanguageID.SpanishL)
|
||||
return (int) LanguageID.English; // fallback
|
||||
return lang < LanguageID.UNUSED_6 ? language - 1 : language - 2;
|
||||
}
|
||||
|
|
@ -360,7 +367,7 @@ public override string OriginalTrainerName
|
|||
get => GetOT(Language);
|
||||
set
|
||||
{
|
||||
for (int i = 1; i <= (int)LanguageID.ChineseT; i++)
|
||||
for (int i = 1; i <= (int)LanguageID.SpanishL; i++)
|
||||
SetOT(i, value);
|
||||
}
|
||||
}
|
||||
|
|
@ -463,16 +470,6 @@ public override PA9 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
|||
pk.IsNicknamed = GetIsNicknamed(language);
|
||||
pk.Nickname = pk.IsNicknamed ? GetNickname(language) : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation);
|
||||
|
||||
// No ribbons set.
|
||||
// for (var i = 0; i < RibbonBytesCount; i++)
|
||||
// {
|
||||
// var ribbon = GetRibbonAtIndex(i);
|
||||
// if (ribbon == RibbonByteNone)
|
||||
// continue;
|
||||
// pk.SetRibbon(ribbon);
|
||||
// pk.AffixedRibbon = (sbyte)ribbon;
|
||||
// }
|
||||
|
||||
SetPINGA(pk, criteria, pi);
|
||||
SetMoves(currentLevel, pk, pi);
|
||||
pk.HealPP();
|
||||
|
|
@ -481,6 +478,18 @@ public override PA9 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
|||
SetEggMetData(pk);
|
||||
pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship;
|
||||
|
||||
if (IsHOMEGift)
|
||||
{
|
||||
for (var i = 0; i < RibbonBytesCount; i++)
|
||||
{
|
||||
var ribbon = GetRibbonAtIndex(i);
|
||||
if (ribbon == RibbonByteNone)
|
||||
continue;
|
||||
pk.SetRibbon(ribbon);
|
||||
}
|
||||
pk.Scale = pk.HeightScalar = pk.WeightScalar = Scale == 256 ? (byte)rnd.Next(256) : (byte)Scale;
|
||||
}
|
||||
|
||||
pk.ResetPartyStats();
|
||||
pk.RefreshChecksum();
|
||||
return pk;
|
||||
|
|
@ -515,6 +524,17 @@ private void SetEggMetData(PA9 pk)
|
|||
}
|
||||
|
||||
private void SetPINGA(PA9 pk, EncounterCriteria criteria, PersonalInfo9ZA pi)
|
||||
{
|
||||
if (IsHOMEGift) // Do not use LumioseRNG for HOME gifts
|
||||
{
|
||||
pk.Nature = pk.StatNature = criteria.GetNature((sbyte)Nature == -1 ? Nature.Random : Nature);
|
||||
pk.Gender = criteria.GetGender(Gender, pi);
|
||||
var av = GetAbilityIndex(criteria, AbilityType);
|
||||
pk.RefreshAbility(av);
|
||||
SetPID(pk);
|
||||
SetIVs(pk);
|
||||
}
|
||||
else
|
||||
{
|
||||
var param = GetParams(pi);
|
||||
ulong init = Util.Rand.Rand64();
|
||||
|
|
@ -525,6 +545,14 @@ private void SetPINGA(PA9 pk, EncounterCriteria criteria, PersonalInfo9ZA pi)
|
|||
if (PIDType is not (ShinyType8.Never or ShinyType8.Random))
|
||||
pk.PID = GetPID(pk, PIDType);
|
||||
}
|
||||
}
|
||||
|
||||
private int GetAbilityIndex(in EncounterCriteria criteria, int type) => type switch
|
||||
{
|
||||
00 or 01 or 02 => type, // Fixed 0/1/2
|
||||
03 or 04 => criteria.GetAbilityFromNumber(Ability), // 0/1 or 0/1/H
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type)),
|
||||
};
|
||||
|
||||
public override AbilityPermission Ability => AbilityType switch
|
||||
{
|
||||
|
|
@ -564,6 +592,39 @@ private static uint GetAntishiny(ITrainerID32 tr)
|
|||
return pid;
|
||||
}
|
||||
|
||||
private void SetPID(PA9 pk)
|
||||
{
|
||||
pk.PID = GetPID(pk, PIDType);
|
||||
}
|
||||
|
||||
private void SetIVs(PA9 pk)
|
||||
{
|
||||
Span<int> finalIVs = stackalloc int[6];
|
||||
GetIVs(finalIVs);
|
||||
var ivflag = finalIVs.IndexOfAny(0xFC, 0xFD, 0xFE);
|
||||
var rng = Util.Rand;
|
||||
if (ivflag == -1) // Random IVs
|
||||
{
|
||||
for (int i = 0; i < finalIVs.Length; i++)
|
||||
{
|
||||
if (finalIVs[i] > 31)
|
||||
finalIVs[i] = rng.Next(32);
|
||||
}
|
||||
}
|
||||
else // 1/2/3 perfect IVs
|
||||
{
|
||||
int IVCount = finalIVs[ivflag] - 0xFB;
|
||||
do { finalIVs[rng.Next(6)] = 31; }
|
||||
while (finalIVs.Count(31) < IVCount);
|
||||
for (int i = 0; i < finalIVs.Length; i++)
|
||||
{
|
||||
if (finalIVs[i] != 31)
|
||||
finalIVs[i] = IsHOMEGift ? HomeBaseIV : rng.Next(32); // HOME ZA-starters gifts have 20 in non-perfect IVs
|
||||
}
|
||||
}
|
||||
pk.SetIVs(finalIVs);
|
||||
}
|
||||
|
||||
public override bool IsMatchExact(PKM pk, EvoCriteria evo)
|
||||
{
|
||||
if (!IsEgg)
|
||||
|
|
@ -641,6 +702,20 @@ public override bool IsMatchExact(PKM pk, EvoCriteria evo)
|
|||
if (pk is IAlphaReadOnly a && a.IsAlpha != IsAlpha)
|
||||
return true;
|
||||
|
||||
if (IsHOMEGift)
|
||||
{
|
||||
if (pk.FlawlessIVCount != FlawlessIVCount)
|
||||
return false; // HOME ZA-starters have non-perfect IVs to 20, so IVs at 31 can't exceed the flawless count.
|
||||
|
||||
Span<int> IVs = stackalloc int[6];
|
||||
pk.GetIVs(IVs);
|
||||
foreach (var iv in IVs)
|
||||
{
|
||||
if (iv != 31 && iv != HomeBaseIV)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// PID Types 0 and 1 do not use the fixed PID value.
|
||||
// Values 2,3 are specific shiny states, and 4 is fixed value.
|
||||
// 2,3,4 can change if it is a traded egg to ensure the same shiny state.
|
||||
|
|
@ -676,11 +751,11 @@ private bool IsMatchTrainerName(ReadOnlySpan<byte> trainerTrash, PKM pk)
|
|||
|
||||
protected override bool IsMatchDeferred(PKM pk) => false;
|
||||
|
||||
protected override bool IsMatchPartial(PKM pk) => TryGetSeed(pk, out _) != SeedCorrelationResult.Success;
|
||||
protected override bool IsMatchPartial(PKM pk) => !IsHOMEGift && TryGetSeed(pk, out _) != SeedCorrelationResult.Success;
|
||||
|
||||
#region Lazy Ribbon Implementation
|
||||
|
||||
private static bool HasRibbon(RibbonIndex _) => false; // HasRibbon(index); // ZA is hard-coded to never set ribbons, so we need to return false for validation/setting.
|
||||
private bool HasRibbon(RibbonIndex index) => IsHOMEGift && this.GetRibbonIndex(index);
|
||||
public bool RibbonEarth { get => HasRibbon(Earth); set => this.SetRibbonIndex(Earth, value); }
|
||||
public bool RibbonNational { get => HasRibbon(National); set => this.SetRibbonIndex(National, value); }
|
||||
public bool RibbonCountry { get => HasRibbon(Country); set => this.SetRibbonIndex(Country, value); }
|
||||
|
|
|
|||
|
|
@ -581,7 +581,17 @@ public override bool IsMatchExact(PKM pk, EvoCriteria evo)
|
|||
}
|
||||
|
||||
if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, Context, pk.Context))
|
||||
{
|
||||
// Small bypass for Greninja-Ash when mega evolved (normal and Ash map to the same Mega-3, managed by game)
|
||||
if (this is { Species: (ushort)Core.Species.Greninja, Form: 1 } && pk is { Context: EntityContext.Gen9a, Form: 3 })
|
||||
{
|
||||
// Allow
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsEgg)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -725,21 +725,8 @@ public override bool IsMatchExact(PKM pk, EvoCriteria evo)
|
|||
|
||||
if (pk is IScaledSize s)
|
||||
{
|
||||
if (!Encounter9RNG.IsHeightMatchSV(pk, (byte)HeightValue))
|
||||
if (!IsMatchSize(pk, s))
|
||||
return false;
|
||||
if (s.WeightScalar != WeightValue)
|
||||
return false;
|
||||
|
||||
if (!IsBeforePatch120(CardID) || (pk.MetDate is { } valid && !IsBeforePatch120(valid)))
|
||||
{
|
||||
// S/V 1.2.0 added scale specification.
|
||||
if (Scale != 256)
|
||||
{
|
||||
var current = pk is IScaledSize3 s3 ? s3.Scale : s.HeightScalar;
|
||||
if (Scale != current)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PID Types 0 and 1 do not use the fixed PID value.
|
||||
|
|
@ -751,6 +738,34 @@ public override bool IsMatchExact(PKM pk, EvoCriteria evo)
|
|||
return pk.PID == GetPID(pk, type);
|
||||
}
|
||||
|
||||
private bool IsMatchSize(PKM pk, IScaledSize s)
|
||||
{
|
||||
var cardID = CardID;
|
||||
if (HomeQuirks.IsGlitchedHisuianZoroarkSV(pk, s, cardID))
|
||||
return true;
|
||||
|
||||
// Check for strict match of Height/Weight.
|
||||
if (!Encounter9RNG.IsHeightMatchSV(pk, (byte)HeightValue))
|
||||
return false;
|
||||
if (s.WeightScalar != WeightValue)
|
||||
return false;
|
||||
|
||||
// S/V 1.2.0 added scale specification to all available (replaced) and future cards.
|
||||
// If it is a pre-patch card, double check that the date was possible to redeem before the patch.
|
||||
if (IsBeforePatch120(cardID) && (pk.MetDate is not { } valid || IsBeforePatch120(valid)))
|
||||
return true; // No scale to check, can be anything random triangular = rand(127) + rand(128);
|
||||
|
||||
if (Scale == 256) // Random
|
||||
return true; // Allowed to be random triangular.
|
||||
|
||||
// Check for strict match.
|
||||
var current = pk is IScaledSize3 s3 ? s3.Scale : s.HeightScalar;
|
||||
if (Scale != current)
|
||||
return false;
|
||||
|
||||
return true; // Everything matches.
|
||||
}
|
||||
|
||||
private bool IsMatchTrainerName(ReadOnlySpan<byte> trainerTrash, PKM pk)
|
||||
{
|
||||
Span<char> trainerName = stackalloc char[12];
|
||||
|
|
|
|||
|
|
@ -22,19 +22,11 @@ public sealed class BK4 : G4PKM
|
|||
public override int SIZE_STORED => PokeCrypto.SIZE_4STORED;
|
||||
public override EntityContext Context => EntityContext.Gen4;
|
||||
public override PersonalInfo4 PersonalInfo => PersonalTable.HGSS[Species];
|
||||
|
||||
public override byte[] DecryptedBoxData => EncryptedBoxData;
|
||||
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt4BE(stored);
|
||||
protected override void EncryptParty(Span<byte> party) { }
|
||||
|
||||
public override bool Valid => ChecksumValid || (Sanity == 0 && Species <= MaxSpeciesID);
|
||||
|
||||
public static BK4 ReadUnshuffle(ReadOnlySpan<byte> data)
|
||||
{
|
||||
var unshuffled = PokeCrypto.DecryptArray4BE(data);
|
||||
var result = new BK4(unshuffled);
|
||||
result.RefreshChecksum();
|
||||
return result;
|
||||
}
|
||||
|
||||
public BK4(Memory<byte> data) : base(data)
|
||||
{
|
||||
Sanity = 0x4000;
|
||||
|
|
@ -302,12 +294,6 @@ public override ushort MetLocationDP
|
|||
// Methods
|
||||
protected override ushort CalculateChecksum() => Checksums.Add16BigEndian(Data[8..PokeCrypto.SIZE_4STORED]);
|
||||
|
||||
protected override byte[] Encrypt()
|
||||
{
|
||||
RefreshChecksum();
|
||||
return PokeCrypto.EncryptArray4BE(Data);
|
||||
}
|
||||
|
||||
public PK4 ConvertToPK4()
|
||||
{
|
||||
PK4 pk4 = ConvertTo<PK4>();
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ public sealed class CK3(Memory<byte> Raw) : G3PKM(Raw), IShadowCapture, ISeparat
|
|||
public override EntityContext Context => EntityContext.Gen3;
|
||||
public override PersonalInfo3 PersonalInfo => PersonalTable.RS[Species];
|
||||
public override CK3 Clone() => new(Data.ToArray());
|
||||
protected override void EncryptStored(Span<byte> stored) { }
|
||||
protected override void EncryptParty(Span<byte> party) { }
|
||||
|
||||
// Trash Bytes
|
||||
public override Span<byte> OriginalTrainerTrash => Data.Slice(0x18, 22);
|
||||
|
|
@ -238,8 +240,6 @@ public bool IsFatefulValid(bool japanese)
|
|||
public const int Purified = -100;
|
||||
public bool IsShadow => ShadowID != 0 && Purification != Purified;
|
||||
|
||||
protected override byte[] Encrypt() => Data.ToArray();
|
||||
|
||||
public PK3 ConvertToPK3()
|
||||
{
|
||||
var pk = ConvertTo<PK3>();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Side game data for <see cref="PA8"/> data transferred into HOME.
|
||||
/// </summary>
|
||||
public sealed class GameDataPA8 : HomeOptional1, IGameDataSide<PA8>, IScaledSizeAbsolute, IScaledSize3, IGameDataSplitAbility, IPokerusStatus, IAlpha
|
||||
public sealed class GameDataPA8 : HomeOptional1, IGameDataSide<PA8>, IScaledSizeAbsolute, IScaledSize3, IGameDataSplitAbility, IPokerusStatus, IAlpha, IGameDataSidePP
|
||||
{
|
||||
private const HomeGameDataFormat ExpectFormat = HomeGameDataFormat.PA8;
|
||||
private const int SIZE = HomeCrypto.SIZE_2GAME_PA8;
|
||||
|
|
@ -29,10 +29,10 @@ public sealed class GameDataPA8 : HomeOptional1, IGameDataSide<PA8>, IScaledSize
|
|||
public ushort Move3 { get => ReadUInt16LittleEndian(Data[0x09..]); set => WriteUInt16LittleEndian(Data[0x09..], value); }
|
||||
public ushort Move4 { get => ReadUInt16LittleEndian(Data[0x0B..]); set => WriteUInt16LittleEndian(Data[0x0B..], value); }
|
||||
|
||||
public int Move1_PP { get => Data[0x0D]; set => Data[0x0D] = (byte)value; }
|
||||
public int Move2_PP { get => Data[0x0E]; set => Data[0x0E] = (byte)value; }
|
||||
public int Move3_PP { get => Data[0x0F]; set => Data[0x0F] = (byte)value; }
|
||||
public int Move4_PP { get => Data[0x10]; set => Data[0x10] = (byte)value; }
|
||||
public byte Move1_PP { get => Data[0x0D]; set => Data[0x0D] = value; }
|
||||
public byte Move2_PP { get => Data[0x0E]; set => Data[0x0E] = value; }
|
||||
public byte Move3_PP { get => Data[0x0F]; set => Data[0x0F] = value; }
|
||||
public byte Move4_PP { get => Data[0x10]; set => Data[0x10] = value; }
|
||||
public ushort RelearnMove1 { get => ReadUInt16LittleEndian(Data[0x11..]); set => WriteUInt16LittleEndian(Data[0x11..], value); }
|
||||
public ushort RelearnMove2 { get => ReadUInt16LittleEndian(Data[0x13..]); set => WriteUInt16LittleEndian(Data[0x13..], value); }
|
||||
public ushort RelearnMove3 { get => ReadUInt16LittleEndian(Data[0x15..]); set => WriteUInt16LittleEndian(Data[0x15..], value); }
|
||||
|
|
@ -67,10 +67,10 @@ public sealed class GameDataPA8 : HomeOptional1, IGameDataSide<PA8>, IScaledSize
|
|||
|
||||
// Not stored.
|
||||
public PersonalInfo GetPersonalInfo(ushort species, byte form) => PersonalTable.LA.GetFormEntry(species, form);
|
||||
public int Move1_PPUps { get => 0; set { } }
|
||||
public int Move2_PPUps { get => 0; set { } }
|
||||
public int Move3_PPUps { get => 0; set { } }
|
||||
public int Move4_PPUps { get => 0; set { } }
|
||||
public byte Move1_PPUps { get => 0; set { } }
|
||||
public byte Move2_PPUps { get => 0; set { } }
|
||||
public byte Move3_PPUps { get => 0; set { } }
|
||||
public byte Move4_PPUps { get => 0; set { } }
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
// TODO HOME ZA -- Simply copied from SV, needs to be updated to ZA when official support is added.
|
||||
|
||||
/// <summary>
|
||||
/// Side game data for <see cref="PA9"/> data transferred into HOME.
|
||||
/// </summary>
|
||||
|
|
@ -26,145 +24,75 @@ public sealed class GameDataPA9 : HomeOptional1, IGameDataSide<PA9>, IScaledSize
|
|||
public ushort Move2 { get => ReadUInt16LittleEndian(Data[0x03..]); set => WriteUInt16LittleEndian(Data[0x03..], value); }
|
||||
public ushort Move3 { get => ReadUInt16LittleEndian(Data[0x05..]); set => WriteUInt16LittleEndian(Data[0x05..], value); }
|
||||
public ushort Move4 { get => ReadUInt16LittleEndian(Data[0x07..]); set => WriteUInt16LittleEndian(Data[0x07..], value); }
|
||||
public bool IsAlpha { get => Data[0x9] != 0; set => Data[0x9] = value ? (byte)1 : (byte)0; }
|
||||
|
||||
public int Move1_PP { get => Data[0x09]; set => Data[0x09] = (byte)value; }
|
||||
public int Move2_PP { get => Data[0x0A]; set => Data[0x0A] = (byte)value; }
|
||||
public int Move3_PP { get => Data[0x0B]; set => Data[0x0B] = (byte)value; }
|
||||
public int Move4_PP { get => Data[0x0C]; set => Data[0x0C] = (byte)value; }
|
||||
public int Move1_PPUps { get => Data[0x0D]; set => Data[0x0D] = (byte)value; }
|
||||
public int Move2_PPUps { get => Data[0x0E]; set => Data[0x0E] = (byte)value; }
|
||||
public int Move3_PPUps { get => Data[0x0F]; set => Data[0x0F] = (byte)value; }
|
||||
public int Move4_PPUps { get => Data[0x10]; set => Data[0x10] = (byte)value; }
|
||||
// Second set of flags is stored in the second block of PA9, devs declared it matching block order, not sequential bitflag order.
|
||||
private const int PlusStartB = 0xA;
|
||||
internal const int PlusCountB = PA9.PlusCount1;
|
||||
private const int PlusLengthB = PlusCountB / 8; // 12
|
||||
public Span<byte> PlusFlagsB => Data.Slice(PlusStartB, PlusLengthB);
|
||||
|
||||
public ushort RelearnMove1 { get => ReadUInt16LittleEndian(Data[0x11..]); set => WriteUInt16LittleEndian(Data[0x11..], value); }
|
||||
public ushort RelearnMove2 { get => ReadUInt16LittleEndian(Data[0x13..]); set => WriteUInt16LittleEndian(Data[0x13..], value); }
|
||||
public ushort RelearnMove3 { get => ReadUInt16LittleEndian(Data[0x15..]); set => WriteUInt16LittleEndian(Data[0x15..], value); }
|
||||
public ushort RelearnMove4 { get => ReadUInt16LittleEndian(Data[0x17..]); set => WriteUInt16LittleEndian(Data[0x17..], value); }
|
||||
public bool IsAlpha { get => Data[0x19] != 0; set => Data[0x19] = value ? (byte)1 : (byte)0; }
|
||||
// 0x1A Padding (???) TODO HOME ZA; this is adapted from SV's structure, replacing the 2 byte properties for Tera Type.
|
||||
public byte Ball { get => Data[0x1B]; set => Data[0x1B] = value; }
|
||||
public ushort EggLocation { get => ReadUInt16LittleEndian(Data[0x1C..]); set => WriteUInt16LittleEndian(Data[0x1C..], value); }
|
||||
public ushort MetLocation { get => ReadUInt16LittleEndian(Data[0x1E..]); set => WriteUInt16LittleEndian(Data[0x1E..], value); }
|
||||
// First set of flags is stored in the third block of PA9, devs declared it matching block order, not sequential bitflag order.
|
||||
private const int PlusStartC = PlusStartB + PlusLengthB;
|
||||
internal const int PlusCountC = PA9.PlusCount0;
|
||||
private const int PlusLengthC = PlusCountC / 8; // 33
|
||||
public Span<byte> PlusFlagsC => Data.Slice(PlusStartC, PlusLengthC);
|
||||
|
||||
private const int RecordStartBase = 0x20;
|
||||
internal const int COUNT_RECORD_BASE = PA9.COUNT_RECORD_BASE; // Up to 200 TM flags, but not all are used.
|
||||
private const int RecordLengthBase = COUNT_RECORD_BASE / 8; // 0x19 bytes, 8 bits
|
||||
public Span<byte> RecordFlagsBase => Data.Slice(RecordStartBase, RecordLengthBase);
|
||||
public byte Ball { get => Data[0x37]; set => Data[0x37] = value; }
|
||||
public ushort EggLocation { get => ReadUInt16LittleEndian(Data[0x38..]); set => WriteUInt16LittleEndian(Data[0x38..], value); }
|
||||
public ushort MetLocation { get => ReadUInt16LittleEndian(Data[0x3A..]); set => WriteUInt16LittleEndian(Data[0x3A..], value); }
|
||||
public byte Obedience_Level { get => Data[0x3C]; set => Data[0x3C] = value; }
|
||||
public ushort Ability { get => ReadUInt16LittleEndian(Data[0x3D..]); set => WriteUInt16LittleEndian(Data[0x3D..], value); }
|
||||
public byte AbilityNumber { get => Data[0x3F]; set => Data[0x3F] = value; }
|
||||
|
||||
// Rev2 Additions
|
||||
public byte Obedience_Level { get => Data[0x39]; set => Data[0x39] = value; }
|
||||
public ushort Ability { get => ReadUInt16LittleEndian(Data[0x3A..]); set => WriteUInt16LittleEndian(Data[0x3A..], value); }
|
||||
public byte AbilityNumber { get => Data[0x3C]; set => Data[0x3C] = value; }
|
||||
// not sure how best to handle the dropping of these
|
||||
public ushort RelearnMove1 { get => 0; set { } }
|
||||
public ushort RelearnMove2 { get => 0; set { } }
|
||||
public ushort RelearnMove3 { get => 0; set { } }
|
||||
public ushort RelearnMove4 { get => 0; set { } }
|
||||
|
||||
// Rev3 Additions
|
||||
private const int RecordStartDLC = 0x3D;
|
||||
internal const int COUNT_RECORD_DLC = PA9.COUNT_RECORD_DLC; // 13 additional bytes allocated for DLC1/2 TM Flags
|
||||
private const int RecordLengthDLC = COUNT_RECORD_DLC / 8;
|
||||
public Span<byte> RecordFlagsDLC => Data.Slice(RecordStartDLC, RecordLengthDLC);
|
||||
|
||||
// Rev4 Additions (ZA)
|
||||
private const int PlusStart0 = 0x4A;
|
||||
internal const int PlusCount0 = PA9.PlusCount0;
|
||||
private const int PlusLength0 = PlusCount0 / 8;
|
||||
public Span<byte> PlusFlags0 => Data.Slice(PlusStart0, PlusLength0);
|
||||
|
||||
private const int PlusStart1 = PlusStart0 + PlusLength0; // 0x6B
|
||||
internal const int PlusCount1 = PA9.PlusCount1;
|
||||
private const int PlusLength1 = PlusCount1 / 8;
|
||||
public Span<byte> PlusFlags1 => Data.Slice(PlusStart1, PlusLength1);
|
||||
|
||||
#endregion
|
||||
|
||||
#region TM Flag Methods
|
||||
public bool GetMoveRecordFlag(int index)
|
||||
{
|
||||
if ((uint)index >= COUNT_RECORD_BASE)
|
||||
return GetMoveRecordFlagDLC(index - COUNT_RECORD_BASE);
|
||||
int ofs = index >> 3;
|
||||
return FlagUtil.GetFlag(Data, RecordStartBase + ofs, index & 7);
|
||||
}
|
||||
|
||||
private bool GetMoveRecordFlagDLC(int index)
|
||||
{
|
||||
if ((uint)index >= COUNT_RECORD_DLC)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
int ofs = index >> 3;
|
||||
return FlagUtil.GetFlag(Data, RecordStartDLC + ofs, index & 7);
|
||||
}
|
||||
|
||||
public void SetMoveRecordFlag(int index, bool value = true)
|
||||
{
|
||||
if ((uint)index >= COUNT_RECORD_BASE)
|
||||
{
|
||||
SetMoveRecordFlagDLC(value, index - COUNT_RECORD_BASE);
|
||||
return;
|
||||
}
|
||||
int ofs = index >> 3;
|
||||
FlagUtil.SetFlag(Data, RecordStartBase + ofs, index & 7, value);
|
||||
}
|
||||
|
||||
private void SetMoveRecordFlagDLC(bool value, int index)
|
||||
{
|
||||
if ((uint)index >= COUNT_RECORD_DLC)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
int ofs = index >> 3;
|
||||
FlagUtil.SetFlag(Data, RecordStartDLC + ofs, index & 7, value);
|
||||
}
|
||||
|
||||
public bool GetMoveRecordFlagAny() => GetMoveRecordFlagAnyBase() || GetMoveRecordFlagAnyDLC();
|
||||
private bool GetMoveRecordFlagAnyBase() => RecordFlagsBase.ContainsAnyExcept<byte>(0);
|
||||
private bool GetMoveRecordFlagAnyDLC() => RecordFlagsDLC.ContainsAnyExcept<byte>(0);
|
||||
|
||||
public void ClearMoveRecordFlags()
|
||||
{
|
||||
ClearMoveRecordFlagsBase();
|
||||
ClearMoveRecordFlagsDLC();
|
||||
}
|
||||
|
||||
private void ClearMoveRecordFlagsBase() => RecordFlagsBase.Clear();
|
||||
private void ClearMoveRecordFlagsDLC() => RecordFlagsDLC.Clear();
|
||||
#endregion
|
||||
|
||||
#region Plus Moves
|
||||
|
||||
public bool GetMovePlusFlag(int index)
|
||||
{
|
||||
if ((uint)index >= PlusCount0)
|
||||
return GetMovePlusFlag1(index - PlusCount0);
|
||||
if ((uint)index >= PlusCountC)
|
||||
return GetMovePlusFlag1(index - PlusCountC);
|
||||
int ofs = index >> 3;
|
||||
return FlagUtil.GetFlag(Data, PlusStart0 + ofs, index & 7);
|
||||
return FlagUtil.GetFlag(Data, PlusStartC + ofs, index & 7);
|
||||
}
|
||||
|
||||
private bool GetMovePlusFlag1(int index)
|
||||
{
|
||||
if ((uint)index >= PlusCount1)
|
||||
if ((uint)index >= PlusCountB)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
int ofs = index >> 3;
|
||||
return FlagUtil.GetFlag(Data, PlusStart1 + ofs, index & 7);
|
||||
return FlagUtil.GetFlag(Data, PlusStartB + ofs, index & 7);
|
||||
}
|
||||
|
||||
public void SetMovePlusFlag(int index, bool value = true)
|
||||
{
|
||||
if ((uint)index >= PlusCount0)
|
||||
if ((uint)index >= PlusCountC)
|
||||
{
|
||||
SetMovePlusFlag1(value, index - PlusCount0);
|
||||
SetMovePlusFlag1(value, index - PlusCountC);
|
||||
return;
|
||||
}
|
||||
int ofs = index >> 3;
|
||||
FlagUtil.SetFlag(Data, PlusStart0 + ofs, index & 7, value);
|
||||
FlagUtil.SetFlag(Data, PlusStartC + ofs, index & 7, value);
|
||||
}
|
||||
|
||||
private void SetMovePlusFlag1(bool value, int index)
|
||||
{
|
||||
if ((uint)index >= PlusCount1)
|
||||
if ((uint)index >= PlusCountB)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
int ofs = index >> 3;
|
||||
FlagUtil.SetFlag(Data, PlusStart1 + ofs, index & 7, value);
|
||||
FlagUtil.SetFlag(Data, PlusStartB + ofs, index & 7, value);
|
||||
}
|
||||
|
||||
public bool GetMovePlusFlagAny() => GetMovePlusFlagAny0() || GetMovePlusFlagAny1();
|
||||
private bool GetMovePlusFlagAny0() => PlusFlags0.ContainsAnyExcept<byte>(0);
|
||||
private bool GetMovePlusFlagAny1() => PlusFlags1.ContainsAnyExcept<byte>(0);
|
||||
private bool GetMovePlusFlagAny0() => PlusFlagsC.ContainsAnyExcept<byte>(0);
|
||||
private bool GetMovePlusFlagAny1() => PlusFlagsB.ContainsAnyExcept<byte>(0);
|
||||
|
||||
public void ClearMovePlusFlags()
|
||||
{
|
||||
|
|
@ -172,8 +100,8 @@ public void ClearMovePlusFlags()
|
|||
ClearMovePlusFlags1();
|
||||
}
|
||||
|
||||
private void ClearMovePlusFlags0() => PlusFlags0.Clear();
|
||||
private void ClearMovePlusFlags1() => PlusFlags1.Clear();
|
||||
private void ClearMovePlusFlags0() => PlusFlagsC.Clear();
|
||||
private void ClearMovePlusFlags1() => PlusFlagsB.Clear();
|
||||
#endregion
|
||||
|
||||
#region Conversion
|
||||
|
|
@ -184,10 +112,12 @@ public void CopyTo(PA9 pk, PKH pkh)
|
|||
{
|
||||
this.CopyTo(pk);
|
||||
pk.Scale = Scale;
|
||||
PlusFlags0.CopyTo(pk.PlusFlags0);
|
||||
PlusFlags1.CopyTo(pk.PlusFlags1);
|
||||
RecordFlagsBase.CopyTo(pk.RecordFlagsBase);
|
||||
RecordFlagsDLC.CopyTo(pk.RecordFlagsDLC);
|
||||
pk.IsAlpha = IsAlpha;
|
||||
if (IsAlpha)
|
||||
pk.Scale = pk.HeightScalar = pk.WeightScalar = 255;
|
||||
|
||||
PlusFlagsC.CopyTo(pk.PlusFlags0);
|
||||
PlusFlagsB.CopyTo(pk.PlusFlags1);
|
||||
pk.ObedienceLevel = Obedience_Level;
|
||||
pk.Ability = Ability;
|
||||
pk.AbilityNumber = AbilityNumber;
|
||||
|
|
@ -197,10 +127,9 @@ public void CopyFrom(PA9 pk, PKH pkh)
|
|||
{
|
||||
this.CopyFrom(pk);
|
||||
pkh.HeightScalar = Scale = pk.Scale; // Overwrite Height
|
||||
pk.PlusFlags0.CopyTo(PlusFlags0);
|
||||
pk.PlusFlags1.CopyTo(PlusFlags1);
|
||||
pk.RecordFlagsBase.CopyTo(RecordFlagsBase);
|
||||
pk.RecordFlagsDLC.CopyTo(RecordFlagsDLC);
|
||||
IsAlpha = pk.IsAlpha;
|
||||
pk.PlusFlags0.CopyTo(PlusFlagsC);
|
||||
pk.PlusFlags1.CopyTo(PlusFlagsB);
|
||||
Obedience_Level = pk.ObedienceLevel;
|
||||
Ability = (ushort)pk.Ability;
|
||||
AbilityNumber = (byte)pk.AbilityNumber;
|
||||
|
|
@ -211,6 +140,7 @@ public PA9 ConvertToPKM(PKH pkh)
|
|||
var pk = new PA9();
|
||||
pkh.CopyTo(pk);
|
||||
CopyTo(pk, pkh);
|
||||
pk.Move1_PP = pk.Move2_PP = pk.Move3_PP = pk.Move4_PP = 0; // Match HOME's behavior of zero PP.
|
||||
|
||||
pk.ResetPartyStats();
|
||||
pk.RefreshChecksum();
|
||||
|
|
@ -244,11 +174,8 @@ public PA9 ConvertToPKM(PKH pkh)
|
|||
return result;
|
||||
}
|
||||
|
||||
private static IGameDataSide? GetNearestNeighbor(PKH pkh) => pkh.DataPK9 as IGameDataSide
|
||||
?? pkh.DataPK8 as IGameDataSide
|
||||
?? pkh.DataPB8 as IGameDataSide
|
||||
?? pkh.DataPB7 as IGameDataSide
|
||||
?? pkh.DataPA8;
|
||||
private static IGameDataSide? GetNearestNeighbor(PKH pkh)
|
||||
=> pkh.DataPK9 ?? pkh.DataPK8 ?? pkh.DataPB8 ?? pkh.DataPB7 ?? pkh.DataPA8 as IGameDataSide;
|
||||
|
||||
public void InitializeFrom(IGameDataSide side, PKH pkh)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Side game data for <see cref="PB7"/> data transferred into HOME.
|
||||
/// </summary>
|
||||
public sealed class GameDataPB7 : HomeOptional1, IGameDataSide<PB7>, IScaledSizeAbsolute, IGameDataSplitAbility
|
||||
public sealed class GameDataPB7 : HomeOptional1, IGameDataSide<PB7>, IScaledSizeAbsolute, IGameDataSplitAbility, IGameDataSidePP
|
||||
{
|
||||
private const HomeGameDataFormat ExpectFormat = HomeGameDataFormat.PB7;
|
||||
private const int SIZE = HomeCrypto.SIZE_2GAME_PB7;
|
||||
|
|
@ -32,14 +32,14 @@ public sealed class GameDataPB7 : HomeOptional1, IGameDataSide<PB7>, IScaledSize
|
|||
public ushort Move3 { get => ReadUInt16LittleEndian(Data[0x0B..]); set => WriteUInt16LittleEndian(Data[0x0B..], value); }
|
||||
public ushort Move4 { get => ReadUInt16LittleEndian(Data[0x0D..]); set => WriteUInt16LittleEndian(Data[0x0D..], value); }
|
||||
|
||||
public int Move1_PP { get => Data[0x0F]; set => Data[0x0F] = (byte)value; }
|
||||
public int Move2_PP { get => Data[0x10]; set => Data[0x10] = (byte)value; }
|
||||
public int Move3_PP { get => Data[0x11]; set => Data[0x11] = (byte)value; }
|
||||
public int Move4_PP { get => Data[0x12]; set => Data[0x12] = (byte)value; }
|
||||
public int Move1_PPUps { get => Data[0x13]; set => Data[0x13] = (byte)value; }
|
||||
public int Move2_PPUps { get => Data[0x14]; set => Data[0x14] = (byte)value; }
|
||||
public int Move3_PPUps { get => Data[0x15]; set => Data[0x15] = (byte)value; }
|
||||
public int Move4_PPUps { get => Data[0x16]; set => Data[0x16] = (byte)value; }
|
||||
public byte Move1_PP { get => Data[0x0F]; set => Data[0x0F] = value; }
|
||||
public byte Move2_PP { get => Data[0x10]; set => Data[0x10] = value; }
|
||||
public byte Move3_PP { get => Data[0x11]; set => Data[0x11] = value; }
|
||||
public byte Move4_PP { get => Data[0x12]; set => Data[0x12] = value; }
|
||||
public byte Move1_PPUps { get => Data[0x13]; set => Data[0x13] = value; }
|
||||
public byte Move2_PPUps { get => Data[0x14]; set => Data[0x14] = value; }
|
||||
public byte Move3_PPUps { get => Data[0x15]; set => Data[0x15] = value; }
|
||||
public byte Move4_PPUps { get => Data[0x16]; set => Data[0x16] = value; }
|
||||
|
||||
public ushort RelearnMove1 { get => ReadUInt16LittleEndian(Data[0x17..]); set => WriteUInt16LittleEndian(Data[0x17..], value); }
|
||||
public ushort RelearnMove2 { get => ReadUInt16LittleEndian(Data[0x19..]); set => WriteUInt16LittleEndian(Data[0x19..], value); }
|
||||
|
|
@ -209,10 +209,8 @@ private void PopulateFromCore(PKH pkh)
|
|||
Ability = (ushort)pi.GetAbilityAtIndex(index);
|
||||
}
|
||||
|
||||
private static IGameDataSide? GetNearestNeighbor(PKH pkh) => pkh.DataPK9 as IGameDataSide
|
||||
?? pkh.DataPB8 as IGameDataSide
|
||||
?? pkh.DataPK8 as IGameDataSide
|
||||
?? pkh.DataPB7;
|
||||
private static IGameDataSide? GetNearestNeighbor(PKH pkh)
|
||||
=> pkh.DataPA9 ?? pkh.DataPK9 ?? pkh.DataPB8 ?? pkh.DataPK8 ?? pkh.DataPB7 as IGameDataSide;
|
||||
|
||||
public static T Create<T>(GameDataPB7 data) where T : IGameDataSide, new() => new()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Side game data for <see cref="PB8"/> data transferred into HOME.
|
||||
/// </summary>
|
||||
public sealed class GameDataPB8 : HomeOptional1, IGameDataSide<PB8>, IGameDataSplitAbility, IPokerusStatus
|
||||
public sealed class GameDataPB8 : HomeOptional1, IGameDataSide<PB8>, IGameDataSplitAbility, IPokerusStatus, IGameDataSidePP
|
||||
{
|
||||
private const HomeGameDataFormat ExpectFormat = HomeGameDataFormat.PB8;
|
||||
private const int SIZE = HomeCrypto.SIZE_2GAME_PB8;
|
||||
|
|
@ -24,14 +24,14 @@ public sealed class GameDataPB8 : HomeOptional1, IGameDataSide<PB8>, IGameDataSp
|
|||
public ushort Move3 { get => ReadUInt16LittleEndian(Data[0x04..]); set => WriteUInt16LittleEndian(Data[0x04..], value); }
|
||||
public ushort Move4 { get => ReadUInt16LittleEndian(Data[0x06..]); set => WriteUInt16LittleEndian(Data[0x06..], value); }
|
||||
|
||||
public int Move1_PP { get => Data[0x08]; set => Data[0x08] = (byte)value; }
|
||||
public int Move2_PP { get => Data[0x09]; set => Data[0x09] = (byte)value; }
|
||||
public int Move3_PP { get => Data[0x0A]; set => Data[0x0A] = (byte)value; }
|
||||
public int Move4_PP { get => Data[0x0B]; set => Data[0x0B] = (byte)value; }
|
||||
public int Move1_PPUps { get => Data[0x0C]; set => Data[0x0C] = (byte)value; }
|
||||
public int Move2_PPUps { get => Data[0x0D]; set => Data[0x0D] = (byte)value; }
|
||||
public int Move3_PPUps { get => Data[0x0E]; set => Data[0x0E] = (byte)value; }
|
||||
public int Move4_PPUps { get => Data[0x0F]; set => Data[0x0F] = (byte)value; }
|
||||
public byte Move1_PP { get => Data[0x08]; set => Data[0x08] = value; }
|
||||
public byte Move2_PP { get => Data[0x09]; set => Data[0x09] = value; }
|
||||
public byte Move3_PP { get => Data[0x0A]; set => Data[0x0A] = value; }
|
||||
public byte Move4_PP { get => Data[0x0B]; set => Data[0x0B] = value; }
|
||||
public byte Move1_PPUps { get => Data[0x0C]; set => Data[0x0C] = value; }
|
||||
public byte Move2_PPUps { get => Data[0x0D]; set => Data[0x0D] = value; }
|
||||
public byte Move3_PPUps { get => Data[0x0E]; set => Data[0x0E] = value; }
|
||||
public byte Move4_PPUps { get => Data[0x0F]; set => Data[0x0F] = value; }
|
||||
|
||||
public ushort RelearnMove1 { get => ReadUInt16LittleEndian(Data[0x10..]); set => WriteUInt16LittleEndian(Data[0x10..], value); }
|
||||
public ushort RelearnMove2 { get => ReadUInt16LittleEndian(Data[0x12..]); set => WriteUInt16LittleEndian(Data[0x12..], value); }
|
||||
|
|
@ -102,10 +102,8 @@ public PB8 ConvertToPKM(PKH pkh)
|
|||
return result;
|
||||
}
|
||||
|
||||
private static IGameDataSide? GetNearestNeighbor(PKH pkh) => pkh.DataPK9 as IGameDataSide
|
||||
?? pkh.DataPK8 as IGameDataSide
|
||||
?? pkh.DataPB7 as IGameDataSide
|
||||
?? pkh.DataPA8;
|
||||
private static IGameDataSide? GetNearestNeighbor(PKH pkh)
|
||||
=> pkh.DataPA9 ?? pkh.DataPK9 ?? pkh.DataPK8 ?? pkh.DataPB7 ?? pkh.DataPA8 as IGameDataSide;
|
||||
|
||||
public void InitializeFrom(IGameDataSide side, PKH pkh)
|
||||
{
|
||||
|
|
|
|||
46
PKHeX.Core/PKM/HOME/GameDataPC9.cs
Normal file
46
PKHeX.Core/PKM/HOME/GameDataPC9.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Side game data for Champions data linked to HOME.
|
||||
/// </summary>
|
||||
public sealed class GameDataPC9 : HomeOptional1
|
||||
{
|
||||
private const HomeGameDataFormat ExpectFormat = HomeGameDataFormat.PC9;
|
||||
private const int SIZE = HomeCrypto.SIZE_4GAME_PC9;
|
||||
protected override HomeGameDataFormat Format => ExpectFormat;
|
||||
|
||||
public GameDataPC9() : base(SIZE) { }
|
||||
public GameDataPC9(Memory<byte> data) : base(data) => EnsureSize(SIZE);
|
||||
public GameDataPC9 Clone() => new(ToArray());
|
||||
public int WriteTo(Span<byte> result) => WriteWithHeader(result);
|
||||
|
||||
#region Structure
|
||||
|
||||
/// <summary> Indicates if the data currently resides in Champions. </summary>
|
||||
public ChampionsTransferState State { get => (ChampionsTransferState)Data[0x00]; set => Data[0x00] = (byte)value; }
|
||||
|
||||
/// <summary> Time of last sync with Champions (deposit/return). time_t (64-bit) in seconds since Unix epoch. </summary>
|
||||
public ulong Timestamp { get => ReadUInt64LittleEndian(Data[0x01..]); set => WriteUInt64LittleEndian(Data[0x01..], value); }
|
||||
|
||||
/// <summary> Probably a GUID, since Champions uses Unity (C#) and this is 16 bytes long. Probably used by Champions to fetch the Champions' specific data while in that game. </summary>
|
||||
public Span<byte> TagSpan => Data.Slice(0x09, 0x10);
|
||||
public Guid Tag => new(TagSpan);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the transfer state of an entity with respect to the Pokémon Champions ecosystem.
|
||||
/// </summary>
|
||||
public enum ChampionsTransferState : byte
|
||||
{
|
||||
/// <summary> Never transferred into Champions. Not really a valid state, as value should be one of the other options once initialized. </summary>
|
||||
None = 0,
|
||||
/// <summary> Indicates that the entity is currently deposited into Champions, and is thus locked out from interaction until returned. </summary>
|
||||
Transferred = 1,
|
||||
/// <summary> Indicates that the entity has been returned from Champions and is no longer locked out from interaction. </summary>
|
||||
Returned = 2,
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Side game data for <see cref="PK8"/> data transferred into HOME.
|
||||
/// </summary>
|
||||
public sealed class GameDataPK8 : HomeOptional1, IGameDataSide<PK8>, IGigantamax, IDynamaxLevel, ISociability, IGameDataSplitAbility, IPokerusStatus
|
||||
public sealed class GameDataPK8 : HomeOptional1, IGameDataSide<PK8>, IGigantamax, IDynamaxLevel, ISociability, IGameDataSplitAbility, IPokerusStatus, IGameDataSidePP
|
||||
{
|
||||
private const HomeGameDataFormat ExpectFormat = HomeGameDataFormat.PK8;
|
||||
private const int SIZE = HomeCrypto.SIZE_2GAME_PK8;
|
||||
|
|
@ -27,14 +27,14 @@ public sealed class GameDataPK8 : HomeOptional1, IGameDataSide<PK8>, IGigantamax
|
|||
public ushort Move3 { get => ReadUInt16LittleEndian(Data[0x09..]); set => WriteUInt16LittleEndian(Data[0x09..], value); }
|
||||
public ushort Move4 { get => ReadUInt16LittleEndian(Data[0x0B..]); set => WriteUInt16LittleEndian(Data[0x0B..], value); }
|
||||
|
||||
public int Move1_PP { get => Data[0x0D]; set => Data[0x0D] = (byte)value; }
|
||||
public int Move2_PP { get => Data[0x0E]; set => Data[0x0E] = (byte)value; }
|
||||
public int Move3_PP { get => Data[0x0F]; set => Data[0x0F] = (byte)value; }
|
||||
public int Move4_PP { get => Data[0x10]; set => Data[0x10] = (byte)value; }
|
||||
public int Move1_PPUps { get => Data[0x11]; set => Data[0x11] = (byte)value; }
|
||||
public int Move2_PPUps { get => Data[0x12]; set => Data[0x12] = (byte)value; }
|
||||
public int Move3_PPUps { get => Data[0x13]; set => Data[0x13] = (byte)value; }
|
||||
public int Move4_PPUps { get => Data[0x14]; set => Data[0x14] = (byte)value; }
|
||||
public byte Move1_PP { get => Data[0x0D]; set => Data[0x0D] = value; }
|
||||
public byte Move2_PP { get => Data[0x0E]; set => Data[0x0E] = value; }
|
||||
public byte Move3_PP { get => Data[0x0F]; set => Data[0x0F] = value; }
|
||||
public byte Move4_PP { get => Data[0x10]; set => Data[0x10] = value; }
|
||||
public byte Move1_PPUps { get => Data[0x11]; set => Data[0x11] = value; }
|
||||
public byte Move2_PPUps { get => Data[0x12]; set => Data[0x12] = value; }
|
||||
public byte Move3_PPUps { get => Data[0x13]; set => Data[0x13] = value; }
|
||||
public byte Move4_PPUps { get => Data[0x14]; set => Data[0x14] = value; }
|
||||
|
||||
public ushort RelearnMove1 { get => ReadUInt16LittleEndian(Data[0x15..]); set => WriteUInt16LittleEndian(Data[0x15..], value); }
|
||||
public ushort RelearnMove2 { get => ReadUInt16LittleEndian(Data[0x17..]); set => WriteUInt16LittleEndian(Data[0x17..], value); }
|
||||
|
|
@ -153,9 +153,8 @@ public PK8 ConvertToPKM(PKH pkh)
|
|||
}
|
||||
|
||||
// Ignores LGP/E, already preferred if exists.
|
||||
private static IGameDataSide? GetNearestNeighbor(PKH pkh) => pkh.DataPK9 as IGameDataSide
|
||||
?? pkh.DataPB8 as IGameDataSide
|
||||
?? pkh.DataPA8;
|
||||
private static IGameDataSide? GetNearestNeighbor(PKH pkh)
|
||||
=> pkh.DataPA9 ?? pkh.DataPK9 ?? pkh.DataPB8 ?? pkh.DataPA8 as IGameDataSide;
|
||||
|
||||
private static GameDataPK8 CreateViaPB7(PKH pkh, GameDataPB7 x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Side game data for <see cref="PK9"/> data transferred into HOME.
|
||||
/// </summary>
|
||||
public sealed class GameDataPK9 : HomeOptional1, IGameDataSide<PK9>, IScaledSize3, IGameDataSplitAbility
|
||||
public sealed class GameDataPK9 : HomeOptional1, IGameDataSide<PK9>, IScaledSize3, IGameDataSplitAbility, IGameDataSidePP
|
||||
{
|
||||
private const HomeGameDataFormat ExpectFormat = HomeGameDataFormat.PK9;
|
||||
private const int SIZE = HomeCrypto.SIZE_3GAME_PK9;
|
||||
|
|
@ -25,14 +25,14 @@ public sealed class GameDataPK9 : HomeOptional1, IGameDataSide<PK9>, IScaledSize
|
|||
public ushort Move3 { get => ReadUInt16LittleEndian(Data[0x05..]); set => WriteUInt16LittleEndian(Data[0x05..], value); }
|
||||
public ushort Move4 { get => ReadUInt16LittleEndian(Data[0x07..]); set => WriteUInt16LittleEndian(Data[0x07..], value); }
|
||||
|
||||
public int Move1_PP { get => Data[0x09]; set => Data[0x09] = (byte)value; }
|
||||
public int Move2_PP { get => Data[0x0A]; set => Data[0x0A] = (byte)value; }
|
||||
public int Move3_PP { get => Data[0x0B]; set => Data[0x0B] = (byte)value; }
|
||||
public int Move4_PP { get => Data[0x0C]; set => Data[0x0C] = (byte)value; }
|
||||
public int Move1_PPUps { get => Data[0x0D]; set => Data[0x0D] = (byte)value; }
|
||||
public int Move2_PPUps { get => Data[0x0E]; set => Data[0x0E] = (byte)value; }
|
||||
public int Move3_PPUps { get => Data[0x0F]; set => Data[0x0F] = (byte)value; }
|
||||
public int Move4_PPUps { get => Data[0x10]; set => Data[0x10] = (byte)value; }
|
||||
public byte Move1_PP { get => Data[0x09]; set => Data[0x09] = value; }
|
||||
public byte Move2_PP { get => Data[0x0A]; set => Data[0x0A] = value; }
|
||||
public byte Move3_PP { get => Data[0x0B]; set => Data[0x0B] = value; }
|
||||
public byte Move4_PP { get => Data[0x0C]; set => Data[0x0C] = value; }
|
||||
public byte Move1_PPUps { get => Data[0x0D]; set => Data[0x0D] = value; }
|
||||
public byte Move2_PPUps { get => Data[0x0E]; set => Data[0x0E] = value; }
|
||||
public byte Move3_PPUps { get => Data[0x0F]; set => Data[0x0F] = value; }
|
||||
public byte Move4_PPUps { get => Data[0x10]; set => Data[0x10] = value; }
|
||||
|
||||
public ushort RelearnMove1 { get => ReadUInt16LittleEndian(Data[0x11..]); set => WriteUInt16LittleEndian(Data[0x11..], value); }
|
||||
public ushort RelearnMove2 { get => ReadUInt16LittleEndian(Data[0x13..]); set => WriteUInt16LittleEndian(Data[0x13..], value); }
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ public static class HomeCrypto
|
|||
public const int Version1 = 1;
|
||||
public const int Version2 = 2;
|
||||
public const int Version3 = 3;
|
||||
public const int Version4 = 4;
|
||||
|
||||
public const int SIZE_1HEADER = 0x10; // 16
|
||||
|
||||
|
|
@ -34,17 +35,19 @@ public static class HomeCrypto
|
|||
public const int SIZE_3GAME_PK9 = 0x3D + 0xD; // 74
|
||||
public const int SIZE_3STORED = 0x247; // 583
|
||||
|
||||
public const int SIZE_4GAME_PA9 = 0x40; // 64 TODO HOME ZA
|
||||
public const int SIZE_4GAME_PC9 = 0x19; // 25
|
||||
public const int SIZE_4STORED = 0x2A6; // 702 TODO HOME ZA
|
||||
|
||||
/// <summary> Latest maximum size of a Pokémon Home entity. </summary>
|
||||
public const int SIZE_STORED = SIZE_3STORED;
|
||||
public const int SIZE_STORED = SIZE_4STORED;
|
||||
/// <summary> Latest maximum size of a Pokémon Home entity's core data shared with all side-games. </summary>
|
||||
public const int SIZE_CORE = SIZE_2CORE;
|
||||
/// <summary> Latest Version identifier stored in the header. </summary>
|
||||
public const int VersionLatest = Version3;
|
||||
public const int VersionLatest = Version4;
|
||||
|
||||
public const int SIZE_4GAME_PA9 = 0x77; // TODO HOME ZA
|
||||
public const int SIZE_4STORED = 0x2BE; // 702
|
||||
|
||||
public static bool IsKnownVersion(ushort version) => version is Version1 or Version2 or Version3;
|
||||
public static bool IsKnownVersion(ushort version) => version is Version1 or Version2 or Version3 or Version4;
|
||||
public static bool IsPlausibleSize(long length) => length is > (SIZE_1HEADER + SIZE_1CORE) and <= SIZE_STORED;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void SetEncryptionKey(Span<byte> key, ulong seed)
|
||||
|
|
@ -150,6 +153,7 @@ private static void RefreshChecksum(ReadOnlySpan<byte> encrypted, Span<byte> des
|
|||
Version1 => IsEncryptedCore1(data),
|
||||
Version2 => IsEncryptedCore2(data),
|
||||
Version3 => IsEncryptedCore3(data),
|
||||
Version4 => IsEncryptedCore4(data),
|
||||
_ => throw new ArgumentException($"Unrecognized format: {format}"),
|
||||
};
|
||||
|
||||
|
|
@ -187,6 +191,7 @@ private static bool IsEncryptedCore2(ReadOnlySpan<byte> data)
|
|||
}
|
||||
|
||||
private static bool IsEncryptedCore3(ReadOnlySpan<byte> data) => IsEncryptedCore2(data); // Same struct as Core version 2.
|
||||
private static bool IsEncryptedCore4(ReadOnlySpan<byte> data) => IsEncryptedCore2(data); // Same struct as Core version 2.
|
||||
|
||||
/// <summary>
|
||||
/// Gets the checksum of a Pokémon's AES-encrypted data.
|
||||
|
|
|
|||
|
|
@ -11,5 +11,6 @@ public enum HomeGameDataFormat : byte
|
|||
PA8 = 3,
|
||||
PB8 = 4,
|
||||
PK9 = 5,
|
||||
PA9 = 6,
|
||||
PC9 = 6,
|
||||
PA9 = 7,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,10 +41,10 @@ public interface IGameDataSide<T> : IGameDataSide where T : PKM, new()
|
|||
/// </summary>
|
||||
public interface IGameDataSide
|
||||
{
|
||||
ushort Move1 { get; set; } int Move1_PP { get; set; } int Move1_PPUps { get; set; } ushort RelearnMove1 { get; set; }
|
||||
ushort Move2 { get; set; } int Move2_PP { get; set; } int Move2_PPUps { get; set; } ushort RelearnMove2 { get; set; }
|
||||
ushort Move3 { get; set; } int Move3_PP { get; set; } int Move3_PPUps { get; set; } ushort RelearnMove3 { get; set; }
|
||||
ushort Move4 { get; set; } int Move4_PP { get; set; } int Move4_PPUps { get; set; } ushort RelearnMove4 { get; set; }
|
||||
ushort Move1 { get; set; } ushort RelearnMove1 { get; set; }
|
||||
ushort Move2 { get; set; } ushort RelearnMove2 { get; set; }
|
||||
ushort Move3 { get; set; } ushort RelearnMove3 { get; set; }
|
||||
ushort Move4 { get; set; } ushort RelearnMove4 { get; set; }
|
||||
byte Ball { get; set; }
|
||||
ushort MetLocation { get; set; }
|
||||
ushort EggLocation { get; set; }
|
||||
|
|
@ -55,6 +55,14 @@ public interface IGameDataSide
|
|||
PersonalInfo GetPersonalInfo(ushort species, byte form);
|
||||
}
|
||||
|
||||
public interface IGameDataSidePP
|
||||
{
|
||||
byte Move1_PP { get; set; } byte Move1_PPUps { get; set; }
|
||||
byte Move2_PP { get; set; } byte Move2_PPUps { get; set; }
|
||||
byte Move3_PP { get; set; } byte Move3_PPUps { get; set; }
|
||||
byte Move4_PP { get; set; } byte Move4_PPUps { get; set; }
|
||||
}
|
||||
|
||||
public static class GameDataSideExtensions
|
||||
{
|
||||
extension(IGameDataSide data)
|
||||
|
|
@ -65,13 +73,21 @@ public static class GameDataSideExtensions
|
|||
/// <param name="pk">Destination entity</param>
|
||||
public void CopyTo(PKM pk)
|
||||
{
|
||||
pk.Move1 = data.Move1; pk.Move1_PP = data.Move1_PP; pk.Move1_PPUps = data.Move1_PPUps; pk.RelearnMove1 = data.RelearnMove1;
|
||||
pk.Move2 = data.Move2; pk.Move2_PP = data.Move2_PP; pk.Move2_PPUps = data.Move2_PPUps; pk.RelearnMove2 = data.RelearnMove2;
|
||||
pk.Move3 = data.Move3; pk.Move3_PP = data.Move3_PP; pk.Move3_PPUps = data.Move3_PPUps; pk.RelearnMove3 = data.RelearnMove3;
|
||||
pk.Move4 = data.Move4; pk.Move4_PP = data.Move4_PP; pk.Move4_PPUps = data.Move4_PPUps; pk.RelearnMove4 = data.RelearnMove4;
|
||||
pk.Move1 = data.Move1; pk.RelearnMove1 = data.RelearnMove1;
|
||||
pk.Move2 = data.Move2; pk.RelearnMove2 = data.RelearnMove2;
|
||||
pk.Move3 = data.Move3; pk.RelearnMove3 = data.RelearnMove3;
|
||||
pk.Move4 = data.Move4; pk.RelearnMove4 = data.RelearnMove4;
|
||||
pk.Ball = data.Ball;
|
||||
pk.MetLocation = data.MetLocation;
|
||||
pk.EggLocation = data.EggLocation;
|
||||
|
||||
if (data is IGameDataSidePP pps)
|
||||
{
|
||||
pk.Move1_PP = pps.Move1_PP; pk.Move1_PPUps = pps.Move1_PPUps;
|
||||
pk.Move2_PP = pps.Move2_PP; pk.Move2_PPUps = pps.Move2_PPUps;
|
||||
pk.Move3_PP = pps.Move3_PP; pk.Move3_PPUps = pps.Move3_PPUps;
|
||||
pk.Move4_PP = pps.Move4_PP; pk.Move4_PPUps = pps.Move4_PPUps;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -80,13 +96,21 @@ public void CopyTo(PKM pk)
|
|||
/// <param name="pk">Destination entity</param>
|
||||
public void CopyTo(IGameDataSide pk)
|
||||
{
|
||||
pk.Move1 = data.Move1; pk.Move1_PP = data.Move1_PP; pk.Move1_PPUps = data.Move1_PPUps; pk.RelearnMove1 = data.RelearnMove1;
|
||||
pk.Move2 = data.Move2; pk.Move2_PP = data.Move2_PP; pk.Move2_PPUps = data.Move2_PPUps; pk.RelearnMove2 = data.RelearnMove2;
|
||||
pk.Move3 = data.Move3; pk.Move3_PP = data.Move3_PP; pk.Move3_PPUps = data.Move3_PPUps; pk.RelearnMove3 = data.RelearnMove3;
|
||||
pk.Move4 = data.Move4; pk.Move4_PP = data.Move4_PP; pk.Move4_PPUps = data.Move4_PPUps; pk.RelearnMove4 = data.RelearnMove4;
|
||||
pk.Move1 = data.Move1; pk.RelearnMove1 = data.RelearnMove1;
|
||||
pk.Move2 = data.Move2; pk.RelearnMove2 = data.RelearnMove2;
|
||||
pk.Move3 = data.Move3; pk.RelearnMove3 = data.RelearnMove3;
|
||||
pk.Move4 = data.Move4; pk.RelearnMove4 = data.RelearnMove4;
|
||||
pk.Ball = data.Ball;
|
||||
pk.MetLocation = data.MetLocation;
|
||||
pk.EggLocation = data.EggLocation;
|
||||
|
||||
if (data is IGameDataSidePP pps && pk is IGameDataSidePP ppk)
|
||||
{
|
||||
ppk.Move1_PP = pps.Move1_PP; ppk.Move1_PPUps = pps.Move1_PPUps;
|
||||
ppk.Move2_PP = pps.Move2_PP; ppk.Move2_PPUps = pps.Move2_PPUps;
|
||||
ppk.Move3_PP = pps.Move3_PP; ppk.Move3_PPUps = pps.Move3_PPUps;
|
||||
ppk.Move4_PP = pps.Move4_PP; ppk.Move4_PPUps = pps.Move4_PPUps;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -95,13 +119,21 @@ public void CopyTo(IGameDataSide pk)
|
|||
/// <param name="pk">Destination entity</param>
|
||||
public void CopyFrom(PKM pk)
|
||||
{
|
||||
data.Move1 = pk.Move1; data.Move1_PP = pk.Move1_PP; data.Move1_PPUps = pk.Move1_PPUps; data.RelearnMove1 = pk.RelearnMove1;
|
||||
data.Move2 = pk.Move2; data.Move2_PP = pk.Move2_PP; data.Move2_PPUps = pk.Move2_PPUps; data.RelearnMove2 = pk.RelearnMove2;
|
||||
data.Move3 = pk.Move3; data.Move3_PP = pk.Move3_PP; data.Move3_PPUps = pk.Move3_PPUps; data.RelearnMove3 = pk.RelearnMove3;
|
||||
data.Move4 = pk.Move4; data.Move4_PP = pk.Move4_PP; data.Move4_PPUps = pk.Move4_PPUps; data.RelearnMove4 = pk.RelearnMove4;
|
||||
data.Move1 = pk.Move1; data.RelearnMove1 = pk.RelearnMove1;
|
||||
data.Move2 = pk.Move2; data.RelearnMove2 = pk.RelearnMove2;
|
||||
data.Move3 = pk.Move3; data.RelearnMove3 = pk.RelearnMove3;
|
||||
data.Move4 = pk.Move4; data.RelearnMove4 = pk.RelearnMove4;
|
||||
data.Ball = pk.Ball;
|
||||
data.MetLocation = pk.MetLocation;
|
||||
data.EggLocation = pk.EggLocation;
|
||||
|
||||
if (pk is IGameDataSidePP ppk && data is IGameDataSidePP pps)
|
||||
{
|
||||
pps.Move1_PP = ppk.Move1_PP; pps.Move1_PPUps = ppk.Move1_PPUps;
|
||||
pps.Move2_PP = ppk.Move2_PP; pps.Move2_PPUps = ppk.Move2_PPUps;
|
||||
pps.Move3_PP = ppk.Move3_PP; pps.Move3_PPUps = ppk.Move3_PPUps;
|
||||
pps.Move4_PP = ppk.Move4_PP; pps.Move4_PPUps = ppk.Move4_PPUps;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -116,10 +148,15 @@ public void ResetMoves(ushort species, byte form, byte level, ILearnSource sourc
|
|||
data.Move2 = moves[1];
|
||||
data.Move3 = moves[2];
|
||||
data.Move4 = moves[3];
|
||||
data.Move1_PP = MoveInfo.GetPP(context, moves[0]);
|
||||
data.Move2_PP = MoveInfo.GetPP(context, moves[1]);
|
||||
data.Move3_PP = MoveInfo.GetPP(context, moves[2]);
|
||||
data.Move4_PP = MoveInfo.GetPP(context, moves[3]);
|
||||
|
||||
if (data is IGameDataSidePP pps)
|
||||
{
|
||||
pps.Move1_PPUps = 0; pps.Move2_PPUps = 0; pps.Move3_PPUps = 0; pps.Move4_PPUps = 0;
|
||||
pps.Move1_PP = MoveInfo.GetPP(context, moves[0]);
|
||||
pps.Move2_PP = MoveInfo.GetPP(context, moves[1]);
|
||||
pps.Move3_PP = MoveInfo.GetPP(context, moves[2]);
|
||||
pps.Move4_PP = MoveInfo.GetPP(context, moves[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,13 @@ public sealed class PKH : PKM, IHandlerLanguage, IFormArgument, IHomeTrack, IBat
|
|||
public GameDataPA8? DataPA8 { get; private set; }
|
||||
public GameDataPB8? DataPB8 { get; private set; }
|
||||
public GameDataPK9? DataPK9 { get; private set; }
|
||||
public GameDataPC9? DataPC9 { get; private set; }
|
||||
public GameDataPA9? DataPA9 { get; private set; }
|
||||
|
||||
public override EntityContext Context => EntityContext.None;
|
||||
public override int WriteDecryptedDataStored(Span<byte> destination) => Rebuild(destination);
|
||||
protected override void EncryptStored(Span<byte> stored) { }
|
||||
protected override void EncryptParty(Span<byte> party) { }
|
||||
|
||||
public PKH(Memory<byte> data) : base(DecryptHome(data))
|
||||
{
|
||||
|
|
@ -53,13 +57,14 @@ private void ReadGameData1(Memory<byte> data)
|
|||
}
|
||||
}
|
||||
|
||||
private IGameDataSide ReadGameData1(Memory<byte> chunk, HomeGameDataFormat format) => format switch
|
||||
private object ReadGameData1(Memory<byte> chunk, HomeGameDataFormat format) => format switch
|
||||
{
|
||||
HomeGameDataFormat.PB7 => DataPB7 = new GameDataPB7(chunk),
|
||||
HomeGameDataFormat.PK8 => DataPK8 = new GameDataPK8(chunk),
|
||||
HomeGameDataFormat.PA8 => DataPA8 = new GameDataPA8(chunk),
|
||||
HomeGameDataFormat.PB8 => DataPB8 = new GameDataPB8(chunk),
|
||||
HomeGameDataFormat.PK9 => DataPK9 = new GameDataPK9(chunk),
|
||||
HomeGameDataFormat.PC9 => DataPC9 = new GameDataPC9(chunk),
|
||||
HomeGameDataFormat.PA9 => DataPA9 = new GameDataPA9(chunk),
|
||||
_ => throw new ArgumentException($"Unknown {nameof(HomeGameDataFormat)} {format}"),
|
||||
};
|
||||
|
|
@ -210,14 +215,14 @@ private static Memory<byte> DecryptHome(Memory<byte> data)
|
|||
public override ushort Move2 { get => LatestGameData.Move2 ; set => LatestGameData.Move2 = value; }
|
||||
public override ushort Move3 { get => LatestGameData.Move3 ; set => LatestGameData.Move3 = value; }
|
||||
public override ushort Move4 { get => LatestGameData.Move4 ; set => LatestGameData.Move4 = value; }
|
||||
public override int Move1_PP { get => LatestGameData.Move1_PP ; set => LatestGameData.Move1_PP = value; }
|
||||
public override int Move2_PP { get => LatestGameData.Move2_PP ; set => LatestGameData.Move2_PP = value; }
|
||||
public override int Move3_PP { get => LatestGameData.Move3_PP ; set => LatestGameData.Move3_PP = value; }
|
||||
public override int Move4_PP { get => LatestGameData.Move4_PP ; set => LatestGameData.Move4_PP = value; }
|
||||
public override int Move1_PPUps { get => LatestGameData.Move1_PPUps; set => LatestGameData.Move1_PPUps = value; }
|
||||
public override int Move2_PPUps { get => LatestGameData.Move2_PPUps; set => LatestGameData.Move2_PPUps = value; }
|
||||
public override int Move3_PPUps { get => LatestGameData.Move3_PPUps; set => LatestGameData.Move3_PPUps = value; }
|
||||
public override int Move4_PPUps { get => LatestGameData.Move4_PPUps; set => LatestGameData.Move4_PPUps = value; }
|
||||
public override int Move1_PP { get => (LatestGameData as IGameDataSidePP)?.Move1_PP ?? 0; set => (LatestGameData as IGameDataSidePP)?.Move1_PP = (byte)value; }
|
||||
public override int Move2_PP { get => (LatestGameData as IGameDataSidePP)?.Move2_PP ?? 0; set => (LatestGameData as IGameDataSidePP)?.Move2_PP = (byte)value; }
|
||||
public override int Move3_PP { get => (LatestGameData as IGameDataSidePP)?.Move3_PP ?? 0; set => (LatestGameData as IGameDataSidePP)?.Move3_PP = (byte)value; }
|
||||
public override int Move4_PP { get => (LatestGameData as IGameDataSidePP)?.Move4_PP ?? 0; set => (LatestGameData as IGameDataSidePP)?.Move4_PP = (byte)value; }
|
||||
public override int Move1_PPUps { get => (LatestGameData as IGameDataSidePP)?.Move1_PPUps ?? 0; set => (LatestGameData as IGameDataSidePP)?.Move1_PPUps = (byte)value; }
|
||||
public override int Move2_PPUps { get => (LatestGameData as IGameDataSidePP)?.Move2_PPUps ?? 0; set => (LatestGameData as IGameDataSidePP)?.Move2_PPUps = (byte)value; }
|
||||
public override int Move3_PPUps { get => (LatestGameData as IGameDataSidePP)?.Move3_PPUps ?? 0; set => (LatestGameData as IGameDataSidePP)?.Move3_PPUps = (byte)value; }
|
||||
public override int Move4_PPUps { get => (LatestGameData as IGameDataSidePP)?.Move4_PPUps ?? 0; set => (LatestGameData as IGameDataSidePP)?.Move4_PPUps = (byte)value; }
|
||||
|
||||
public override byte Ball { get => LatestGameData.Ball; set => LatestGameData.Ball = value; }
|
||||
public override ushort MetLocation { get => LatestGameData.MetLocation; set => LatestGameData.MetLocation = value; }
|
||||
|
|
@ -255,24 +260,46 @@ private static Memory<byte> DecryptHome(Memory<byte> data)
|
|||
public override void RefreshChecksum() => Checksum = 0;
|
||||
public override bool ChecksumValid => true;
|
||||
|
||||
protected override byte[] Encrypt()
|
||||
{
|
||||
var result = Rebuild();
|
||||
return HomeCrypto.Encrypt(result);
|
||||
}
|
||||
|
||||
public byte[] Rebuild()
|
||||
{
|
||||
var length = WriteLength;
|
||||
|
||||
// Handle PKCS7 manually
|
||||
var remainder = length & 0xF;
|
||||
var totalSize = GetPaddedSize(length, out var remainder);
|
||||
|
||||
var result = new byte[totalSize];
|
||||
WriteTo(result, length, remainder, totalSize);
|
||||
return result;
|
||||
}
|
||||
|
||||
public int Rebuild(Span<byte> dest)
|
||||
{
|
||||
var length = WriteLength;
|
||||
// Handle PKCS7 manually
|
||||
var totalSize = GetPaddedSize(length, out var remainder);
|
||||
|
||||
var result = dest[..totalSize];
|
||||
WriteTo(result, length, remainder, totalSize);
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
private void WriteTo(Span<byte> data, int innerLength, int remainder, int totalSize)
|
||||
{
|
||||
var payload = data[..innerLength];
|
||||
data[innerLength..].Fill((byte)remainder);
|
||||
WriteTo(payload, totalSize);
|
||||
}
|
||||
|
||||
public static int GetPaddedSize(int innerLength, out int remainder)
|
||||
{
|
||||
remainder = innerLength & 0xF;
|
||||
if (remainder != 0) // pad to nearest 0x10, fill remainder bytes with value.
|
||||
remainder = 0x10 - remainder;
|
||||
var result = new byte[length + remainder];
|
||||
var span = result.AsSpan(0, length);
|
||||
result.AsSpan(length).Fill((byte)remainder);
|
||||
var totalSize = innerLength + remainder;
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
private void WriteTo(Span<byte> span, int innerLength)
|
||||
{
|
||||
// Header and Core are already in the current byte array.
|
||||
// Write each part, starting with header and core.
|
||||
int ctr = HomeCrypto.SIZE_1HEADER + 2;
|
||||
|
|
@ -284,16 +311,15 @@ public byte[] Rebuild()
|
|||
if (DataPA8 is { } pa8) ctr += pa8.WriteTo(span[ctr..]);
|
||||
if (DataPB8 is { } pb8) ctr += pb8.WriteTo(span[ctr..]);
|
||||
if (DataPK9 is { } pk9) ctr += pk9.WriteTo(span[ctr..]);
|
||||
if (DataPC9 is { } pc9) ctr += pc9.WriteTo(span[ctr..]);
|
||||
if (DataPA9 is { } pa9) ctr += pa9.WriteTo(span[ctr..]);
|
||||
WriteUInt16LittleEndian(gameDataLengthSpan, GameDataSize = (ushort)(ctr - gameDataStart));
|
||||
|
||||
// Update metadata to ensure we're a valid object.
|
||||
DataVersion = HomeCrypto.VersionLatest;
|
||||
EncodedDataSize = (ushort)(result.Length - HomeCrypto.SIZE_1HEADER);
|
||||
EncodedDataSize = (ushort)(innerLength - HomeCrypto.SIZE_1HEADER);
|
||||
CoreDataSize = (ushort)Core.SerializedSize;
|
||||
Data[..(HomeCrypto.SIZE_1HEADER + 2)].CopyTo(span); // Copy updated header & CoreData length.
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private int WriteLength
|
||||
|
|
|
|||
|
|
@ -45,6 +45,24 @@ public interface IFormArgument
|
|||
byte FormArgumentMaximum { get; set; }
|
||||
}
|
||||
|
||||
public enum FormArgumentType
|
||||
{
|
||||
/// <summary> Doesn't use the form argument value. </summary>
|
||||
None,
|
||||
|
||||
/// <summary> Uses the form argument value as a single value. </summary>
|
||||
Raw,
|
||||
|
||||
/// <summary> Party Stat split; stores (streak) in core and (remain, elapsed) in party. </summary>
|
||||
TripleParty,
|
||||
|
||||
/// <summary> Uses the form argument value as a (max, remain, elapsed) tuple. </summary>
|
||||
Triple,
|
||||
|
||||
/// <summary> Uses the form argument value as a single value, but has special edge case handling for precise names. </summary>
|
||||
Named,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logic for mutating <see cref="IFormArgument"/> objects.
|
||||
/// </summary>
|
||||
|
|
@ -59,7 +77,7 @@ public void SetSuggestedFormArgument(ushort species, byte form, EntityContext cu
|
|||
{
|
||||
if (pk is not IFormArgument)
|
||||
return;
|
||||
uint value = IsFormArgumentTypeDatePair(species, form)
|
||||
uint value = IsFormArgumentTypeDateTriple(species, form)
|
||||
? GetFormArgumentMax(species, form, current)
|
||||
: GetFormArgumentMinEvolution(species, originalSpecies);
|
||||
if (IsFormArgumentAbleToStay0(species, form, history))
|
||||
|
|
@ -106,7 +124,7 @@ public void ChangeFormArgument(uint value)
|
|||
/// <param name="value">Value to apply</param>
|
||||
public static void ChangeFormArgument(this IFormArgument f, ushort species, byte form, EntityContext context, uint value)
|
||||
{
|
||||
if (!IsFormArgumentTypeDatePair(species, form))
|
||||
if (!IsFormArgumentTypeDateTriple(species, form))
|
||||
{
|
||||
f.FormArgument = value;
|
||||
return;
|
||||
|
|
@ -126,6 +144,35 @@ public static void ChangeFormArgument(this IFormArgument f, ushort species, byte
|
|||
f.FormArgumentMaximum = Math.Max(f.FormArgumentMaximum, elapsed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum value the <see cref="IFormArgument.FormArgument"/> can be for GUI editing purposes, accounting for edge cases that are manually checked by legality.
|
||||
/// </summary>
|
||||
/// <param name="species">Entity Species</param>
|
||||
/// <param name="form">Entity Form</param>
|
||||
/// <param name="context">Context to check with.</param>
|
||||
public static uint GetFormArgumentMaxEdge(ushort species, byte form, EntityContext context)
|
||||
{
|
||||
if (species == (ushort)Furfrou && context != EntityContext.Gen6)
|
||||
return 5; // Gen6=>Gen7 clears form but forgets to clear Form Argument.
|
||||
|
||||
return GetFormArgumentMax(species, form, context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value format that form argument values are saved as.
|
||||
/// </summary>
|
||||
/// <param name="species">Entity Species</param>
|
||||
/// <param name="form">Entity Form</param>
|
||||
/// <param name="context">Context to check with.</param>
|
||||
public static FormArgumentType GetType(ushort species, byte form, EntityContext context) => (Species)species switch
|
||||
{
|
||||
Furfrou => context == EntityContext.Gen6 ? FormArgumentType.TripleParty : FormArgumentType.Triple,
|
||||
Hoopa when form == 1 => context == EntityContext.Gen6 ? FormArgumentType.TripleParty : FormArgumentType.Triple,
|
||||
Alcremie => FormArgumentType.Named,
|
||||
|
||||
_ => GetFormArgumentMax(species, form, context) > 0 ? FormArgumentType.Raw : FormArgumentType.None,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum value the <see cref="IFormArgument.FormArgument"/> can be.
|
||||
/// </summary>
|
||||
|
|
@ -175,10 +222,20 @@ public static void ChangeFormArgument(this IFormArgument f, ushort species, byte
|
|||
/// <summary>
|
||||
/// Checks if the <see cref="IFormArgument.FormArgument"/> value is stored as a days-elapsed / days-remaining pair.
|
||||
/// </summary>
|
||||
public static bool IsFormArgumentTypeDatePair(ushort species, byte form) => species switch
|
||||
public static bool IsFormArgumentTypeDateTriple(ushort species, byte form) => species switch
|
||||
{
|
||||
(int)Furfrou when form != 0 => true,
|
||||
(int)Hoopa when form == 1 => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the <see cref="IFormArgument.FormArgument"/> value is stored as a days-elapsed / days-remaining pair.
|
||||
/// </summary>
|
||||
public static bool IsFormArgumentTypeDateTripleVisible(ushort species, byte form) => species switch
|
||||
{
|
||||
(int)Furfrou => true, // Gen6=>Bank can revert form to 0 but not clear the form argument value, carries forward into Gen9+.
|
||||
(int)Hoopa when form == 1 => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ public static void ResetTeraType(PK9 pk, IEncounterTemplate enc)
|
|||
pk.TeraTypeOverride = enc is not ITeraType x ? (MoveType)OverrideNone : x.TeraTypeOverride; // WC9
|
||||
pk.TeraTypeOriginal = enc switch
|
||||
{
|
||||
{ Context: not EntityContext.Gen9 } => (pk.TeraTypeOverride = (MoveType)pk.PersonalInfo.Type1), // Treat as HOME transferred
|
||||
ITeraTypeReadOnly t => t.TeraType,
|
||||
ITeraRaid9 t9 => (MoveType)Tera9RNG.GetTeraType(Tera9RNG.GetOriginalSeed(pk), t9.TeraType, enc.Species, enc.Form),
|
||||
_ => (MoveType)Tera9RNG.GetTeraTypeFromPersonal(enc.Species, enc.Form, Util.Rand.Rand64()),
|
||||
|
|
|
|||
|
|
@ -48,26 +48,25 @@ public interface ITrainerID32ReadOnly : ITrainerID16ReadOnly
|
|||
}
|
||||
|
||||
public static class ITrainerID32Extensions
|
||||
{
|
||||
private const int ShinyXorThreshold36 = 8; // 1:8192
|
||||
private const int ShinyXorThreshold7 = 16; // 1:4096
|
||||
|
||||
extension(ITrainerID32 tr)
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the <see cref="pid"/> is shiny when owned by the <see cref="ITrainerID32"/>.
|
||||
/// </summary>
|
||||
/// <param name="tr">Possessing trainer</param>
|
||||
/// <param name="pid"><see cref="PKM.PID"/></param>
|
||||
/// <param name="generation">Generation of origin.</param>
|
||||
/// <returns>True if shiny, false if not.</returns>
|
||||
public static bool IsShiny(this ITrainerID32 tr, uint pid, byte generation = 7)
|
||||
public bool IsShiny(uint pid, byte generation = 7)
|
||||
{
|
||||
var xor = tr.GetShinyXor(pid);
|
||||
var threshold = (generation >= 7 ? ShinyXorThreshold7 : ShinyXorThreshold36);
|
||||
return xor < threshold;
|
||||
}
|
||||
|
||||
private const int ShinyXorThreshold36 = 8; // 1:8192
|
||||
private const int ShinyXorThreshold7 = 16; // 1:4096
|
||||
|
||||
extension(ITrainerID32 tr)
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the <see cref="pid"/> and <see cref="ITrainerID32.ID32"/> xor.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ public sealed class PA8 : PKM, ISanityChecksum,
|
|||
public override EntityContext Context => EntityContext.Gen8a;
|
||||
public PA8() : base(PokeCrypto.SIZE_8APARTY) => AffixedRibbon = Core.AffixedRibbon.None;
|
||||
public PA8(Memory<byte> data) : base(DecryptParty(data)) { }
|
||||
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt8A(stored);
|
||||
protected override void EncryptParty(Span<byte> party) => PokeCrypto.CryptArray(party, EncryptionConstant);
|
||||
|
||||
public override int SIZE_PARTY => PokeCrypto.SIZE_8APARTY;
|
||||
public override int SIZE_STORED => PokeCrypto.SIZE_8ASTORED;
|
||||
|
|
@ -43,7 +45,7 @@ public sealed class PA8 : PKM, ISanityChecksum,
|
|||
|
||||
private static Memory<byte> DecryptParty(Memory<byte> data)
|
||||
{
|
||||
PokeCrypto.DecryptIfEncrypted8A(ref data);
|
||||
PokeCrypto.DecryptIfEncrypted8A(data.Span);
|
||||
if (data.Length >= PokeCrypto.SIZE_8APARTY)
|
||||
return data;
|
||||
|
||||
|
|
@ -86,11 +88,6 @@ public override byte CurrentFriendship
|
|||
public override int Characteristic => EntityCharacteristic.GetCharacteristicInit0(EncryptionConstant, IV32);
|
||||
|
||||
// Methods
|
||||
protected override byte[] Encrypt()
|
||||
{
|
||||
RefreshChecksum();
|
||||
return PokeCrypto.EncryptArray8A(Data);
|
||||
}
|
||||
|
||||
public void FixRelearn()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,24 +21,26 @@ public sealed class PA9 : PKM, ISanityChecksum, ITechRecord, IObedienceLevel, IH
|
|||
public override PersonalInfo9ZA PersonalInfo => PersonalTable.ZA.GetFormEntry(Species, Form);
|
||||
public IPermitRecord Permit => PersonalInfo;
|
||||
public override EntityContext Context => EntityContext.Gen9a;
|
||||
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt8(stored);
|
||||
protected override void EncryptParty(Span<byte> party) => PokeCrypto.CryptArray(party, EncryptionConstant);
|
||||
|
||||
public PA9() : base(PokeCrypto.SIZE_9PARTY) => AffixedRibbon = PKHeX.Core.AffixedRibbon.None;
|
||||
public PA9() : base(PokeCrypto.SIZE_8PARTY) => AffixedRibbon = PKHeX.Core.AffixedRibbon.None;
|
||||
|
||||
public PA9(Memory<byte> data) : base(DecryptParty(data)) { }
|
||||
public override PA9 Clone() => new(Data.ToArray());
|
||||
|
||||
private static Memory<byte> DecryptParty(Memory<byte> data)
|
||||
{
|
||||
PokeCrypto.DecryptIfEncrypted9(ref data);
|
||||
if (data.Length >= PokeCrypto.SIZE_9PARTY)
|
||||
PokeCrypto.DecryptIfEncrypted8(data.Span);
|
||||
if (data.Length >= PokeCrypto.SIZE_8PARTY)
|
||||
return data;
|
||||
|
||||
var result = new byte[PokeCrypto.SIZE_9PARTY];
|
||||
var result = new byte[PokeCrypto.SIZE_8PARTY];
|
||||
data.Span.CopyTo(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private ushort CalculateChecksum() => Checksums.Add16(Data[8..PokeCrypto.SIZE_9STORED]);
|
||||
private ushort CalculateChecksum() => Checksums.Add16(Data[8..PokeCrypto.SIZE_8STORED]);
|
||||
|
||||
// Simple Generated Attributes
|
||||
public override byte CurrentFriendship
|
||||
|
|
@ -47,8 +49,8 @@ public override byte CurrentFriendship
|
|||
set { if (CurrentHandler == 0) OriginalTrainerFriendship = value; else HandlingTrainerFriendship = value; }
|
||||
}
|
||||
|
||||
public override int SIZE_PARTY => PokeCrypto.SIZE_9PARTY;
|
||||
public override int SIZE_STORED => PokeCrypto.SIZE_9STORED;
|
||||
public override int SIZE_PARTY => PokeCrypto.SIZE_8PARTY;
|
||||
public override int SIZE_STORED => PokeCrypto.SIZE_8STORED;
|
||||
|
||||
public override bool ChecksumValid => CalculateChecksum() == Checksum;
|
||||
public override void RefreshChecksum() => Checksum = CalculateChecksum();
|
||||
|
|
@ -76,11 +78,6 @@ public override byte CurrentFriendship
|
|||
public override int Characteristic => EntityCharacteristic.GetCharacteristicInit0(EncryptionConstant, IV32);
|
||||
|
||||
// Methods
|
||||
protected override byte[] Encrypt()
|
||||
{
|
||||
RefreshChecksum();
|
||||
return PokeCrypto.EncryptArray9(Data);
|
||||
}
|
||||
|
||||
public void FixRelearn()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public sealed class PB7 : G6PKM, IHyperTrain, IAwakened, IScaledSizeValue, IComb
|
|||
|
||||
private static Memory<byte> DecryptParty(Memory<byte> data)
|
||||
{
|
||||
PokeCrypto.DecryptIfEncrypted67(ref data);
|
||||
PokeCrypto.DecryptIfEncrypted67(data.Span);
|
||||
if (data.Length >= SIZE)
|
||||
return data;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ public sealed class PK1 : GBPKML, IPersonalType
|
|||
|
||||
public override bool Valid => Species <= 151 && (Data[0] == 0 || Species != 0);
|
||||
|
||||
public override int SIZE_PARTY => PokeCrypto.SIZE_1PARTY;
|
||||
public override int SIZE_STORED => PokeCrypto.SIZE_1STORED;
|
||||
public override int SIZE_STORED => Japanese ? PokeCrypto.SIZE_1JLIST : PokeCrypto.SIZE_1ULIST;
|
||||
public override int SIZE_PARTY => SIZE_STORED;
|
||||
public override bool Korean => false;
|
||||
|
||||
public override EntityContext Context => EntityContext.Gen1;
|
||||
|
||||
public PK1(bool jp = false) : base(PokeCrypto.SIZE_1PARTY, jp) { }
|
||||
public PK1(byte[] decryptedData, bool jp = false) : base(EnsurePartySize(decryptedData), jp) { }
|
||||
public PK1(Memory<byte> decryptedData, bool jp = false) : base(EnsurePartySize(decryptedData), jp) { }
|
||||
|
||||
public PK1(ReadOnlySpan<byte> data, ReadOnlySpan<byte> ot, ReadOnlySpan<byte> nick)
|
||||
: this(ot.Length == StringLengthJapanese)
|
||||
|
|
@ -27,11 +27,13 @@ public PK1(ReadOnlySpan<byte> data, ReadOnlySpan<byte> ot, ReadOnlySpan<byte> ni
|
|||
nick.CopyTo(NicknameTrash);
|
||||
}
|
||||
|
||||
private static byte[] EnsurePartySize(byte[] data)
|
||||
private static Memory<byte> EnsurePartySize(Memory<byte> data)
|
||||
{
|
||||
if (data.Length != PokeCrypto.SIZE_1PARTY)
|
||||
Array.Resize(ref data, PokeCrypto.SIZE_1PARTY);
|
||||
if (data.Length == PokeCrypto.SIZE_1PARTY)
|
||||
return data;
|
||||
var result = new byte[PokeCrypto.SIZE_1PARTY];
|
||||
data.CopyTo(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public override PK1 Clone()
|
||||
|
|
@ -42,7 +44,13 @@ public override PK1 Clone()
|
|||
return clone;
|
||||
}
|
||||
|
||||
protected override byte[] Encrypt() => PokeList1.WrapSingle(this);
|
||||
// We (PKHeX) internally manage as single-entry lists in temp buffers.
|
||||
public override int WriteDecryptedDataStored(Span<byte> destination) => PokeList1.WrapSingle(this, destination);
|
||||
public override void WriteEncryptedDataStored(Span<byte> destination) => WriteDecryptedDataStored(destination);
|
||||
public override void WriteDecryptedDataParty(Span<byte> destination) => WriteDecryptedDataStored(destination);
|
||||
public override void WriteEncryptedDataParty(Span<byte> destination) => WriteDecryptedDataStored(destination);
|
||||
public override void WriteDecryptedDataParty(Span<byte> stored, Span<byte> party) => WriteDecryptedDataStored(stored);
|
||||
public override void WriteEncryptedDataParty(Span<byte> stored, Span<byte> party) => WriteDecryptedDataStored(stored);
|
||||
|
||||
#region Stored Attributes
|
||||
public byte SpeciesInternal { get => Data[0]; set => Data[0] = value; } // raw access
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ public sealed class PK2 : GBPKML, ICaughtData2
|
|||
|
||||
public override bool Valid => Species <= Legal.MaxSpeciesID_2;
|
||||
|
||||
public override int SIZE_PARTY => PokeCrypto.SIZE_2PARTY;
|
||||
public override int SIZE_STORED => PokeCrypto.SIZE_2STORED;
|
||||
public override int SIZE_STORED => Japanese ? PokeCrypto.SIZE_2JLIST : PokeCrypto.SIZE_2ULIST;
|
||||
public override int SIZE_PARTY => SIZE_STORED;
|
||||
public override bool Korean => !Japanese && OriginalTrainerTrash[0] <= 0xB;
|
||||
|
||||
public override EntityContext Context => EntityContext.Gen2;
|
||||
|
||||
public PK2(bool jp = false) : base(PokeCrypto.SIZE_2PARTY, jp) { }
|
||||
public PK2(byte[] decryptedData, bool jp = false) : base(EnsurePartySize(decryptedData), jp) { }
|
||||
public PK2(Memory<byte> decryptedData, bool jp = false) : base(EnsurePartySize(decryptedData), jp) { }
|
||||
|
||||
public PK2(ReadOnlySpan<byte> data, ReadOnlySpan<byte> ot, ReadOnlySpan<byte> nick)
|
||||
: this(ot.Length == StringLengthJapanese)
|
||||
|
|
@ -27,11 +27,13 @@ public PK2(ReadOnlySpan<byte> data, ReadOnlySpan<byte> ot, ReadOnlySpan<byte> ni
|
|||
nick.CopyTo(NicknameTrash);
|
||||
}
|
||||
|
||||
private static byte[] EnsurePartySize(byte[] data)
|
||||
private static Memory<byte> EnsurePartySize(Memory<byte> data)
|
||||
{
|
||||
if (data.Length != PokeCrypto.SIZE_2PARTY)
|
||||
Array.Resize(ref data, PokeCrypto.SIZE_2PARTY);
|
||||
if (data.Length == PokeCrypto.SIZE_2PARTY)
|
||||
return data;
|
||||
var result = new byte[PokeCrypto.SIZE_2PARTY];
|
||||
data.CopyTo(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public override PK2 Clone()
|
||||
|
|
@ -42,7 +44,14 @@ public override PK2 Clone()
|
|||
return clone;
|
||||
}
|
||||
|
||||
protected override byte[] Encrypt() => PokeList2.WrapSingle(this);
|
||||
|
||||
// We (PKHeX) internally manage as single-entry lists in temp buffers.
|
||||
public override int WriteDecryptedDataStored(Span<byte> destination) => PokeList2.WrapSingle(this, destination);
|
||||
public override void WriteEncryptedDataStored(Span<byte> destination) => WriteDecryptedDataStored(destination);
|
||||
public override void WriteDecryptedDataParty(Span<byte> destination) => WriteDecryptedDataStored(destination);
|
||||
public override void WriteEncryptedDataParty(Span<byte> destination) => WriteDecryptedDataStored(destination);
|
||||
public override void WriteDecryptedDataParty(Span<byte> stored, Span<byte> party) => WriteDecryptedDataStored(stored);
|
||||
public override void WriteEncryptedDataParty(Span<byte> stored, Span<byte> party) => WriteDecryptedDataStored(stored);
|
||||
|
||||
#region Stored Attributes
|
||||
public override ushort Species { get => Data[0]; set => Data[0] = (byte)value; }
|
||||
|
|
|
|||
|
|
@ -16,10 +16,12 @@ public sealed class PK3 : G3PKM, ISanityChecksum
|
|||
|
||||
public PK3() : base(PokeCrypto.SIZE_3PARTY) { }
|
||||
public PK3(Memory<byte> data) : base(DecryptParty(data)) { }
|
||||
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt3(stored);
|
||||
protected override void EncryptParty(Span<byte> party) { }
|
||||
|
||||
private static Memory<byte> DecryptParty(Memory<byte> data)
|
||||
{
|
||||
PokeCrypto.DecryptIfEncrypted3(ref data);
|
||||
PokeCrypto.DecryptIfEncrypted3(data.Span);
|
||||
if (data.Length >= PokeCrypto.SIZE_3PARTY)
|
||||
return data;
|
||||
|
||||
|
|
@ -199,12 +201,6 @@ public override bool IsEgg
|
|||
public override int Stat_SPD { get => ReadUInt16LittleEndian(Data[0x62..]); set => WriteUInt16LittleEndian(Data[0x62..], (ushort)value); }
|
||||
#endregion
|
||||
|
||||
protected override byte[] Encrypt()
|
||||
{
|
||||
RefreshChecksum();
|
||||
return PokeCrypto.EncryptArray3(Data);
|
||||
}
|
||||
|
||||
private ushort CalculateChecksum() => Checksums.Add16(Data[0x20..PokeCrypto.SIZE_3STORED]);
|
||||
|
||||
public override void RefreshChecksum()
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public sealed class PK4 : G4PKM
|
|||
|
||||
private static Memory<byte> DecryptParty(Memory<byte> data)
|
||||
{
|
||||
PokeCrypto.DecryptIfEncrypted45(ref data);
|
||||
PokeCrypto.DecryptIfEncrypted45(data.Span);
|
||||
if (data.Length >= PokeCrypto.SIZE_4PARTY)
|
||||
return data;
|
||||
|
||||
|
|
@ -292,11 +292,6 @@ public override ushort MetLocationDP
|
|||
#endregion
|
||||
|
||||
// Methods
|
||||
protected override byte[] Encrypt()
|
||||
{
|
||||
RefreshChecksum();
|
||||
return PokeCrypto.EncryptArray45(Data);
|
||||
}
|
||||
|
||||
public BK4 ConvertToBK4()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -22,13 +22,15 @@ public sealed class PK5 : PKM, ISanityChecksum,
|
|||
public override int SIZE_STORED => PokeCrypto.SIZE_5STORED;
|
||||
public override EntityContext Context => EntityContext.Gen5;
|
||||
public override PersonalInfo5B2W2 PersonalInfo => PersonalTable.B2W2.GetFormEntry(Species, Form);
|
||||
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt45(stored);
|
||||
protected override void EncryptParty(Span<byte> party) => PokeCrypto.CryptArray(party, EncryptionConstant);
|
||||
|
||||
public PK5() : base(PokeCrypto.SIZE_5PARTY) { }
|
||||
public PK5(Memory<byte> data) : base(DecryptParty(data)) { }
|
||||
|
||||
private static Memory<byte> DecryptParty(Memory<byte> data)
|
||||
{
|
||||
PokeCrypto.DecryptIfEncrypted45(ref data);
|
||||
PokeCrypto.DecryptIfEncrypted45(data.Span);
|
||||
if (data.Length >= PokeCrypto.SIZE_5PARTY)
|
||||
return data;
|
||||
|
||||
|
|
@ -307,11 +309,6 @@ public override string Nickname
|
|||
public override int MaxStringLengthNickname => 10;
|
||||
|
||||
// Methods
|
||||
protected override byte[] Encrypt()
|
||||
{
|
||||
RefreshChecksum();
|
||||
return PokeCrypto.EncryptArray45(Data);
|
||||
}
|
||||
|
||||
// Synthetic Trading Logic
|
||||
public bool BelongsTo(ITrainerInfo tr)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public sealed class PK6 : G6PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetC
|
|||
|
||||
private static Memory<byte> DecryptParty(Memory<byte> data)
|
||||
{
|
||||
PokeCrypto.DecryptIfEncrypted67(ref data);
|
||||
PokeCrypto.DecryptIfEncrypted67(data.Span);
|
||||
if (data.Length >= PokeCrypto.SIZE_6PARTY)
|
||||
return data;
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ public sealed class PK7 : G6PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetC
|
|||
|
||||
private static Memory<byte> DecryptParty(Memory<byte> data)
|
||||
{
|
||||
PokeCrypto.DecryptIfEncrypted67(ref data);
|
||||
PokeCrypto.DecryptIfEncrypted67(data.Span);
|
||||
if (data.Length >= PokeCrypto.SIZE_6PARTY)
|
||||
return data;
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ public sealed class PK9 : PKM, ISanityChecksum, ITeraType, ITechRecord, IObedien
|
|||
public IPermitRecord Permit => PersonalInfo;
|
||||
public override EntityContext Context => EntityContext.Gen9;
|
||||
|
||||
public PK9() : base(PokeCrypto.SIZE_9PARTY)
|
||||
public PK9() : base(PokeCrypto.SIZE_8PARTY)
|
||||
{
|
||||
AffixedRibbon = PKHeX.Core.AffixedRibbon.None;
|
||||
TeraTypeOverride = (MoveType)TeraTypeUtil.OverrideNone;
|
||||
|
|
@ -34,16 +34,18 @@ public PK9() : base(PokeCrypto.SIZE_9PARTY)
|
|||
|
||||
private static Memory<byte> DecryptParty(Memory<byte> data)
|
||||
{
|
||||
PokeCrypto.DecryptIfEncrypted9(ref data);
|
||||
if (data.Length >= PokeCrypto.SIZE_9PARTY)
|
||||
PokeCrypto.DecryptIfEncrypted8(data.Span);
|
||||
if (data.Length >= PokeCrypto.SIZE_8PARTY)
|
||||
return data;
|
||||
|
||||
var result = new byte[PokeCrypto.SIZE_9PARTY];
|
||||
var result = new byte[PokeCrypto.SIZE_8PARTY];
|
||||
data.Span.CopyTo(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private ushort CalculateChecksum() => Checksums.Add16(Data[8..PokeCrypto.SIZE_9STORED]);
|
||||
private ushort CalculateChecksum() => Checksums.Add16(Data[8..PokeCrypto.SIZE_8STORED]);
|
||||
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt8(stored);
|
||||
protected override void EncryptParty(Span<byte> party) => PokeCrypto.CryptArray(party, EncryptionConstant);
|
||||
|
||||
// Simple Generated Attributes
|
||||
public override byte CurrentFriendship
|
||||
|
|
@ -52,8 +54,8 @@ public override byte CurrentFriendship
|
|||
set { if (CurrentHandler == 0) OriginalTrainerFriendship = value; else HandlingTrainerFriendship = value; }
|
||||
}
|
||||
|
||||
public override int SIZE_PARTY => PokeCrypto.SIZE_9PARTY;
|
||||
public override int SIZE_STORED => PokeCrypto.SIZE_9STORED;
|
||||
public override int SIZE_PARTY => PokeCrypto.SIZE_8PARTY;
|
||||
public override int SIZE_STORED => PokeCrypto.SIZE_8STORED;
|
||||
|
||||
public override bool ChecksumValid => CalculateChecksum() == Checksum;
|
||||
public override void RefreshChecksum() => Checksum = CalculateChecksum();
|
||||
|
|
@ -81,11 +83,6 @@ public override byte CurrentFriendship
|
|||
public override int Characteristic => EntityCharacteristic.GetCharacteristicInit0(EncryptionConstant, IV32);
|
||||
|
||||
// Methods
|
||||
protected override byte[] Encrypt()
|
||||
{
|
||||
RefreshChecksum();
|
||||
return PokeCrypto.EncryptArray9(Data);
|
||||
}
|
||||
|
||||
public void FixRelearn()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -29,11 +29,6 @@ public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILa
|
|||
protected PKM(Memory<byte> data) => Raw = data;
|
||||
protected PKM([ConstantExpected] int size) => Raw = new byte[size];
|
||||
|
||||
public virtual byte[] EncryptedPartyData => Encrypt().AsSpan()[..SIZE_PARTY].ToArray();
|
||||
public virtual byte[] EncryptedBoxData => Encrypt().AsSpan()[..SIZE_STORED].ToArray();
|
||||
public virtual byte[] DecryptedPartyData => Write()[..SIZE_PARTY].ToArray();
|
||||
public virtual byte[] DecryptedBoxData => Write()[..SIZE_STORED].ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Rough indication if the data is junk or not.
|
||||
/// </summary>
|
||||
|
|
@ -49,17 +44,61 @@ public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILa
|
|||
/// </summary>
|
||||
public virtual void PrepareNickname() { }
|
||||
|
||||
protected abstract byte[] Encrypt();
|
||||
public abstract EntityContext Context { get; }
|
||||
public byte Format => Context.Generation;
|
||||
public TrainerIDFormat TrainerIDDisplayFormat => this.GetTrainerIDFormat();
|
||||
|
||||
private Span<byte> Write()
|
||||
/// <summary> Writes the entity data to a sequential (stored only, no party stats) buffer destination. </summary>
|
||||
public virtual int WriteDecryptedDataStored(Span<byte> destination)
|
||||
{
|
||||
RefreshChecksum();
|
||||
return Data;
|
||||
int length = SIZE_STORED;
|
||||
Data[..length].CopyTo(destination);
|
||||
return length;
|
||||
}
|
||||
|
||||
/// <summary> Writes the entity data to a sequential (stored, party) buffer destination. </summary>
|
||||
public virtual void WriteDecryptedDataParty(Span<byte> destination)
|
||||
{
|
||||
var stored = destination[..SIZE_STORED];
|
||||
var party = destination[SIZE_STORED..SIZE_PARTY];
|
||||
WriteDecryptedDataParty(stored, party);
|
||||
}
|
||||
|
||||
/// <summary> Writes the entity data to a separate (stored, party) buffer destination. </summary>
|
||||
public virtual void WriteDecryptedDataParty(Span<byte> stored, Span<byte> party)
|
||||
{
|
||||
WriteDecryptedDataStored(stored);
|
||||
Data[SIZE_STORED..SIZE_PARTY].CopyTo(party);
|
||||
}
|
||||
|
||||
/// <summary> Writes the entity data to a sequential (stored only, no party stats) buffer destination and encrypts to the at-rest state. </summary>
|
||||
public virtual void WriteEncryptedDataStored(Span<byte> destination)
|
||||
{
|
||||
var stored = destination[..SIZE_STORED];
|
||||
WriteDecryptedDataStored(stored);
|
||||
EncryptStored(stored);
|
||||
}
|
||||
|
||||
/// <summary> Writes the entity data to a sequential (stored, party) buffer destination and encrypts to the at-rest state. </summary>
|
||||
public virtual void WriteEncryptedDataParty(Span<byte> destination)
|
||||
{
|
||||
var stored = destination[..SIZE_STORED];
|
||||
var party = destination[SIZE_STORED..SIZE_PARTY];
|
||||
WriteEncryptedDataParty(stored, party);
|
||||
}
|
||||
|
||||
/// <summary> Writes the entity data to a separate (stored, party) buffer destination and encrypts to the at-rest state. </summary>
|
||||
public virtual void WriteEncryptedDataParty(Span<byte> stored, Span<byte> party)
|
||||
{
|
||||
WriteDecryptedDataParty(stored, party);
|
||||
EncryptStored(stored);
|
||||
EncryptParty(party);
|
||||
}
|
||||
|
||||
protected abstract void EncryptStored(Span<byte> stored);
|
||||
protected abstract void EncryptParty(Span<byte> party);
|
||||
|
||||
// Surface Properties
|
||||
public abstract ushort Species { get; set; }
|
||||
public abstract string Nickname { get; set; }
|
||||
|
|
@ -1163,4 +1202,23 @@ public void ClearInvalidMoves()
|
|||
5 => IV_SPD,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index), index, "IV index must be between 0 and 5."),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current <see cref="PKM"/> has the same stored data as another <see cref="PKM"/>. This is used to check if a PKM has been modified from its original imported state.
|
||||
/// </summary>
|
||||
public virtual bool EqualsStored(PKM pk)
|
||||
{
|
||||
// Generally, the objects should be of the same derived type. Don't bother checking that explicitly.
|
||||
if (pk.PID != PID)
|
||||
return false;
|
||||
|
||||
var stored = pk.Data;
|
||||
if (stored.Length >= pk.SIZE_STORED)
|
||||
stored = stored[..SIZE_STORED];
|
||||
var self = Data;
|
||||
if (self.Length >= SIZE_STORED)
|
||||
self = self[..SIZE_STORED];
|
||||
|
||||
return stored.SequenceEqual(self);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public sealed class RK4 : G4PKM
|
|||
private static Memory<byte> Decrypt(Memory<byte> data)
|
||||
{
|
||||
data = data[..PokeCrypto.SIZE_4RSTORED];
|
||||
PokeCrypto.DecryptIfEncrypted45(ref data);
|
||||
PokeCrypto.DecryptIfEncrypted45(data.Span[..PokeCrypto.SIZE_4STORED]);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
@ -275,33 +275,29 @@ public override ushort MetLocationDP
|
|||
|
||||
#region Battle Stats
|
||||
public override int Status_Condition { get => 0; set { } }
|
||||
|
||||
// Battle Stats never stored; compute on the fly (a little wasteful if checking more than 1 at a time).
|
||||
public override byte Stat_Level { get => CurrentLevel; set { } }
|
||||
public override int Stat_HPCurrent { get => PersonalInfo.HP; set { } }
|
||||
public override int Stat_HPMax { get => PersonalInfo.HP; set { } }
|
||||
public override int Stat_ATK { get => PersonalInfo.ATK; set { } }
|
||||
public override int Stat_DEF { get => PersonalInfo.DEF; set { } }
|
||||
public override int Stat_SPE { get => PersonalInfo.SPE; set { } }
|
||||
public override int Stat_SPA { get => PersonalInfo.SPA; set { } }
|
||||
public override int Stat_SPD { get => PersonalInfo.SPD; set { } }
|
||||
public override int Stat_HPCurrent { get => GetStats(PersonalInfo)[0]; set { } }
|
||||
public override int Stat_HPMax { get => GetStats(PersonalInfo)[0]; set { } }
|
||||
public override int Stat_ATK { get => GetStats(PersonalInfo)[1]; set { } }
|
||||
public override int Stat_DEF { get => GetStats(PersonalInfo)[2]; set { } }
|
||||
public override int Stat_SPE { get => GetStats(PersonalInfo)[3]; set { } }
|
||||
public override int Stat_SPA { get => GetStats(PersonalInfo)[4]; set { } }
|
||||
public override int Stat_SPD { get => GetStats(PersonalInfo)[5]; set { } }
|
||||
|
||||
#endregion
|
||||
|
||||
#region My Pokémon Ranch Data
|
||||
#region My Pokémon Ranch Data (never encrypted)
|
||||
|
||||
/* ====Metadata====
|
||||
* uint8_t poke_type;// 01 trainer, 04 hayley, 05 traded
|
||||
* unused alignment byte
|
||||
* uint16_t tradeable;// 02 is tradeable, normal 00
|
||||
* uint16_t tid;
|
||||
* uint16_t sid;
|
||||
* uint32_t name1;
|
||||
* uint32_t name2;
|
||||
* uint32_t name3;
|
||||
* uint32_t name4;
|
||||
* uint32_t trainerId;
|
||||
* char[10] name;
|
||||
*/
|
||||
|
||||
// 4 bytes extra at the end of the metadata, unused/reserved; or, it's just extra for the Trainer Name.
|
||||
|
||||
public RanchOwnershipType OwnershipType
|
||||
{
|
||||
get => (RanchOwnershipType)Data[0x88];
|
||||
|
|
@ -314,10 +310,11 @@ public RanchOwnershipStatus OwnershipStatus
|
|||
set => WriteUInt16BigEndian(Data[0x8A..], (ushort)value);
|
||||
}
|
||||
|
||||
public uint HandlingTrainerID32 { get => ReadUInt32LittleEndian(Data[0x8C..]); set => WriteUInt32LittleEndian(Data[0x8C..], value); }
|
||||
public ushort HandlingTrainerTID { get => ReadUInt16LittleEndian(Data[0x8C..]); set => WriteUInt16LittleEndian(Data[0x8C..], value); }
|
||||
public ushort HandlingTrainerSID { get => ReadUInt16LittleEndian(Data[0x8E..]); set => WriteUInt16LittleEndian(Data[0x8E..], value); }
|
||||
|
||||
public override Span<byte> HandlingTrainerTrash => Data.Slice(0x90, 0x10);
|
||||
public override Span<byte> HandlingTrainerTrash => Data.Slice(0x90, 0x14);
|
||||
public override string HandlingTrainerName
|
||||
{
|
||||
get => StringConverter4.GetString(HandlingTrainerTrash);
|
||||
|
|
@ -327,17 +324,6 @@ public override string HandlingTrainerName
|
|||
#endregion
|
||||
|
||||
// Methods
|
||||
protected override byte[] Encrypt()
|
||||
{
|
||||
RefreshChecksum();
|
||||
|
||||
byte[] data = Data.ToArray();
|
||||
byte[] pkData = data[..PokeCrypto.SIZE_4STORED];
|
||||
pkData = PokeCrypto.EncryptArray45(pkData);
|
||||
pkData.CopyTo(data, 0);
|
||||
return data;
|
||||
}
|
||||
|
||||
public PK4 ConvertToPK4()
|
||||
{
|
||||
byte[] data = Data[..PokeCrypto.SIZE_4STORED].ToArray();
|
||||
|
|
@ -358,4 +344,7 @@ public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
|
|||
public override int GetStringLength(ReadOnlySpan<byte> data)
|
||||
=> TrashBytesUTF16.GetStringLength(data, StringConverter4.Terminator);
|
||||
public override int GetBytesPerChar() => 2;
|
||||
|
||||
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt45(stored[..PokeCrypto.SIZE_4STORED]);
|
||||
protected override void EncryptParty(Span<byte> party) { }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ public override SK2 Clone() => new(Data.ToArray(), Japanese)
|
|||
IsEgg = IsEgg,
|
||||
};
|
||||
|
||||
protected override byte[] Encrypt() => Data.ToArray();
|
||||
|
||||
#region Stored Attributes
|
||||
public override ushort Species { get => Data[0]; set => Data[0] = (byte)value; }
|
||||
public override int SpriteItem => ItemConverter.GetItemFuture2((byte)HeldItem);
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user