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
12 from pokedex
.util
import permutations
14 def pokemon_prng(seed
):
15 u
"""Creates a generator that simulates the main Pokémon PRNG."""
17 seed
= 0x41C64E6D * seed
+ 0x6073
22 class PokemonSave(object):
23 u
"""Represents an individual Pokémon, from the game's point of view.
25 Handles translating between the on-disk encrypted form, the in-RAM blob
26 (also used by pokesav), and something vaguely intelligible.
28 XXX: Okay, well, right now it's just encryption and decryption. But, you
32 def __init__(self
, blob
, encrypted
=False):
33 u
"""Wraps a Pokémon save struct in a friendly object.
35 If `encrypted` is True, the blob will be decrypted as though it were an
36 on-disk save. Otherwise, the blob is taken to be already decrypted and
40 # XXX Sometime this should have an abstract internal representation.
41 # For now, just store the decrypted version
45 # Interpret as one word (pid), followed by a bunch of shorts
46 struct_def
= "I" + "H" * ((len(blob
) - 4) / 2)
47 shuffled
= list( struct
.unpack(struct_def
, blob
) )
49 # Apply standard Pokémon decryption, undo the block shuffling, and
51 self
.reciprocal_crypt(shuffled
)
52 words
= self
.shuffle_chunks(shuffled
, reverse
=True)
53 self
.blob
= struct
.pack(struct_def
, *words
)
62 u
"""Returns a decrypted struct, aka .pkm file."""
66 def as_encrypted(self
):
67 u
"""Returns an encrypted struct the game expects in a save file."""
69 # Interpret as one word (pid), followed by a bunch of shorts
70 struct_def
= "I" + "H" * ((len(self
.blob
) - 4) / 2)
71 words
= list( struct
.unpack(struct_def
, self
.blob
) )
73 # Apply the block shuffle and standard Pokémon encryption
74 shuffled
= self
.shuffle_chunks(words
)
75 self
.reciprocal_crypt(shuffled
)
77 # Stuff back into a string, and done
78 return struct
.pack(struct_def
, *shuffled
)
83 shuffle_orders
= list( permutations(range(4)) )
86 def shuffle_chunks(cls
, words
, reverse
=False):
87 """The main 128 encrypted bytes (or 64 words) in a save block are split
88 into four chunks and shuffled around in some order, based on
89 personality. The actual order of shuffling is a permutation of four
90 items in order, indexed by the shuffle index. That is, 0 yields 0123,
91 1 yields 0132, 2 yields 0213, etc.
93 Given a list of words (the first of which should be the pid), this
94 function returns the words in shuffled order. Pass reverse=True to
99 shuffle_index
= (pid
>> 0xD & 0x1F) %
24
101 shuffle_order
= cls
.shuffle_orders
[shuffle_index
]
103 # Decoding requires going the other way; invert the order
104 shuffle_order
= [shuffle_order
.index(i
) for i
in range(4)]
106 shuffled
= words
[:3] # skip the unencrypted stuff
107 for chunk
in shuffle_order
:
108 shuffled
+= words
[ chunk
* 16 + 3 : chunk
* 16 + 19 ]
109 shuffled
+= words
[67:] # extra bytes are also left alone
114 def reciprocal_crypt(cls
, words
):
115 u
"""Applies the reciprocal Pokémon save file cipher to the provided
118 Returns nothing; the list is changed in-place.
120 # Apply regular Pokémon "encryption": xor everything with the output of
121 # the PRNG. First three items are pid/unused/checksum and are not
124 # Main data is encrypted using the checksum as a seed
125 prng
= pokemon_prng(words
[2])
126 for i
in range(3, 67):
127 words
[i
] ^
= next(prng
)
130 # Extra bytes are encrypted using the pid as a seed
131 prng
= pokemon_prng(words
[0])
132 for i
in range(67, len(words
)):
133 words
[i
] ^
= next(prng
)