3 Handles reading and encryption/decryption of Pokémon save file data.
5 See: http://projectpokemon.org/wiki/Pokemon_NDS_Structure
7 Kudos to LordLandon for his pkmlib.py, from which this module was originally
13 from pokedex
.util
import permutations
14 from pokedex
.struct
._pokemon_struct
import pokemon_struct
16 def pokemon_prng(seed
):
17 u
"""Creates a generator that simulates the main Pokémon PRNG."""
19 seed
= 0x41C64E6D * seed
+ 0x6073
24 class SaveFilePokemon(object):
25 u
"""Represents an individual Pokémon, from the game's point of view.
27 Handles translating between the on-disk encrypted form, the in-RAM blob
28 (also used by pokesav), and something vaguely intelligible.
31 def __init__(self
, blob
, encrypted
=False):
32 u
"""Wraps a Pokémon save struct in a friendly object.
34 If `encrypted` is True, the blob will be decrypted as though it were an
35 on-disk save. Otherwise, the blob is taken to be already decrypted and
41 # Interpret as one word (pid), followed by a bunch of shorts
42 struct_def
= "I" + "H" * ((len(blob
) - 4) / 2)
43 shuffled
= list( struct
.unpack(struct_def
, blob
) )
45 # Apply standard Pokémon decryption, undo the block shuffling, and
47 self
.reciprocal_crypt(shuffled
)
48 words
= self
.shuffle_chunks(shuffled
, reverse
=True)
49 self
.blob
= struct
.pack(struct_def
, *words
)
55 self
.structure
= pokemon_struct
.parse(self
.blob
)
60 u
"""Returns a decrypted struct, aka .pkm file."""
64 def as_encrypted(self
):
65 u
"""Returns an encrypted struct the game expects in a save file."""
67 # Interpret as one word (pid), followed by a bunch of shorts
68 struct_def
= "I" + "H" * ((len(self
.blob
) - 4) / 2)
69 words
= list( struct
.unpack(struct_def
, self
.blob
) )
71 # Apply the block shuffle and standard Pokémon encryption
72 shuffled
= self
.shuffle_chunks(words
)
73 self
.reciprocal_crypt(shuffled
)
75 # Stuff back into a string, and done
76 return struct
.pack(struct_def
, *shuffled
)
83 u
"""Returns true iff this Pokémon is shiny."""
84 # See http://bulbapedia.bulbagarden.net/wiki/Personality#Shininess
85 # But don't see it too much, because the above is super over
86 # complicated. Do this instead!
87 personality_msdw
= self
.structure
.personality
>> 16
88 personality_lsdw
= self
.structure
.personality
& 0xffff
90 self
.structure
.original_trainer_id
91 ^ self
.structure
.original_trainer_secret_id
99 shuffle_orders
= list( permutations(range(4)) )
102 def shuffle_chunks(cls
, words
, reverse
=False):
103 """The main 128 encrypted bytes (or 64 words) in a save block are split
104 into four chunks and shuffled around in some order, based on
105 personality. The actual order of shuffling is a permutation of four
106 items in order, indexed by the shuffle index. That is, 0 yields 0123,
107 1 yields 0132, 2 yields 0213, etc.
109 Given a list of words (the first of which should be the pid), this
110 function returns the words in shuffled order. Pass reverse=True to
115 shuffle_index
= (pid
>> 0xD & 0x1F) %
24
117 shuffle_order
= cls
.shuffle_orders
[shuffle_index
]
119 # Decoding requires going the other way; invert the order
120 shuffle_order
= [shuffle_order
.index(i
) for i
in range(4)]
122 shuffled
= words
[:3] # skip the unencrypted stuff
123 for chunk
in shuffle_order
:
124 shuffled
+= words
[ chunk
* 16 + 3 : chunk
* 16 + 19 ]
125 shuffled
+= words
[67:] # extra bytes are also left alone
130 def reciprocal_crypt(cls
, words
):
131 u
"""Applies the reciprocal Pokémon save file cipher to the provided
134 Returns nothing; the list is changed in-place.
136 # Apply regular Pokémon "encryption": xor everything with the output of
137 # the PRNG. First three items are pid/unused/checksum and are not
140 # Main data is encrypted using the checksum as a seed
141 prng
= pokemon_prng(words
[2])
142 for i
in range(3, 67):
143 words
[i
] ^
= next(prng
)
146 # Extra bytes are encrypted using the pid as a seed
147 prng
= pokemon_prng(words
[0])
148 for i
in range(67, len(words
)):
149 words
[i
] ^
= next(prng
)