Exemplo n.º 1
0
 def activate_hash_server(self, auth_token):
     self._hash_engine = HashServer(auth_token)
Exemplo n.º 2
0
 def activate_hash_server(self, auth_token):
     self._hash_engine = HashServer(auth_token)
Exemplo n.º 3
0
class RpcApi:

    RPC_ID = 0
    START_TIME = 0

    def __init__(self, auth_provider, device_info):

        self.log = logging.getLogger(__name__)

        self._auth_provider = auth_provider

        # mystical unknown6 - resolved by PokemonGoDev
        self._hash_engine = None
        self.request_proto = None

        if RpcApi.START_TIME == 0:
            RpcApi.START_TIME = get_time(ms=True)

        # data fields for SignalAgglom
        self.session_hash = os.urandom(16)
        self.token2 = random.randint(1, 59)
        self.course = random.uniform(0, 360)

        self.device_info = device_info

    def activate_hash_server(self, auth_token):
        self._hash_engine = HashServer(auth_token)

    def get_rpc_id(self):
        if RpcApi.RPC_ID==0 :  #Startup
            RpcApi.RPC_ID=1
            if self.device_info is not None  and  \
               self.device_info.get('device_brand','Apple')!='Apple':
                rand=0x53B77E48
            else:
                rand=0x000041A7
        else:
            rand=random.randint(0,2**31)
        RpcApi.RPC_ID += 1
        cnt= RpcApi.RPC_ID
        reqid= ((rand| ((cnt&0xFFFFFFFF)>>31))<<32)|cnt
        self.log.debug("Incremented RPC Request ID: %s", reqid)

        return reqid

    def decode_raw(self, raw):
        output = error = None
        try:
            process = subprocess.Popen(['protoc', '--decode_raw'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
            output, error = process.communicate(raw)
        except (subprocess.SubprocessError, OSError):
            output = "Couldn't find protoc in your environment OR other issue..."

        return output

    def get_class(self, cls):
        module_, class_ = cls.rsplit('.', 1)
        class_ = getattr(import_module(module_), to_camel_case(class_))
        return class_

    def _make_rpc(self, endpoint, request_proto_plain):
        self.log.debug('Execution of RPC')

        request_proto_serialized = request_proto_plain.SerializeToString()
        try:
            http_response = self._session.post(endpoint, data=request_proto_serialized, timeout=30)
        except requests.exceptions.Timeout:
            raise NianticTimeoutException('RPC request timed out.')
        except requests.exceptions.ConnectionError as e:
            raise NianticOfflineException(e)

        return http_response

    def request(self, endpoint, subrequests, platforms, player_position, use_dict = True):

        if not self._auth_provider or self._auth_provider.is_login() is False:
            raise NotLoggedInException()

        self.request_proto = self.request_proto or self._build_main_request(subrequests, platforms, player_position)
        response = self._make_rpc(endpoint, self.request_proto)

        response_dict = self._parse_main_response(response, subrequests, use_dict)

        # some response validations
        if isinstance(response_dict, dict):
            if use_dict:
                status_code = response_dict.get('status_code')
                if ('auth_ticket' in response_dict) and ('expire_timestamp_ms' in response_dict['auth_ticket']):
                    ticket = response_dict['auth_ticket']
                    self.check_authentication(ticket['expire_timestamp_ms'], ticket['start'], ticket['end'])
            else:
                status_code = response_dict['envelope'].status_code
                ticket = response_dict['envelope'].auth_ticket
                if ticket:
                    self.check_authentication(ticket.expire_timestamp_ms, ticket.start, ticket.end)
                                
            if status_code == 102:
                raise AuthTokenExpiredException
            elif status_code == 52:
                raise NianticThrottlingException("Request throttled by server... slow down man")
            elif status_code == 53:
                api_url = response_dict.get('api_url')
                if api_url:
                    exception = ServerApiEndpointRedirectException()
                    exception.set_redirected_endpoint(api_url)
                    raise exception
                else:
                    raise UnexpectedResponseException

        return response_dict

    def check_authentication(self, expire_timestamp_ms, start, end):
        if self._auth_provider.is_new_ticket(expire_timestamp_ms):

            had_ticket = self._auth_provider.has_ticket()
            self._auth_provider.set_ticket([expire_timestamp_ms, start, end])

            now_ms = get_time(ms=True)
            h, m, s = get_format_time_diff(now_ms, expire_timestamp_ms, True)

            if had_ticket:
                self.log.debug('Replacing old Session Ticket with new one valid for %02d:%02d:%02d hours (%s < %s)', h, m, s, now_ms, expire_timestamp_ms)
            else:
                self.log.debug('Received Session Ticket valid for %02d:%02d:%02d hours (%s < %s)', h, m, s, now_ms, expire_timestamp_ms)

    def _build_main_request(self, subrequests, platforms, player_position=None):
        self.log.debug('Generating main RPC request...')

        request = RequestEnvelope()
        request.status_code = 2
        request.request_id = self.get_rpc_id()
        request.accuracy = random.choice((5, 5, 5, 5, 10, 10, 10, 30, 30, 50, 65, random.uniform(66,80)))

        if player_position:
            request.latitude, request.longitude, altitude = player_position

        # generate sub requests before Signature generation
        request = self._build_sub_requests(request, subrequests)
        request = self._build_platform_requests(request, platforms)

        ticket = self._auth_provider.get_ticket()
        if ticket:
            self.log.debug('Found Session Ticket - using this instead of oauth token')
            request.auth_ticket.expire_timestamp_ms, request.auth_ticket.start, request.auth_ticket.end = ticket
            ticket_serialized = request.auth_ticket.SerializeToString()

        else:
            self.log.debug('No Session Ticket found - using OAUTH Access Token')
            request.auth_info.provider = self._auth_provider.get_name()
            request.auth_info.token.contents = self._auth_provider.get_access_token()
            request.auth_info.token.unknown2 = self.token2
            ticket_serialized = request.auth_info.SerializeToString()  #Sig uses this when no auth_ticket available

        sig = Signature()

        sig.session_hash = self.session_hash
        sig.timestamp = get_time(ms=True)
        sig.timestamp_since_start = get_time(ms=True) - RpcApi.START_TIME
        if sig.timestamp_since_start < 5000:
            sig.timestamp_since_start = random.randint(5000, 8000)

        self._hash_engine.hash(sig.timestamp, request.latitude, request.longitude, request.accuracy, ticket_serialized, sig.session_hash, request.requests)
        sig.location_hash1 = self._hash_engine.get_location_auth_hash()
        sig.location_hash2 = self._hash_engine.get_location_hash()
        for req_hash in self._hash_engine.get_request_hashes():
            sig.request_hash.append(ctypes.c_uint64(req_hash).value)

        loc = sig.location_fix.add()
        sen = sig.sensor_info.add()

        sen.timestamp_snapshot = random.randint(sig.timestamp_since_start - 5000, sig.timestamp_since_start - 100)
        loc.timestamp_snapshot = random.randint(sig.timestamp_since_start - 5000, sig.timestamp_since_start - 1000)

        loc.provider = random.choice(('network', 'network', 'network', 'network', 'fused'))
        loc.latitude = request.latitude
        loc.longitude = request.longitude

        loc.altitude = altitude or random.triangular(300, 400, 350)

        if random.random() > .95:
            # no reading for roughly 1 in 20 updates
            loc.course = -1
            loc.speed = -1
        else:
            self.course = random.triangular(0, 360, self.course)
            loc.course = self.course
            loc.speed = random.triangular(0.2, 4.25, 1)

        loc.provider_status = 3
        loc.location_type = 1
        if request.accuracy >= 65:
            loc.vertical_accuracy = random.triangular(35, 100, 65)
            loc.horizontal_accuracy = random.choice((request.accuracy, 65, 65, random.uniform(66,80), 200))
        else:
            if request.accuracy > 10:
                loc.vertical_accuracy = random.choice((24, 32, 48, 48, 64, 64, 96, 128))
            else:
                loc.vertical_accuracy = random.choice((3, 4, 6, 6, 8, 12, 24))
            loc.horizontal_accuracy = request.accuracy

        sen.linear_acceleration_x = random.triangular(-3, 1, 0)
        sen.linear_acceleration_y = random.triangular(-2, 3, 0)
        sen.linear_acceleration_z = random.triangular(-4, 2, 0)
        sen.magnetic_field_x = random.triangular(-50, 50, 0)
        sen.magnetic_field_y = random.triangular(-60, 50, -5)
        sen.magnetic_field_z = random.triangular(-60, 40, -30)
        sen.magnetic_field_accuracy = random.choice((-1, 1, 1, 2, 2, 2, 2))
        sen.attitude_pitch = random.triangular(-1.5, 1.5, 0.2)
        sen.attitude_yaw = random.uniform(-3, 3)
        sen.attitude_roll = random.triangular(-2.8, 2.5, 0.25)
        sen.rotation_rate_x = random.triangular(-6, 4, 0)
        sen.rotation_rate_y = random.triangular(-5.5, 5, 0)
        sen.rotation_rate_z = random.triangular(-5, 3, 0)
        sen.gravity_x = random.triangular(-1, 1, 0.15)
        sen.gravity_y = random.triangular(-1, 1, -.2)
        sen.gravity_z = random.triangular(-1, .7, -0.8)
        sen.status = 3

        sig.unknown25 = 4500779412463383546

        if self.device_info:
            for key in self.device_info:
                setattr(sig.device_info, key, self.device_info[key])
            if self.device_info['device_brand'] == 'Apple':
                sig.activity_status.stationary = True
        else:
            sig.activity_status.stationary = True

        signature_proto = sig.SerializeToString()

        if self._needsPtr8(subrequests):
            plat_eight = UnknownPtr8Request()
            plat_eight.message = '15c79df0558009a4242518d2ab65de2a59e09499'
            plat8 = request.platform_requests.add()
            plat8.type = 8
            plat8.request_message = plat_eight.SerializeToString()
        
        sig_request = SendEncryptedSignatureRequest()
        sig_request.encrypted_signature = pycrypt(signature_proto, sig.timestamp_since_start)
        plat = request.platform_requests.add()
        plat.type = 6
        plat.request_message = sig_request.SerializeToString()

        request.ms_since_last_locationfix = int(random.triangular(300, 30000, 10000))

        self.log.debug('Generated protobuf request: \n\r%s', request)

        return request

    def _needsPtr8(self, requests):
        if len(requests) == 0:
            return False
        randval = random.uniform(0, 1)
        rtype, _ = requests[0]
        # GetMapObjects or GetPlayer: 50%
        # Encounter: 10%
        # Others: 3%        
        if ((rtype in (2, 106) and randval > 0.5)
                or (rtype == 102 and randval > 0.9)
                or randval > 0.97):
            return True
        return False
        
    def _build_sub_requests(self, mainrequest, subrequest_list):
        self.log.debug('Generating sub RPC requests...')

        for entry_id, params in subrequest_list:
            if params:
                entry_name = RequestType.Name(entry_id)
                proto_name = entry_name.lower() + '_message'
                bytes = self._get_proto_bytes('pogoprotos.networking.requests.messages.', proto_name, params)

                subrequest = mainrequest.requests.add()
                subrequest.request_type = entry_id
                subrequest.request_message = bytes

            else:
                subrequest = mainrequest.requests.add()
                subrequest.request_type = entry_id

        return mainrequest

    def _build_platform_requests(self, mainrequest, platform_list):
        self.log.debug('Generating platform RPC requests...')

        for entry_id, params in platform_list:
            if params:
                entry_name = PlatformRequestType.Name(entry_id)
                if entry_name == 'UNKNOWN_PTR_8':
                    entry_name = 'UNKNOWN_PTR8'
                proto_name = entry_name.lower() + '_request'
                bytes = self._get_proto_bytes('pogoprotos.networking.platform.requests.', proto_name, params)

                platform = mainrequest.platform_requests.add()
                platform.type = entry_id
                platform.request_message = bytes

            else:
                platform = mainrequest.platform_requests.add()
                platform.type = entry_id

        return mainrequest
        

    def _get_proto_bytes(self, path, name, entry_content):
        proto_classname = path + name + '_pb2.' + name
        proto = self.get_class(proto_classname)()

        self.log.debug("Subrequest class: %s", proto_classname)

        for key, value in entry_content.items():
            if isinstance(value, list):
                self.log.debug("Found list: %s - trying as repeated", key)
                for i in value:
                    try:
                        self.log.debug("%s -> %s", key, i)
                        r = getattr(proto, key)
                        r.append(i)
                    except Exception as e:
                        self.log.warning('Argument %s with value %s unknown inside %s (Exception: %s)', key, i, proto_name, e)
            elif isinstance(value, dict):
                for k in value.keys():
                    try:
                        r = getattr(proto, key)
                        setattr(r, k, value[k])
                    except Exception as e:
                        self.log.warning('Argument %s with value %s unknown inside %s (Exception: %s)', key, str(value), proto_name, e)
            else:
                try:
                    setattr(proto, key, value)
                except Exception as e:
                    try:
                        self.log.debug("%s -> %s", key, value)
                        r = getattr(proto, key)
                        r.append(value)
                    except Exception as e:
                        self.log.warning('Argument %s with value %s unknown inside %s (Exception: %s)', key, value, proto_name, e)

        return proto.SerializeToString()

    def _parse_main_response(self, response_raw, subrequests, use_dict = True):
        self.log.debug('Parsing main RPC response...')

        if response_raw.status_code == 400:
            raise BadRequestException("400: Bad Request")
        if response_raw.status_code == 403:
            raise NianticIPBannedException("Seems your IP Address is banned or something else went badly wrong...")
        elif response_raw.status_code in (502, 503, 504):
            raise NianticOfflineException('{} Server Error'.format(response_raw.status_code))
        elif response_raw.status_code != 200:
            error = 'Unexpected HTTP server response - needs 200 got {}'.format(response_raw.status_code)
            self.log.warning(error)
            self.log.debug('HTTP output: \n%s', response_raw.content.decode('utf-8'))
            raise UnexpectedResponseException(error)

        if not response_raw.content:
            self.log.warning('Empty server response!')
            raise MalformedNianticResponseException('Empty server response!')

        response_proto = ResponseEnvelope()
        try:
            response_proto.ParseFromString(response_raw.content)
        except message.DecodeError as e:
            self.log.error('Could not parse response: %s', e)
            raise MalformedNianticResponseException('Could not decode response.')

        self.log.debug('Protobuf structure of rpc response:\n\r%s', response_proto)
        try:
            self.log.debug('Decode raw over protoc (protoc has to be in your PATH):\n\r%s', self.decode_raw(response_raw.content).decode('utf-8'))
        except Exception:
            self.log.debug('Error during protoc parsing - ignored.')

        if use_dict:
            response_proto_dict = protobuf_to_dict(response_proto)
            if 'returns' in response_proto_dict:
                del response_proto_dict['returns']
        else:
            response_proto_dict = {'envelope': response_proto}

        if not response_proto_dict:
            raise MalformedNianticResponseException('Could not convert protobuf to dict.')
            
        response_proto_dict = self._parse_sub_responses(response_proto, subrequests, response_proto_dict, use_dict)
        
        #It can't be done before
        if not use_dict:
            del response_proto_dict['envelope'].returns[:]

        return response_proto_dict

    def _parse_sub_responses(self, response_proto, subrequests_list, response_proto_dict, use_dict = True):
        self.log.debug('Parsing sub RPC responses...')
        response_proto_dict['responses'] = {}

        if response_proto.status_code == 53:
            exception = ServerApiEndpointRedirectException()
            exception.set_redirected_endpoint(response_proto.api_url)
            raise exception

        i = 0
        for subresponse in response_proto.returns:
            entry_id, _ = subrequests_list[i]
            entry_name = RequestType.Name(entry_id)
            proto_name = entry_name.lower() + '_response'
            proto_classname = 'pogoprotos.networking.responses.' + proto_name + '_pb2.' + proto_name

            self.log.debug("Parsing class: %s", proto_classname)

            subresponse_return = None
            try:
                subresponse_extension = self.get_class(proto_classname)()
            except Exception as e:
                subresponse_extension = None
                error = 'Protobuf definition for {} not found'.format(proto_classname)
                subresponse_return = error
                self.log.warning(error)

            if subresponse_extension:
                try:
                    subresponse_extension.ParseFromString(subresponse)
                    if use_dict:

                        subresponse_return = protobuf_to_dict(subresponse_extension)
                    else:
                        subresponse_return = subresponse_extension
                except Exception:
                    error = "Protobuf definition for {} seems not to match".format(proto_classname)
                    subresponse_return = error
                    self.log.warning(error)

            response_proto_dict['responses'][entry_name] = subresponse_return
            i += 1

        return response_proto_dict
Exemplo n.º 4
0
class RpcApi:

    RPC_ID = 0
    START_TIME = 0

    def __init__(self, auth_provider, device_info):

        self.log = logging.getLogger(__name__)

        self._auth_provider = auth_provider

        # mystical unknown6 - resolved by PokemonGoDev
        self._hash_engine = None
        self.request_proto = None

        if RpcApi.START_TIME == 0:
            RpcApi.START_TIME = get_time(ms=True)

        # data fields for SignalAgglom
        self.session_hash = os.urandom(16)
        self.token2 = random.randint(1, 59)
        self.course = random.uniform(0, 360)

        self.device_info = device_info

    def activate_hash_server(self, auth_token):
        self._hash_engine = HashServer(auth_token)

    def get_rpc_id(self):
        if RpcApi.RPC_ID==0 :  #Startup
            RpcApi.RPC_ID=1
            if self.device_info is not None  and  \
               self.device_info.get('device_brand','Apple')!='Apple':
                rand=0x53B77E48
            else:
                rand=0x000041A7
        else:
            rand=random.randint(0,2**31)
        RpcApi.RPC_ID += 1
        cnt= RpcApi.RPC_ID
        reqid= ((rand| ((cnt&0xFFFFFFFF)>>31))<<32)|cnt
        self.log.debug("Incremented RPC Request ID: %s", reqid)

        return reqid

    def decode_raw(self, raw):
        output = error = None
        try:
            process = subprocess.Popen(['protoc', '--decode_raw'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
            output, error = process.communicate(raw)
        except (subprocess.SubprocessError, OSError):
            output = "Couldn't find protoc in your environment OR other issue..."

        return output

    def get_class(self, cls):
        module_, class_ = cls.rsplit('.', 1)
        class_ = getattr(import_module(module_), to_camel_case(class_))
        return class_

    def _make_rpc(self, endpoint, request_proto_plain):
        self.log.debug('Execution of RPC')

        request_proto_serialized = request_proto_plain.SerializeToString()
        try:
            http_response = self._session.post(endpoint, data=request_proto_serialized, timeout=30)
        except requests.exceptions.Timeout:
            raise NianticTimeoutException('RPC request timed out.')
        except requests.exceptions.ConnectionError as e:
            raise NianticOfflineException(e)

        return http_response

    def request(self, endpoint, subrequests, platforms, player_position, use_dict = True):

        if not self._auth_provider or self._auth_provider.is_login() is False:
            raise NotLoggedInException()

        self.request_proto = self.request_proto or self._build_main_request(subrequests, platforms, player_position)
        response = self._make_rpc(endpoint, self.request_proto)

        response_dict = self._parse_main_response(response, subrequests, use_dict)

        # some response validations
        if isinstance(response_dict, dict):
            if use_dict:
                status_code = response_dict.get('status_code')
                if ('auth_ticket' in response_dict) and ('expire_timestamp_ms' in response_dict['auth_ticket']):
                    ticket = response_dict['auth_ticket']
                    self.check_authentication(ticket['expire_timestamp_ms'], ticket['start'], ticket['end'])
            else:
                status_code = response_dict['envelope'].status_code
                ticket = response_dict['envelope'].auth_ticket
                if ticket:
                    self.check_authentication(ticket.expire_timestamp_ms, ticket.start, ticket.end)
                                
            if status_code == 102:
                raise AuthTokenExpiredException
            elif status_code == 52:
                raise NianticThrottlingException("Request throttled by server... slow down man")
            elif status_code == 53:
                api_url = response_dict.get('api_url')
                if api_url:
                    exception = ServerApiEndpointRedirectException()
                    exception.set_redirected_endpoint(api_url)
                    raise exception
                else:
                    raise UnexpectedResponseException

        return response_dict

    def check_authentication(self, expire_timestamp_ms, start, end):
        if self._auth_provider.is_new_ticket(expire_timestamp_ms):

            had_ticket = self._auth_provider.has_ticket()
            self._auth_provider.set_ticket([expire_timestamp_ms, start, end])

            now_ms = get_time(ms=True)
            h, m, s = get_format_time_diff(now_ms, expire_timestamp_ms, True)

            if had_ticket:
                self.log.debug('Replacing old Session Ticket with new one valid for %02d:%02d:%02d hours (%s < %s)', h, m, s, now_ms, expire_timestamp_ms)
            else:
                self.log.debug('Received Session Ticket valid for %02d:%02d:%02d hours (%s < %s)', h, m, s, now_ms, expire_timestamp_ms)

    def _build_main_request(self, subrequests, platforms, player_position=None):
        self.log.debug('Generating main RPC request...')

        request = RequestEnvelope()
        request.status_code = 2
        request.request_id = self.get_rpc_id()
        request.accuracy = random.choice((5, 5, 5, 5, 10, 10, 10, 30, 30, 50, 65, random.uniform(66,80)))

        if player_position:
            request.latitude, request.longitude, altitude = player_position

        # generate sub requests before Signature generation
        request = self._build_sub_requests(request, subrequests)
        request = self._build_platform_requests(request, platforms)

        ticket = self._auth_provider.get_ticket()
        if ticket:
            self.log.debug('Found Session Ticket - using this instead of oauth token')
            request.auth_ticket.expire_timestamp_ms, request.auth_ticket.start, request.auth_ticket.end = ticket
            ticket_serialized = request.auth_ticket.SerializeToString()

        else:
            self.log.debug('No Session Ticket found - using OAUTH Access Token')
            request.auth_info.provider = self._auth_provider.get_name()
            request.auth_info.token.contents = self._auth_provider.get_access_token()
            request.auth_info.token.unknown2 = self.token2
            ticket_serialized = request.auth_info.SerializeToString()  #Sig uses this when no auth_ticket available

        sig = Signature()

        sig.session_hash = self.session_hash
        sig.timestamp = get_time(ms=True)
        sig.timestamp_since_start = get_time(ms=True) - RpcApi.START_TIME
        if sig.timestamp_since_start < 5000:
            sig.timestamp_since_start = random.randint(5000, 8000)

        self._hash_engine.hash(sig.timestamp, request.latitude, request.longitude, request.accuracy, ticket_serialized, sig.session_hash, request.requests)
        sig.location_hash1 = self._hash_engine.get_location_auth_hash()
        sig.location_hash2 = self._hash_engine.get_location_hash()
        for req_hash in self._hash_engine.get_request_hashes():
            sig.request_hash.append(ctypes.c_uint64(req_hash).value)

        loc = sig.location_fix.add()
        sen = sig.sensor_info.add()

        sen.timestamp_snapshot = random.randint(sig.timestamp_since_start - 5000, sig.timestamp_since_start - 100)
        loc.timestamp_snapshot = random.randint(sig.timestamp_since_start - 5000, sig.timestamp_since_start - 1000)

        loc.provider = random.choice(('network', 'network', 'network', 'network', 'fused'))
        loc.latitude = request.latitude
        loc.longitude = request.longitude

        loc.altitude = altitude or random.triangular(300, 400, 350)

        if random.random() > .95:
            # no reading for roughly 1 in 20 updates
            loc.course = -1
            loc.speed = -1
        else:
            self.course = random.triangular(0, 360, self.course)
            loc.course = self.course
            loc.speed = random.triangular(0.2, 4.25, 1)

        loc.provider_status = 3
        loc.location_type = 1
        if request.accuracy >= 65:
            loc.vertical_accuracy = random.triangular(35, 100, 65)
            loc.horizontal_accuracy = random.choice((request.accuracy, 65, 65, random.uniform(66,80), 200))
        else:
            if request.accuracy > 10:
                loc.vertical_accuracy = random.choice((24, 32, 48, 48, 64, 64, 96, 128))
            else:
                loc.vertical_accuracy = random.choice((3, 4, 6, 6, 8, 12, 24))
            loc.horizontal_accuracy = request.accuracy

        sen.linear_acceleration_x = random.triangular(-3, 1, 0)
        sen.linear_acceleration_y = random.triangular(-2, 3, 0)
        sen.linear_acceleration_z = random.triangular(-4, 2, 0)
        sen.magnetic_field_x = random.triangular(-50, 50, 0)
        sen.magnetic_field_y = random.triangular(-60, 50, -5)
        sen.magnetic_field_z = random.triangular(-60, 40, -30)
        sen.magnetic_field_accuracy = random.choice((-1, 1, 1, 2, 2, 2, 2))
        sen.attitude_pitch = random.triangular(-1.5, 1.5, 0.2)
        sen.attitude_yaw = random.uniform(-3, 3)
        sen.attitude_roll = random.triangular(-2.8, 2.5, 0.25)
        sen.rotation_rate_x = random.triangular(-6, 4, 0)
        sen.rotation_rate_y = random.triangular(-5.5, 5, 0)
        sen.rotation_rate_z = random.triangular(-5, 3, 0)
        sen.gravity_x = random.triangular(-1, 1, 0.15)
        sen.gravity_y = random.triangular(-1, 1, -.2)
        sen.gravity_z = random.triangular(-1, .7, -0.8)
        sen.status = 3

        sig.unknown25 = -782790124105039914

        if self.device_info:
            for key in self.device_info:
                setattr(sig.device_info, key, self.device_info[key])
            if self.device_info['device_brand'] == 'Apple':
                sig.activity_status.stationary = True
        else:
            sig.activity_status.stationary = True

        signature_proto = sig.SerializeToString()

        if self._needsPtr8(subrequests):
            plat_eight = UnknownPtr8Request()
            plat_eight.message = '15c79df0558009a4242518d2ab65de2a59e09499'
            plat8 = request.platform_requests.add()
            plat8.type = 8
            plat8.request_message = plat_eight.SerializeToString()
        
        sig_request = SendEncryptedSignatureRequest()
        sig_request.encrypted_signature = pycrypt(signature_proto, sig.timestamp_since_start)
        plat = request.platform_requests.add()
        plat.type = 6
        plat.request_message = sig_request.SerializeToString()

        request.ms_since_last_locationfix = int(random.triangular(300, 30000, 10000))

        self.log.debug('Generated protobuf request: \n\r%s', request)

        return request

    def _needsPtr8(self, requests):
        if len(requests) == 0:
            return False
        randval = random.uniform(0, 1)
        rtype, _ = requests[0]
        # GetMapObjects or GetPlayer: 50%
        # Encounter: 10%
        # Others: 3%        
        if ((rtype in (2, 106) and randval > 0.5)
                or (rtype == 102 and randval > 0.9)
                or randval > 0.97):
            return True
        return False
        
    def _build_sub_requests(self, mainrequest, subrequest_list):
        self.log.debug('Generating sub RPC requests...')

        for entry_id, params in subrequest_list:
            if params:
                entry_name = RequestType.Name(entry_id)
                proto_name = entry_name.lower() + '_message'
                bytes = self._get_proto_bytes('pogoprotos.networking.requests.messages.', proto_name, params)

                subrequest = mainrequest.requests.add()
                subrequest.request_type = entry_id
                subrequest.request_message = bytes

            else:
                subrequest = mainrequest.requests.add()
                subrequest.request_type = entry_id

        return mainrequest

    def _build_platform_requests(self, mainrequest, platform_list):
        self.log.debug('Generating platform RPC requests...')

        for entry_id, params in platform_list:
            if params:
                entry_name = PlatformRequestType.Name(entry_id)
                if entry_name == 'UNKNOWN_PTR_8':
                    entry_name = 'UNKNOWN_PTR8'
                proto_name = entry_name.lower() + '_request'
                bytes = self._get_proto_bytes('pogoprotos.networking.platform.requests.', proto_name, params)

                platform = mainrequest.platform_requests.add()
                platform.type = entry_id
                platform.request_message = bytes

            else:
                platform = mainrequest.platform_requests.add()
                platform.type = entry_id

        return mainrequest
        

    def _get_proto_bytes(self, path, name, entry_content):
        proto_classname = path + name + '_pb2.' + name
        proto = self.get_class(proto_classname)()

        self.log.debug("Subrequest class: %s", proto_classname)

        for key, value in entry_content.items():
            if isinstance(value, list):
                self.log.debug("Found list: %s - trying as repeated", key)
                for i in value:
                    try:
                        self.log.debug("%s -> %s", key, i)
                        r = getattr(proto, key)
                        r.append(i)
                    except Exception as e:
                        self.log.warning('Argument %s with value %s unknown inside %s (Exception: %s)', key, i, proto_name, e)
            elif isinstance(value, dict):
                for k in value.keys():
                    try:
                        r = getattr(proto, key)
                        setattr(r, k, value[k])
                    except Exception as e:
                        self.log.warning('Argument %s with value %s unknown inside %s (Exception: %s)', key, str(value), proto_name, e)
            else:
                try:
                    setattr(proto, key, value)
                except Exception as e:
                    try:
                        self.log.debug("%s -> %s", key, value)
                        r = getattr(proto, key)
                        r.append(value)
                    except Exception as e:
                        self.log.warning('Argument %s with value %s unknown inside %s (Exception: %s)', key, value, proto_name, e)

        return proto.SerializeToString()

    def _parse_main_response(self, response_raw, subrequests, use_dict = True):
        self.log.debug('Parsing main RPC response...')

        if response_raw.status_code == 400:
            raise BadRequestException("400: Bad Request")
        if response_raw.status_code == 403:
            raise NianticIPBannedException("Seems your IP Address is banned or something else went badly wrong...")
        elif response_raw.status_code in (502, 503, 504):
            raise NianticOfflineException('{} Server Error'.format(response_raw.status_code))
        elif response_raw.status_code != 200:
            error = 'Unexpected HTTP server response - needs 200 got {}'.format(response_raw.status_code)
            self.log.warning(error)
            self.log.debug('HTTP output: \n%s', response_raw.content.decode('utf-8'))
            raise UnexpectedResponseException(error)

        if not response_raw.content:
            self.log.warning('Empty server response!')
            raise MalformedNianticResponseException('Empty server response!')

        response_proto = ResponseEnvelope()
        try:
            response_proto.ParseFromString(response_raw.content)
        except message.DecodeError as e:
            self.log.error('Could not parse response: %s', e)
            raise MalformedNianticResponseException('Could not decode response.')

        self.log.debug('Protobuf structure of rpc response:\n\r%s', response_proto)
        try:
            self.log.debug('Decode raw over protoc (protoc has to be in your PATH):\n\r%s', self.decode_raw(response_raw.content).decode('utf-8'))
        except Exception:
            self.log.debug('Error during protoc parsing - ignored.')

        if use_dict:
            response_proto_dict = protobuf_to_dict(response_proto)
            if 'returns' in response_proto_dict:
                del response_proto_dict['returns']
        else:
            response_proto_dict = {'envelope': response_proto}

        if not response_proto_dict:
            raise MalformedNianticResponseException('Could not convert protobuf to dict.')
            
        response_proto_dict = self._parse_sub_responses(response_proto, subrequests, response_proto_dict, use_dict)
        
        #It can't be done before
        if not use_dict:
            del response_proto_dict['envelope'].returns[:]

        return response_proto_dict

    def _parse_sub_responses(self, response_proto, subrequests_list, response_proto_dict, use_dict = True):
        self.log.debug('Parsing sub RPC responses...')
        response_proto_dict['responses'] = {}

        if response_proto.status_code == 53:
            exception = ServerApiEndpointRedirectException()
            exception.set_redirected_endpoint(response_proto.api_url)
            raise exception

        i = 0
        for subresponse in response_proto.returns:
            entry_id, _ = subrequests_list[i]
            entry_name = RequestType.Name(entry_id)
            proto_name = entry_name.lower() + '_response'
            proto_classname = 'pogoprotos.networking.responses.' + proto_name + '_pb2.' + proto_name

            self.log.debug("Parsing class: %s", proto_classname)

            subresponse_return = None
            try:
                subresponse_extension = self.get_class(proto_classname)()
            except Exception as e:
                subresponse_extension = None
                error = 'Protobuf definition for {} not found'.format(proto_classname)
                subresponse_return = error
                self.log.warning(error)

            if subresponse_extension:
                try:
                    subresponse_extension.ParseFromString(subresponse)
                    if use_dict:

                        subresponse_return = protobuf_to_dict(subresponse_extension)
                    else:
                        subresponse_return = subresponse_extension
                except Exception:
                    error = "Protobuf definition for {} seems not to match".format(proto_classname)
                    subresponse_return = error
                    self.log.warning(error)

            response_proto_dict['responses'][entry_name] = subresponse_return
            i += 1

        return response_proto_dict