Compare commits

...

65 Commits

Author SHA1 Message Date
BtEtta
c2bb4188e5
Add a movement threshold for initiating drag & drop operations (#4794) 2026-04-26 16:49:49 -05:00
BtEtta
906cae24e8
Fix/optimisation pass on all PNG images (#4788)
Closes #4785
2026-04-25 12:22:16 -05:00
Coki628
2ff7ad40d9
Gen2 HP Up item name fix (#4787)
* Update text_ItemsG2_ja.txt
`ポイントアップ` means PP Up in Japanese, not HP Up.
HP Up is `マックスアップ`.

Updated unit tests to ensure all gen1-3 string lists are unique item names.
2026-04-25 12:16:54 -05:00
Kurt
cc7708e9d4 Misc fixes
Apply relearn moves when static8=>pk8 for dlc2 horses
Fix tera import regen; the PKH=>PK9 is correct, this rejuv was just undoing it.
2026-04-24 17:50:17 -05:00
Kurt
3d7a371395 Gen3: encounter legality tweaks
Gen3-5: Egg (breeding) requesting shiny now loops to match criteria.
Gen3: Add evolution nickname bypass for trash byte application
Gen3: Fix japanese OT prefill check from not actually working
Gen3: Add missing Voltorb encounter (was superseded by Wild slot, but aggressive checks since added).
2026-04-22 23:37:11 -05:00
Kurt
8a1c364207 Add threshold for transparent pixel handling
Artwork Pokemon Sprites have artefacts when rendering Sprite Glow due to them having small-value ARGB pixels. PKHeX was originally designed with the assumption of pure pixels, and not blurred edges from downscaling.

Co-Authored-By: abcboy101 <16735361+abcboy101@users.noreply.github.com>

#4785

#4785
2026-04-22 22:59:00 -05:00
Manu
0a1e955007
Handle HOME ZA gifts (#4786)
* Handle PA9 <-> PKH IsAlpha conversion

* IsHomeGift checks

* Read Ribbons, Scale, Height and Weight from HOME cards

* Extended legality checks
2026-04-21 22:31:59 -05:00
Ka-n00b
e3fd457272
Update Translations (#4784)
* Update const_oras_es-419.txt

* Update const_oras_es.txt

* Update flags_oras_es-419.txt

* Update flags_oras_es.txt

* Update flags_oras_ko.txt

* Update flags_sm_es-419.txt

* Update lang_de.txt

* Update lang_en.txt

* Update lang_es-419.txt

* Update lang_es.txt

* Update lang_fr.txt

* Update lang_it.txt

* Update lang_ja.txt

* Update lang_ko.txt

* Update lang_zh-Hans.txt

* Update lang_zh-Hant.txt

* Update lang_de.txt

* Update lang_ko.txt

* Update lang_fr.txt

* Update lang_zh-Hant.txt

* Update lang_fr.txt

* Update lang_it.txt

* Update lang_fr.txt

* Update lang_ko.txt

* Update lang_es-419.txt

* Update lang_es.txt

* Update lang_es.txt

* Update lang_es-419.txt

* Update lang_it.txt

* Update lang_ko.txt

* Update lang_es-419.txt

* Update lang_zh-Hans.txt

* Update lang_zh-Hant.txt

* Update lang_zh-Hans.txt

* Update lang_zh-Hant.txt

* Update lang_ja.txt

* Update lang_de.txt

* Update lang_de.txt

* Update lang_es-419.txt

* Update lang_es.txt

* Update lang_de.txt

* Update lang_ko.txt

* Update lang_de.txt

* Update lang_es-419.txt

* Update lang_es.txt

* Update lang_de.txt

* Update lang_es.txt

* Update lang_es-419.txt

* Update legality_es-419.json

* Update legality_es.json

* Update lang_de.txt
2026-04-21 01:17:41 -05:00
Kurt
08178f70fe Fix g1 outsider trade evo message
https://projectpokemon.org/home/forums/topic/67931-bug-report-transferring-haunter-from-red-save-file-to-blue-save-file-giving-error/#comment-300058
2026-04-19 22:08:44 -05:00
Kurt
75e2a7a497 Rearrange tab indexes to follow visual
Closes #4781
2026-04-17 23:10:10 -05:00
Ka-n00b
516cc25d91
Update Translations (#4782)
* Update legality_de.json

* Update lang_de.txt

* Update lang_en.txt

* Update lang_es-419.txt

* Update lang_es.txt

* Update lang_fr.txt

* Update lang_it.txt

* Update lang_ja.txt

* Update lang_ko.txt

* Update lang_zh-Hans.txt

* Update lang_zh-Hant.txt

* Update lang_zh-Hant.txt

* Update lang_en.txt

* Update lang_fr.txt

* Update lang_it.txt

* Update lang_de.txt
2026-04-17 22:50:57 -05:00
Joel Suárez
4007eb7ca8
Battle Chateau rank editor for XY trainer data (#4780)
Directly edit Rank and Points
Translated rank titles
2026-04-17 22:50:25 -05:00
Kurt
452c8b5b81 Misc tweaks
SAV9ZA: Fix pc data import
SAV9ZA: More metadata writing for party; only write first `01` for non-basepatch saves.
2026-04-17 22:26:15 -05:00
Kurt
ee79ba2f61 WC9: Permit 255-all hisuian zoroark size
HOME misapplying PLA's size fixing, fun. Seems it was fixed in 4.0.0 (after 3.2.1?) or server side. A last-minute redeem=>deposit was "fixed" to 255, so the full date range of the card is allowed to be mutated.
2026-04-16 00:36:38 -05:00
Lusamine
dfcd86d950 Replace colon in French translation
Colon was previously removed because the label is too long, but it is still too long. Better fix would be to realign the labels (tbd).
2026-04-15 22:00:28 -05:00
Ka-n00b
c58981e365
Update Translations (#4776)
- Updated and revised the encountered Ground Tiles for generation 4 to match up with English in all other languages.
- Update Event Constants for some Spanish and Korean games.
- Additional translation additions, revisions, spacing adjustments, and typo fixes.
2026-04-15 21:49:36 -05:00
Kurt
2f9512e33b Misc tweaks
Fix box export (individual files) not including party data in the bin. Previous behavior would have 00'd party data if not force-calculated. Just calculate party stats if not present for the format. Log Database entity as party format, just to avoid using the Stored format size.
Revise gen2 odd egg declarations to group shiny eggs together, and enforce the Shiny property for object filtering. If IVs are specified, then Shininess property needs to be provided (rather than "random" which is untrue).
Simplify some logic paths for trade1/trade2. Extract some of the nuance of transferred Hiragana Dugtrio so the unit test is xref'd nicely.
2026-04-15 21:49:07 -05:00
ry
234f402beb
update odd egg IVs (#4777) 2026-04-14 15:40:14 -05:00
Kurt
021b93b87f HT lang: allow LATAM iff 9a
also allow WA9 latam lang (get/set was defaulting to english which probably matched anyway)
add a sanity check to TryGetSpecies which was only used by Gen1 with a hardcoded language ID. maybe someone will use it in their own project, might as well prevent an exception.
2026-04-12 10:20:23 -05:00
Kurt
227b9aaf13 Misc transfer regression for x=>SV 2026-04-11 20:16:12 -05:00
Kurt
2ece772735 Misc tweaks for Z-A slot presence
Not really an issue cuz the game sets them to `0` or `1` on startup, but clearing boxes/slots should match runtime behavior. livehex after clearing boxes can result in slots that don't update until the game is rebooted. no more ;)

should probably refactor the API a little better to avoid virtual spaghetti
2026-04-11 19:47:40 -05:00
Kurt
2cb62f7aa4 Update 26.04.11 2026-04-11 18:53:56 -05:00
Kurt
6b8599b6ed Revise PKM editor nickname check for gen4/5
Closes #4773
2026-04-11 18:33:00 -05:00
Kurt
b449867e72 Remove SAV.SetPKM formarg auto-fixing
Circling back to my notes on formarg updating; it's not ideal to update these values automatically, as it's wiping the streak values for the current game, or if carried forward into future games. The user will have to manage them since the legality flagging now handles warning the user. Better than hiding and overwriting values to something the user can't control.
5e7fc6c817
2026-04-11 18:06:08 -05:00
Kurt
9c03ef1f23 Support champions set imports 2026-04-11 18:00:42 -05:00
Kurt
4568392f61 Widen move 16px, extrabyte +4px
Shifted down extrabyte edits a little more for aesthetics.
Move combobox werent as wide as the relearn moves, so +16 to make them match.

update translation resources with latest additions
2026-04-11 10:45:51 -05:00
Kurt
2b4a01512e Add setting to quiet every other sound made
Closes #4774
2026-04-10 00:48:34 -05:00
sora10pls
13110adfd7 Add Pokémon and item icons from Champions 2026-04-08 10:08:14 -04:00
Kurt
2321e764cb Update GameDataPC9.cs 2026-04-08 01:43:01 -05:00
Kurt
298a53130c Refine home's champions data
PC9 in RAM is loosely deserialized, not a sequential blob. Will need to see how the json is stored in the at-rest savedata. Probably won't be something useful to directly integrate into PKHeX since it's online-only.
2026-04-08 00:10:42 -05:00
Ka-n00b
d7284ce590
Update Translations (#4772)
* Update lang_es.txt

* Update lang_es-419.txt

* Update lang_it.txt

* Update lang_fr.txt

* Update lang_de.txt

* Update lang_zh-Hant.txt

* Update lang_ko.txt

* Update lang_ko.txt

* Update lang_ko.txt

* Update lang_ko.txt

* Update lang_it.txt
2026-04-07 01:03:04 -05:00
Kurt
301bf1bec0 Add donut generator
Closes #4669
better late than never
2026-04-06 22:28:30 -05:00
Kurt
fc80208a06 Reattach form(arg) changed events
not sure how they were unhooked
slight unnoticeable tweaks to layout
2026-04-06 14:34:50 -05:00
Kurt
9742ed43da Bump test case for eh4
No structural change besides the first u16 `03` => `04`.
2026-04-06 10:13:49 -05:00
Kurt
0ebf575a03 gen5: fix dex skin footer, fix musical terminator
Closes #4603
Add some filename import length sanitization
2026-04-06 01:04:26 -05:00
Kurt
097ff3a870 Update SAV_Trainer7.cs
Fixes #4770
2026-04-05 23:59:07 -05:00
Kurt
6da88d6232 Reflow pkm editor main & ot tabs as TableLayout
less designer code (-250) so I think this is a win

* Friendship moved to OT/Misc tab since it is much less relevant in current formats; HT now shows the friendship rather than flexing CurrentFriendship.
2026-04-05 23:48:23 -05:00
Kurt
e132a2719a Remove unnecessary legality check text
Eggs no longer yield with event forms, so there's no need to say "this specific event form is not valid" when it's already flagged as such.
2026-04-05 23:44:04 -05:00
Kurt
5e7fc6c817 Rewrite formargument editing control
A fair bit of these SetPKM updaters need to stop touching FormArgument now.
2026-04-05 23:43:17 -05:00
Kurt
245873aa8a Extract formarg verification logic for tri-days 2026-04-05 23:42:31 -05:00
Kurt
f31503ac7c Update handling for HOME=>ZA edge cases 2026-04-04 15:53:27 -05:00
Kurt
8d24b16fe3 Misc tweaks
the final "TODO ZA" and "TODO HOME ZA"
remove champions from blank save selection (would need PC9 docs first)
2026-04-03 01:07:51 -05:00
Kurt
d21bc2ff6d Allow pkh => import 2026-04-03 00:41:09 -05:00
Kurt
7c4b15bffa Disallow affixed ribbons in ZA/LA w/o transfer 2026-04-03 00:11:52 -05:00
Kurt
02eaca9a0c Add initial legality handling for ZA HOME transfer 2026-04-02 23:55:01 -05:00
Ka-n00b
dc2a60f830
Update Spanish Event Flags (#4768)
Some more names, and also 2 small typo fixes in the French UI text adding s at the end of "Solde" for the Pass Powers.
2026-04-02 21:44:04 -05:00
Ka-n00b
d09a425e6a
Update Translations (#4767)
- Translations mostly complete for the UI and legality message strings in Spanish, Korean, and Traditional Chinese,
- Includes the titles for generation 5 Pass Powers, generation 6 O-Powers, as well as officially used terms for things such as Memories, Athlete Points etc.
- Some official nouns used in the Spain's Spanish translation were using a mix of LATAM terms; they've been corrected to the ones used for that version, and LATAM was corrected or retained to what it should use.
- Made some text shorter in some languages to fit the UI labels and buttons.
- A few typo corrections and additional translation text to the German, Spanish, and Korean READMEs.
2026-04-02 12:25:29 -05:00
Kurt
fcdce737a3 Allow LATAM Spanish to backwards transfer
I'll do it if you won't.
Also add handling for core=>ZA transfers, along with this ZA=>core=>side handling.
2026-04-02 12:24:20 -05:00
sora10pls
d9bdbcae68 Update ES-LA met strings to match HOME 4.0.0 2026-04-02 12:53:13 -04:00
Kurt
5f44fb75d4 add champions side-data storage
note lack of training values being stored in home; possibly they're managed server-side in champions. wonder if they'll allow inter-player training transfer?
the 0x10 could be a hash of immutable core data... still have to wait for Champions to release to see what data is filled in, since HOME doesn't seem to manage it?
2026-04-02 08:48:51 -05:00
Kurt
d15526a0be Add initial PKH side struct for ZA
Haven't bothered testing because maintenance; just disassembled the pa9=>pkh import function. There's a side(sizeof=25) assumedly allocated for Champions. Rare chance it's actually for Gen3; can't find the "create" method due to virtualization/indirection in the disassembly.
2026-04-02 01:14:56 -05:00
sora10pls
5567577a26 Add date ranges for HOME 4.0.0 Z-A connectivity gifts 2026-04-01 11:39:51 -04:00
Kurt
e41797d0cb oops
fix bit shift missed in prior commit
2026-03-29 21:30:01 -05:00
Kurt
5b511a8078 Misc champions version side-effects
fixes enc generator crash from bdsp
fixes enc generation yielding gen9 eggs for champions version
adds version text for champions based on whatever bulbapedia currently has for localizations (champions for all except ja)
2026-03-29 16:35:38 -05:00
Kurt
d8481e711b Fix gen3 options bitflags
off by a few bits
Closes #4766
2026-03-29 16:11:18 -05:00
Kurt
aad2839736 Fix party stat decrypt
from refactor
2026-03-26 15:36:14 -05:00
Kurt
49505a4a8c Update EntitySearchControl.cs 2026-03-25 21:58:36 -05:00
Kurt
ecfd8f6748 Misc tweaks
Fix ranch lengths (I wonder if BK4's handling can be adapted to RK4 for these appended-to-entity fields -- I doubt they modified the PK4 struct)
2026-03-25 08:35:46 -05:00
Kurt
5bf1e2cf45
PKM: Reduce allocation via in-place decryption logic (#4764) 2026-03-24 21:31:59 -05:00
Kurt
793525875f Misc tweaks
no functional change

Preview: fix hover card's off-by-one on move index indication.
PB7: Initialize editor's default date-time to a valid value when none present/invalid.
ID32: move to extension block with other methods
PIDVerifier: use xor method from ShinyUtil rather than inlined (can stay inlined elsewhere)
battlepass: revise parens for clarity
2026-03-24 00:31:51 -05:00
Kurt
492aea166e main dragout: track drag operation, keep cursor
drag enter was triggering once the mouse moved, resetting to hand -- not anymore.
2026-03-22 14:16:36 -05:00
Kurt
83071ca7c2 Update SAV_Trainer9a.Designer.cs
Closes #4762
2026-03-22 01:39:09 -05:00
Ka-n00b
6df330e62f
Update Translations and Event Flags (#4761) 2026-03-21 19:37:53 -05:00
间辞
66df00d038
Add files via upload (#4760) 2026-03-21 02:20:20 -05:00
Kurt
4784e2de82 Update ItemStorage3E.cs
Closes #4759
2026-03-21 01:43:10 -05:00
7662 changed files with 18731 additions and 12306 deletions

View File

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

View File

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

View File

@ -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(통합 개발 환경)

View File

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

View File

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

View File

@ -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 &amp; format.
/// </summary>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,186 @@ 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 static bool IsFormArgumentValidFurfrou8HOME(IFormArgument f, IEncounterTemplate enc)
{
if (f.FormArgument == 0 && enc is { Version: GameVersion.GO })
return true; // Does not come with a Form Argument.
return IsFormArgumentDayCounterValid(f, 5, enc.Generation < 8);
}
private CheckResult CheckPrimeape(LegalityAnalysis data, PKM pk, uint arg, IEncounterTemplate enc)
{
if (arg == 0)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -139,9 +139,13 @@ private static void FlagIsNicknameClean(LegalityAnalysis data, PK3 pk)
// International games are 10 chars (full buffer) max; implicit terminator if full.
var nick = pk.GetNicknamePrefillRegion();
if (!TrashByteRules3.IsTerminatedFF(nick))
{
// Trade to another language and evolve will treat it like a nickname, without actually filling with FF.
if (!TrashByteRules3.IsTerminatedFFZero(nick) || pk.Species == data.EncounterOriginal.Species) // not evolved
data.AddLine(GetInvalid(Trainer, TrashBytesMismatchInitial));
}
}
}
public static class TrashByteRules3
{
@ -192,9 +196,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,8 +221,7 @@ 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);

View File

@ -1,5 +1,6 @@
using System;
using static PKHeX.Core.LegalityCheckResultCode;
using static PKHeX.Core.MoveHealState;
namespace PKHeX.Core;
@ -66,68 +67,124 @@ 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 bool IsFreshZATransferFromHOME_400(PA9 pk, EntityContext context)
{
if (data.IsStoredSlot(StorageSlotType.Party))
if (context != EntityContext.Gen9a)
return true;
var scale = pk.Scale;
if (pk.HeightScalar == scale && pk.WeightScalar == scale)
return true;
return false;
}
private static MoveHealState GetPermittedStatePP(LegalityAnalysis data, PKM pk)
{
if (Legal.IsPPUnused(pk))
{
if (pk is PA9 pa9 && IsFreshZATransferFromHOME_400(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 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.
}

View File

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

View File

@ -147,20 +147,23 @@ public static bool IsMarkValidAlpha(PKM pk, bool wasAlpha)
return true;
if (!wasAlpha)
return !m.RibbonMarkAlpha; // Shouldn't have the flag.
if (!HasEnteredHOME300(pk))
if (!HasEnteredHOME_Alpha(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)
private static bool HasEnteredHOME_Alpha(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.
// 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;
}
@ -169,7 +172,7 @@ private static bool HasEnteredHOME300(PKM pk)
/// </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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,40 @@ public override bool IsMatchExact(PKM pk, EvoCriteria evo)
return pk.PID == GetPID(pk, type);
}
private bool IsMatchSize(PKM pk, IScaledSize s)
{
if (CardID == 1513 && s is { HeightScalar: 255, WeightScalar: 255 } and IHomeTrack { HasTracker: true })
{
// 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).
if (pk is not IScaledSize3 { Scale: not 255 }) // if Scale is also present, should be 255.
return true;
// Otherwise, fall through and check via usual logic, which will return false if it doesn't match 128.
}
// 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];

View File

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

View File

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

View File

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

View File

@ -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,9 @@ 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;
PlusFlagsC.CopyTo(pk.PlusFlags0);
PlusFlagsB.CopyTo(pk.PlusFlags1);
pk.ObedienceLevel = Obedience_Level;
pk.Ability = Ability;
pk.AbilityNumber = AbilityNumber;
@ -197,10 +124,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;
@ -244,11 +170,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)
{

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -11,5 +11,6 @@ public enum HomeGameDataFormat : byte
PA8 = 3,
PB8 = 4,
PK9 = 5,
PA9 = 6,
PC9 = 6,
PA9 = 7,
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,8 @@ public abstract class G4PKM : PKM, IHandlerUpdate,
{
protected G4PKM(Memory<byte> data) : base(data) { }
protected G4PKM([ConstantExpected] int size) : base(size) { }
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt45(stored);
protected override void EncryptParty(Span<byte> party) => PokeCrypto.CryptArray(party, EncryptionConstant);
// Maximums
public sealed override ushort MaxMoveID => Legal.MaxMoveID_4;

View File

@ -10,6 +10,8 @@ public abstract class G6PKM : PKM, ISanityChecksum, IHandlerUpdate
public override int SIZE_STORED => PokeCrypto.SIZE_6STORED;
protected G6PKM(Memory<byte> data) : base(data) { }
protected G6PKM([ConstantExpected] int size) : base(size) { }
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt67(stored);
protected override void EncryptParty(Span<byte> party) => PokeCrypto.CryptArray(party, EncryptionConstant);
// Trash Bytes
public sealed override Span<byte> NicknameTrash => Data.Slice(0x40, 26);
@ -48,11 +50,6 @@ public byte OppositeFriendship
public override int Characteristic => EntityCharacteristic.GetCharacteristicInit0(EncryptionConstant, IV32);
// Methods
protected sealed override byte[] Encrypt()
{
RefreshChecksum();
return PokeCrypto.EncryptArray6(Data);
}
// General User-error Fixes
public void FixRelearn()

View File

@ -11,10 +11,12 @@ public abstract class G8PKM : PKM, ISanityChecksum,
{
protected G8PKM() : base(PokeCrypto.SIZE_8PARTY) { }
protected G8PKM(Memory<byte> data) : base(DecryptParty(data)) { }
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt8(stored);
protected override void EncryptParty(Span<byte> party) => PokeCrypto.CryptArray(party, EncryptionConstant);
private static Memory<byte> DecryptParty(Memory<byte> data)
{
PokeCrypto.DecryptIfEncrypted8(ref data);
PokeCrypto.DecryptIfEncrypted8(data.Span);
if (data.Length >= PokeCrypto.SIZE_8PARTY)
return data;
@ -62,11 +64,6 @@ public override byte CurrentFriendship
public override int Characteristic => EntityCharacteristic.GetCharacteristicInit0(EncryptionConstant, IV32);
// Methods
protected override byte[] Encrypt()
{
RefreshChecksum();
return PokeCrypto.EncryptArray8(Data);
}
public void FixRelearn()
{

View File

@ -23,10 +23,8 @@ public abstract class GBPKM : PKM
protected GBPKM([ConstantExpected] int size) : base(size) { }
protected GBPKM(Memory<byte> data) : base(data) { }
public sealed override byte[] EncryptedPartyData => Encrypt();
public sealed override byte[] EncryptedBoxData => Encrypt();
public sealed override byte[] DecryptedBoxData => Encrypt();
public sealed override byte[] DecryptedPartyData => Encrypt();
protected override void EncryptStored(Span<byte> stored) { }
protected override void EncryptParty(Span<byte> party) { }
public override bool Valid { get => true; set { } }
public sealed override void RefreshChecksum() { }

View File

@ -91,4 +91,20 @@ private void SetStringKeepTerminatorStyle(ReadOnlySpan<char> value, Span<byte> e
var option = zeroed ? StringConverterOption.ClearZero : StringConverterOption.Clear50;
SetString(exist, value, value.Length, option);
}
public override bool EqualsStored(PKM pk)
{
var storedSize = Format == 1 ? PokeCrypto.SIZE_1STORED : PokeCrypto.SIZE_2STORED;
var self = Data[..storedSize];
var other = pk.Data[..storedSize];
if (!self.SequenceEqual(other))
return false;
// Compare string buffers as well, since they are stored separately in Gen 1 & 2 formats.
if (!NicknameTrash.SequenceEqual(pk.NicknameTrash))
return false;
if (!OriginalTrainerTrash.SequenceEqual(pk.OriginalTrainerTrash))
return false;
return true;
}
}

View File

@ -59,6 +59,8 @@ public static int CountPresent(ReadOnlySpan<byte> input, int capacity, int lerp
private static bool IsJapaneseList(int length) => length == PokeCrypto.SIZE_1JLIST;
private static bool IsJapaneseString(int length) => length == GBPKML.StringLengthJapanese;
public static int GetListLength(int capacity, int sizeBody, int stringLength) => 1 + (capacity + 1) + (sizeBody * capacity) + (stringLength * capacity * 2);
public static int GetListLength(int capacity, bool jp, bool party) => 1 + (capacity + 1) + (GetBodyLength(party) * capacity) + (GetStringLength(jp) * capacity * 2);
public static int GetListLengthSingle(bool jp) => jp ? PokeCrypto.SIZE_1JLIST : PokeCrypto.SIZE_1ULIST;
private static int GetBodyLength(bool party) => party ? PokeCrypto.SIZE_1PARTY : PokeCrypto.SIZE_1STORED;
private static int GetStringLength(bool jp) => jp ? GBPKML.StringLengthJapanese : GBPKML.StringLengthNotJapan;
@ -111,6 +113,9 @@ public static PK1 ReadFromSingle(ReadOnlySpan<byte> input)
/// <param name="index">Entity index to read</param>
public static PK1 ReadFromList(ReadOnlySpan<byte> input, int stringLength, int capacity = 1, bool isParty = true, int index = 0)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)capacity - 1, IsJapaneseString(stringLength) ? 30u : 20u);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)capacity);
var start = 1 + (capacity + 1);
int sizeBody = GetBodyLength(isParty);
@ -133,12 +138,15 @@ public static PK1 ReadFromList(ReadOnlySpan<byte> input, int stringLength, int c
/// <param name="capacity">Count of slots allowed in the list</param>
/// <param name="isParty">List stores party stats for each entity</param>
/// <param name="index">Entity index to write</param>
public static void WriteToList(Span<byte> output, PK1 pk, int capacity = 1, bool isParty = true, int index = 0)
public static int WriteToList(Span<byte> output, PK1 pk, int capacity = 1, bool isParty = true, int index = 0)
{
var start = 1 + (capacity + 1);
var sizeBody = GetBodyLength(isParty);
var stringLength = pk.OriginalTrainerTrash.Length;
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)capacity - 1, IsJapaneseString(stringLength) ? 30u : 20u);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)capacity);
var ofsBody = start + (sizeBody * index);
var ofsStr1 = start + (sizeBody * capacity) + (stringLength * index);
var ofsStr2 = ofsStr1 + (capacity * stringLength);
@ -154,6 +162,9 @@ public static void WriteToList(Span<byte> output, PK1 pk, int capacity = 1, bool
output[1 + index] = GetHeaderIdentifierMark(pk);
output[0] = (byte)CountPresent(output, capacity);
output[1 + capacity] = SlotEmpty; // cap off the list
// indicate the byte length of the list for the given parameters
return GetListLength(capacity, sizeBody, stringLength);
}
/// <summary>
@ -166,6 +177,8 @@ public static void WriteToList(Span<byte> output, PK1 pk, int capacity = 1, bool
/// <param name="isParty">List stores party stats for each entity</param>
public static void Unpack(ReadOnlySpan<byte> input, Span<byte> output, int stringLength, int capacity, bool isParty)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)capacity - 1, IsJapaneseString(stringLength) ? 30u : 20u);
var lengthBody = GetBodyLength(isParty);
var lengthParty = GetBodyLength(true);
@ -208,6 +221,8 @@ public static void Unpack(ReadOnlySpan<byte> input, Span<byte> output, int strin
/// <param name="isDestInitialized">True if the destination list is initialized</param>
public static bool MergeSingles(ReadOnlySpan<byte> input, Span<byte> output, int stringLength, int capacity, bool isParty, bool isDestInitialized = true)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)capacity - 1, IsJapaneseString(stringLength) ? 30u : 20u);
// Collect the count of set slots
var jp = IsJapaneseString(stringLength);
var size = GetListLengthSingle(jp);
@ -270,7 +285,7 @@ public static byte[] WrapSingle(PK1 pk)
/// </summary>
/// <param name="pk">Entity to wrap</param>
/// <param name="output">Destination to write the single-slot list</param>
public static void WrapSingle(PK1 pk, Span<byte> output) => WriteToList(output, pk);
public static int WrapSingle(PK1 pk, Span<byte> output) => WriteToList(output, pk);
public static void UnpackNOB(ReadOnlySpan<byte> input, Span<byte> output, int stringLength, bool isParty = false)
{

View File

@ -63,6 +63,8 @@ private static int CountPresent(ReadOnlySpan<byte> input, int capacity, int lerp
private static bool IsJapaneseList(int length) => length == PokeCrypto.SIZE_2JLIST;
private static bool IsJapaneseString(int length) => length == GBPKML.StringLengthJapanese;
public static int GetListLength(int capacity, int sizeBody, int stringLength) => 1 + (capacity + 1) + (sizeBody * capacity) + (stringLength * capacity * 2);
public static int GetListLength(int capacity, bool jp, bool party) => 1 + (capacity + 1) + (GetBodyLength(party) * capacity) + (GetStringLength(jp) * capacity * 2);
public static int GetListLengthSingle(bool jp) => jp ? PokeCrypto.SIZE_2JLIST : PokeCrypto.SIZE_2ULIST;
private static int GetBodyLength(bool party) => party ? PokeCrypto.SIZE_2PARTY : PokeCrypto.SIZE_2STORED;
private static int GetStringLength(bool jp) => jp ? GBPKML.StringLengthJapanese : GBPKML.StringLengthNotJapan;
@ -115,6 +117,9 @@ public static PK2 ReadFromSingle(ReadOnlySpan<byte> input)
/// <param name="index">Entity index to read</param>
public static PK2 ReadFromList(ReadOnlySpan<byte> input, int stringLength, int capacity = 1, bool isParty = true, int index = 0)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)capacity - 1, IsJapaneseString(stringLength) ? 30u : 20u);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)capacity);
var start = 1 + (capacity + 1);
var sizeBody = GetBodyLength(isParty);
@ -137,12 +142,15 @@ public static PK2 ReadFromList(ReadOnlySpan<byte> input, int stringLength, int c
/// <param name="capacity">Count of slots allowed in the list</param>
/// <param name="isParty">List stores party stats for each entity</param>
/// <param name="index">Entity index to write</param>
public static void WriteToList(Span<byte> output, PK2 pk, int capacity = 1, bool isParty = true, int index = 0)
public static int WriteToList(Span<byte> output, PK2 pk, int capacity = 1, bool isParty = true, int index = 0)
{
var start = 1 + (capacity + 1);
var sizeBody = GetBodyLength(isParty);
var stringLength = pk.OriginalTrainerTrash.Length;
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)capacity - 1, IsJapaneseString(stringLength) ? 30u : 20u);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)capacity);
var ofsBody = start + (sizeBody * index);
var ofsStr1 = start + (sizeBody * capacity) + (stringLength * index);
var ofsStr2 = ofsStr1 + (capacity * stringLength);
@ -158,6 +166,9 @@ public static void WriteToList(Span<byte> output, PK2 pk, int capacity = 1, bool
output[1 + index] = GetHeaderIdentifierMark(pk);
output[0] = (byte)CountPresent(output, capacity);
output[1 + capacity] = SlotEmpty; // cap off the list
// indicate the byte length of the list for the given parameters
return GetListLength(capacity, sizeBody, stringLength);
}
/// <summary>
@ -170,6 +181,8 @@ public static void WriteToList(Span<byte> output, PK2 pk, int capacity = 1, bool
/// <param name="isParty">List stores party stats for each entity</param>
public static void Unpack(ReadOnlySpan<byte> input, Span<byte> output, int stringLength, int capacity, bool isParty)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)capacity - 1, IsJapaneseString(stringLength) ? 30u : 20u);
var lengthBody = GetBodyLength(isParty);
var lengthParty = GetBodyLength(true);
@ -211,6 +224,8 @@ public static void Unpack(ReadOnlySpan<byte> input, Span<byte> output, int strin
/// <param name="isParty">List stores party stats for each entity</param>
public static bool MergeSingles(ReadOnlySpan<byte> input, Span<byte> output, int stringLength, int capacity, bool isParty)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)capacity - 1, IsJapaneseString(stringLength) ? 30u : 20u);
// Collect the count of set slots
var jp = IsJapaneseString(stringLength);
var size = GetListLengthSingle(jp);
@ -273,7 +288,7 @@ public static byte[] WrapSingle(PK2 pk)
/// </summary>
/// <param name="pk">Entity to wrap</param>
/// <param name="output">Destination to write the single-slot list</param>
public static void WrapSingle(PK2 pk, Span<byte> output) => WriteToList(output, pk);
public static int WrapSingle(PK2 pk, Span<byte> output) => WriteToList(output, pk);
public static void UnpackNOB(ReadOnlySpan<byte> input, Span<byte> output, int stringLength, bool isParty = false)
{

View File

@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using static PKHeX.Core.EntityConverterResult;
using static PKHeX.Core.GameVersion;
@ -85,6 +86,15 @@ public static bool IsConvertibleToFormat(PKM pk, byte format)
return pk;
}
if (pk is PKH pkh)
{
if (TryConvertFromHOME(pkh, destType, out var x))
{
result = Success;
return x;
}
}
var entity = ConvertPKM(pk, destType, fromType, out result);
if (entity is not null)
{
@ -169,6 +179,15 @@ public static bool IsConvertibleToFormat(PKM pk, byte format)
_ => GetFinalResult(pk, destType, ref result),
};
private static bool TryConvertFromHOME(PKH pkh, Type destType, [NotNullWhen(true)] out PKM? result)
{
result = null;
var type = PKH.GetType(destType);
if (type is not HomeGameDataFormat.None)
result = pkh.ConvertToPKM(type);
return result != null;
}
private static PKM? GetFinalResult(PKM pk, Type destType, ref EntityConverterResult result)
{
// Every format can eventually feed into HOME. Don't bother checking current type.

View File

@ -40,11 +40,16 @@ private static void ResetOutboundSWSH(PKM result, PK8 pk8)
RejuvenateSV(result, pk8);
}
/// <summary>
/// Updates properties then infers legality for original encounter data to restore properties that are not transferred across games.
/// </summary>
private static void ResetSideways(PKM pk)
{
if (pk is PA8 pa8)
{
// Won't work well for Alphas
SanitizeFutureLanguage(pk);
// Legality detection won't work well for Alphas unless we infer this first.
if (pa8.RibbonMarkAlpha)
pa8.IsAlpha = true;
var la = new LegalityAnalysis(pa8);
@ -53,25 +58,52 @@ private static void ResetSideways(PKM pk)
if (pa8.LA)
ResetBallPLA(pa8, enc);
}
else if (pk is PB8 { BDSP: true })
else if (pk is PB8 bdsp)
{
SanitizeFutureLanguage(pk);
if (bdsp.BDSP)
ResetRelearn(pk, new LegalityAnalysis(pk));
}
else if (pk is PK9 { SV: true } pk9)
else if (pk is PK9 pk9)
{
SanitizeFutureLanguage(pk);
if (pk9.SV)
{
var la = new LegalityAnalysis(pk);
ResetRelearn(pk, la);
// Try to restore original Tera type / override instead of HOME's double override to current Type1.
TeraTypeUtil.ResetTeraType(pk9, la.EncounterMatch);
}
else if (pk is PA9 { ZA: true })
{
var la = new LegalityAnalysis(pk);
ResetRelearn(pk, la);
}
else
{
// User-friendly sanity check (not official):
// Fix original Tera type to current Type1, same as HOME, only if it's an illegal state.
// This only comes into play when the HOME data was present, but wasn't valid.
var pi = pk9.PersonalInfo;
var expect = TeraTypeUtil.GetTeraTypeImport(pi.Type1, pi.Type2);
if (pk9.TeraTypeOriginal != expect)
pk9.TeraTypeOriginal = expect;
if (!TeraTypeUtil.IsOverrideValid((byte)pk9.TeraTypeOverride))
pk9.TeraTypeOverride = expect;
}
}
else if (pk is PA9 pa9)
{
SanitizeFutureLanguage(pk);
// Legality detection won't work well for Alphas unless we infer this first.
if (pa9.RibbonMarkAlpha)
pa9.IsAlpha = true;
// Game doesn't use relearn moves, but Plus Flags are needed.
pa9.SetPlusFlags(pa9.PersonalInfo, PlusRecordApplicatorOption.LegalCurrent);
}
else if (pk is PK8 pk8 && !LocationsHOME.IsLocationSWSH(pk8.MetLocation))
{
SanitizeFutureLanguage(pk);
// Gen8 and below (Gen6/7) need their original relearn moves
// We can always set a Battle Version for non Gen8 origins, but most users won't be making stuff battle ready after.
// Battle Version is always zero in this case, so be nice and give the original relearn moves.
@ -79,6 +111,18 @@ private static void ResetSideways(PKM pk)
}
}
/// <summary>
/// Clamps the language ID back to those available in Gen8.
/// </summary>
/// <param name="pk">Entity to sanitize</param>
private static void SanitizeFutureLanguage(PKM pk)
{
// LATAM Spanish was added in Legends: Z-A.
// No functional difference in Species names, so we're safe to simply reassign to Castilian Spanish.
if (pk.Language == (int)LanguageID.SpanishL)
pk.Language = (int)LanguageID.Spanish;
}
private static void ResetRelearn(PKM pk, LegalityAnalysis la)
{
// Set suggested relearn moves.

Some files were not shown because too many files have changed in this diff Show More