15b34575e7f41b9d7be9d72a58f6102aed14945e
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
23 from spline
.model
import meta
24 from spline
.lib
.base
import BaseController
, render
25 from spline
.lib
import helpers
as h
26 from splinext
.gts
import model
as gts_model
28 log
= logging
.getLogger(__name__
)
34 """Implements the GTS's linear congruential generator.
36 I love typing that out. It makes me sounds so smart.
38 Yields magical numbers."""
39 # 0xabcd => 0xabcdabcd
40 seed
= seed |
(seed
<< 16)
42 seed
= (seed
* 0x45 + 0x1111) & 0x7fffffff # signed dword!
43 yield (seed
>> 16) & 0xff
45 def stream_decipher(data
, keystream
):
46 """Reverses a stream cipher, given iterable data and keystream.
48 Yields decrypted bytes.
50 for c
, key
in izip(data
, keystream
):
51 new_c
= (ord(c
) ^ key
) & 0xff
55 def decrypt_data(data
):
56 """Takes a binary blob uploaded from a game and returns the original binary
57 blob. Depending on your perspective, the returned value may be more
60 # GTS encryption is a simple stream cipher.
61 # The first four bytes of the data are a header containing an obfuscated
62 # key; the rest is the message
63 obf_key_blob
, message
= data
[0:4], data
[4:]
64 obf_key
, = struct
.unpack('>I', obf_key_blob
)
65 key
= obf_key ^
0x4a3b2c1d
67 # Data is XORed with the output of an LCG, like everything else in Pokémon
68 return ''.join( stream_decipher(message
, gts_prng(key
)) )
77 class GTSController(BaseController
):
79 def dispatch(self
, page
):
80 """We do our own dispatching for two reasons:
82 1. All requests do challenge/response before anything, and it's easier
83 to do that and then dispatch than copy/paste some block of code to
86 2. Easier to dump stuff if desired.
91 # Always return binary!
92 response
.headers
['Content-type'] = 'application/octet-stream'
94 if 'hash' in request
.params
:
95 # Okay, already done the response. Decrypt, then dispatch!
96 if request
.params
['data']:
97 # Note: base64 doesn't like unicode. Go figure. It's binary
99 encrypted_data
= urlsafe_b64decode(str(request
.params
['data']))
100 data
= decrypt_data(encrypted_data
)
101 data
= data
[4:] # data always starts with pid; we don't care
105 method
= 'page_' + page
106 if not hasattr(self
, method
):
107 dbg("NOT YET IMPLEMENTED?")
110 res
= getattr(self
, method
)(request
.params
['pid'], data
)
111 dbg("RESPONDING WITH ", type(res
), len(res
), repr(res
))
114 # No hash. Need to issue a challenge. It's random, so whatever
115 return ''.join(sample( uppercase
+ lowercase
+ digits
, 32 ))
119 def page_info(self
, pid
, data
):
122 Apparently always returns 0x0001. Probably just a ping to see if the
128 def page_setProfile(self
, pid
, data
):
131 Only Pt and later check this page. Don't know why. It returns eight
137 def page_result(self
, pid
, data
):
142 This checks the game's status on the GTS. If there's nothing
143 interesting to report, it returns 0x0005. If the game has a Pokémon up
144 on the GTS, it returns 0x0004.
146 However... if the game has a Pokémon waiting to come to it, it returns
147 that entire Pokémon struct! No header, no encryption. Just a regular
151 # Check for an existing Pokémon
152 # TODO support multiple!
154 stored_pokemon
= meta
.Session
.query(gts_model
.GTSPokemon
) \
155 .filter_by(pid
=pid
) \
157 # We've got one! Cool, send it back. The game will ask us to
158 # delete it after receiving successfully
159 pokemon_save
= SaveFilePokemon(stored_pokemon
.pokemon_blob
)
160 return pokemon_save
.as_encrypted
166 def page_delete(self
, pid
, data
):
169 If a Pokémon is received from result.asp, the game requests this page
170 to confirm that the incoming Pokémon may be deleted.
175 meta
.Session
.query(gts_model
.GTSPokemon
).filter_by(pid
=pid
).delete()
176 meta
.Session
.commit()
180 def page_post(self
, pid
, data
):
183 Deposits a Pokémon in the GTS. Returns 0x0001 on success, or 0x000c if
184 the deposit is rejected.
188 # The uploaded Pokémon is encrypted, which is not very useful
189 pokemon_save
= SaveFilePokemon(data
, encrypted
=True)
192 stored_pokemon
= gts_model
.GTSPokemon(
194 pokemon_blob
=pokemon_save
.as_struct
,
196 meta
.Session
.add(stored_pokemon
)
197 meta
.Session
.commit()
200 # If that failed (presumably due to unique key collision), we're
201 # already storing something. Reject!
204 def page_post_finish(self
, pid
, data
):
207 Surely this does something, but for the life of me I can't figure out