A Little Hack on the game Warship Girls R

Thanks to a recent historical movie regarding the infamous World War II sea battle, Midway, I have picked up a game that I dropped right before the university started. Not only that, I also had a chance to reorganize some notes I wrote for the game and built a sub-site for those “long-forgotten” notes.

A quick introduction to those who never heard of Warship Girls R (WGR): it is a kancolle-like game with a series of local modifications for Chinese players. Although as much as I enjoy playing WGR, I do not recommend this type of game for my post readers. Simply because {my post readers} ∩ {target audience of WGR} = ∅ (I am 99% sure the equation works; prove me wrong if you find kancolle-like game interests you).

Now back to the main purpose of this post: why am I hacking this game? After I returned to this game, I found the game brought up a new and stupid system (kitchen system). The stupid numerical design of the system pushed me for an alternate solution.

I wrote a script.

The purpose of the script was to (1) login one of my alternate accounts (this can scale up to hundreds of accounts); (2) perform one task in the new system (order a menu in the kitchen).

The first thing was to figure out how does the game communicate between local clients and the server, so that I can mimic the behaviour of a real user. After some research, I found that the game uses straightforward HTTP/HTTPS protocol. I then found a post that uses Fiddler to monitor all connections between devices and the Web. Through some testing, I documented all critical URLs and Hosts for my script. What left are just coding!

Followings are some snippets of the script.

A normal mean of login:

    def login(self, username, pwd, server):
        url_login = self.hm_login_server + "1.0/get/login/@self"
        data = {
            "platform": "0",
            "appid": "0",
            "app_server_type": "0",
            "password": pwd,
            "username": username


        login_response = session.post(url=url_login, data=json.dumps(data).replace(" ", ""),
                                      headers=self.pastport_headers, timeout=10).text
        login_response = json.loads(login_response)

        if "error" in login_response and int(login_response["error"]) != 0:
            return False

        tokens = ""
        if "access_token" in login_response:
            tokens = login_response["access_token"]
        if "token" in login_response:
            tokens = login_response["token"]

        url_init = self.hm_login_server + "1.0/get/initConfig/@self"
        session.post(url=url_init, data="{}", headers=self.pastport_headers, timeout=10)

        # Validate token
        while True:
            url_info = self.hm_login_server + "1.0/get/userInfo/@self"

            login_data = json.dumps({"access_token": tokens})

            user_info = session.post(url=url_info, data=login_data, headers=self.pastport_headers, timeout=10).text
            user_info = json.loads(user_info)
            if "error" in user_info and user_info["error"] != 0:
                tokens = ""

        login_url = self.login_server + "index/hmLogin/" + tokens + self.get_url_end()
        login_response = session.get(url=login_url, headers=HEADER, timeout=10)
        login_text = json.loads(zlib.decompress(login_response.content))

        self.cookies = login_response.cookies.get_dict()
        self.uid = str(login_text['userId'])
        return login_text

Encrypting timestamps:

    def encryption(self, url, method):
        times = datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
        data = str(method) + "\n" + times + "\n" + "/" + url.split("/", 3)[-1]
        mac = hmac.new(self.key.encode(), data.encode(), hashlib.sha1)
        data = mac.digest()
        return base64.b64encode(data).decode("utf-8"), times

The task I wanted to perform (order a menu in the kitchen):

    def get_friend_list(self):
        url = self.server_list[0]["host"] + 'friend/getlist' + self.get_url_end()
        raw_data = self.decompress_data(url)
        data = json.loads(raw_data)
        return data["list"]
    def friend_feat(self, uid, cook_item):
        url = self.server_list[0]["host"] + 'live/feat/' + uid + '/' + cook_item + self.get_url_end()
        raw_data = self.decompress_data(url)
        data = json.loads(raw_data)
        return data

Ta da. That’s it. If you are interested for the full source code, check it here. Since I need to run the script daily (and I am too lazy to run it myself), a cronjob was set up as well.

Using the scripts, I estimated that I can achieve my desired goal in the game in just 3 months, instead of 5 years (told you, the numerical design is stupid)!

Thanks for reading ;)