1349c16bdecb73802b76fe34b5acc754b94eb92d
2 from __future__
import absolute_import
, division
4 from base64
import urlsafe_b64decode
5 from collections
import namedtuple
6 from itertools
import izip
8 from random
import sample
9 from string
import uppercase
, lowercase
, digits
13 import pokedex
.db
.tables
as tables
14 import pokedex
.formulae
15 from pokedex
.struct
import SaveFilePokemon
16 from pylons
import config
, request
, response
, session
, tmpl_context
as c
, url
17 from pylons
.controllers
.util
import abort
, redirect_to
18 from sqlalchemy
import and_
, or_
, not_
19 from sqlalchemy
.orm
import aliased
, contains_eager
, eagerload
, eagerload_all
, join
20 from sqlalchemy
.orm
.exc
import NoResultFound
21 from sqlalchemy
.sql
import func
22 from sqlalchemy
.exc
import IntegrityError
24 from spline
.model
import meta
25 from spline
.lib
.base
import BaseController
, render
26 from spline
.lib
import helpers
as h
27 from splinext
.gts
import model
as gts_model
29 log
= logging
.getLogger(__name__
)
35 """Implements the GTS's linear congruential generator.
37 I love typing that out. It makes me sounds so smart.
39 Yields magical numbers."""
40 # 0xabcd => 0xabcdabcd
41 seed
= seed |
(seed
<< 16)
43 seed
= (seed
* 0x45 + 0x1111) & 0x7fffffff # signed dword!
44 yield (seed
>> 16) & 0xff
46 def stream_decipher(data
, keystream
):
47 """Reverses a stream cipher, given iterable data and keystream.
49 Yields decrypted bytes.
51 for c
, key
in izip(data
, keystream
):
52 new_c
= (ord(c
) ^ key
) & 0xff
56 def decrypt_data(data
):
57 """Takes a binary blob uploaded from a game and returns the original binary
58 blob. Depending on your perspective, the returned value may be more
61 # GTS encryption is a simple stream cipher.
62 # The first four bytes of the data are a header containing an obfuscated
63 # key; the rest is the message
64 obf_key_blob
, message
= data
[0:4], data
[4:]
65 obf_key
, = struct
.unpack('>I', obf_key_blob
)
66 key
= obf_key ^
0x4a3b2c1d
68 # Data is XORed with the output of an LCG, like everything else in Pokémon
69 return ''.join( stream_decipher(message
, gts_prng(key
)) )
78 class GTSController(BaseController
):
80 def dispatch(self
, page
):
81 """We do our own dispatching for two reasons:
83 1. All requests do challenge/response before anything, and it's easier
84 to do that and then dispatch than copy/paste some block of code to
87 2. Easier to dump stuff if desired.
92 # Always return binary!
93 response
.headers
['Content-type'] = 'application/octet-stream'
95 if 'hash' in request
.params
:
96 # Okay, already done the response. Decrypt, then dispatch!
97 if request
.params
['data']:
98 # Note: base64 doesn't like unicode. Go figure. It's binary
100 encrypted_data
= urlsafe_b64decode(str(request
.params
['data']))
101 data
= decrypt_data(encrypted_data
)
102 data
= data
[4:] # data always starts with pid; we don't care
106 method
= 'page_' + page
107 if not hasattr(self
, method
):
108 dbg("NOT YET IMPLEMENTED?")
111 res
= getattr(self
, method
)(request
.params
['pid'], data
)
112 dbg("RESPONDING WITH ", type(res
), len(res
), repr(res
))
115 # No hash. Need to issue a challenge. It's random, so whatever
116 return ''.join(sample( uppercase
+ lowercase
+ digits
, 32 ))
120 def page_info(self
, pid
, data
):
123 Apparently always returns 0x0001. Probably just a ping to see if the
129 def page_setProfile(self
, pid
, data
):
132 Only Pt and later check this page. Don't know why. It returns eight
138 def page_result(self
, pid
, data
):
143 This checks the game's status on the GTS. If there's nothing
144 interesting to report, it returns 0x0005. If the game has a Pokémon up
145 on the GTS, it returns 0x0004.
147 However... if the game has a Pokémon waiting to come to it, it returns
148 that entire Pokémon struct! No header, no encryption. Just a regular
152 # Check for an existing Pokémon
153 # TODO support multiple!
155 stored_pokemon
= meta
.Session
.query(gts_model
.GTSPokemon
) \
156 .filter_by(pid
=pid
) \
158 # We've got one! Cool, send it back. The game will ask us to
159 # delete it after receiving successfully
160 pokemon_save
= SaveFilePokemon(stored_pokemon
.pokemon_blob
)
161 return pokemon_save
.as_encrypted
167 def page_delete(self
, pid
, data
):
170 If a Pokémon is received from result.asp, the game requests this page
171 to confirm that the incoming Pokémon may be deleted.
176 meta
.Session
.query(gts_model
.GTSPokemon
).filter_by(pid
=pid
).delete()
177 meta
.Session
.commit()
181 def page_post(self
, pid
, data
):
184 Deposits a Pokémon in the GTS. Returns 0x0001 on success, or 0x000c if
185 the deposit is rejected.
189 # The uploaded Pokémon is encrypted, which is not very useful
190 pokemon_save
= SaveFilePokemon(data
, encrypted
=True)
193 stored_pokemon
= gts_model
.GTSPokemon(
195 pokemon_blob
=pokemon_save
.as_struct
,
197 meta
.Session
.add(stored_pokemon
)
198 meta
.Session
.commit()
200 except IntegrityError
:
201 # If that failed due to unique key collision, we're already storing
205 def page_post_finish(self
, pid
, data
):
208 Surely this does something, but for the life of me I can't figure out