import os import sys import json import hashlib import pathlib import argparse import requests import subprocess import urllib.parse from uuid import getnode from datetime import datetime, timedelta parser = argparse.ArgumentParser(description='DMM bypass script') parser.add_argument('-g', '--game', type=str, help="DMM code name of the game", default="kfp2g") parser.add_argument('-u', '--update', help="Check for game update before launching", action='store_true') args = parser.parse_args() def load_config(config_name): with open(config_name, "rt", encoding="utf-8") as f: return json.load(f) def save_config(config_name, config): with open(config_name, 'wt', encoding="utf-8") as f: json.dump(config, f, indent=1, ensure_ascii=False) config_name = args.game + '.cfg' if not os.path.exists(config_name): subprocess.check_call([sys.executable, "-m", "pip", "install", "requests", "beautifulsoup4", "PyPasser"]) config = { "game_id" : args.game, "file_path" : input('Enter full file path to KF3 .exe: ').strip('\"'), "dmm_login" : input('DMM Login (email): '), "dmm_password" : input('DMM Password: '), "use_proxy" : input('Your login tokens will be sent through my VPN machine.\nIs that okay? (yes/no): ').lower() == "yes" } save_config(config_name, config) else: config = load_config(config_name) print(f'Loaded settings from {config_name}') config["update_game"] = args.update import dmmUpdater from bs4 import BeautifulSoup from pypasser import reCaptchaV3 def get_hash(data): sha_obj = hashlib.sha256() sha_obj.update(str(data).encode()) hash_hex = sha_obj.hexdigest() return hash_hex def get_mac(): mac = getnode() return ':'.join(("%012X" % mac)[i:i+2] for i in range(0, 12, 2)).lower() def get_game_root_path(user_path, game_dir, exe_name): """Return valid game install directory""" path = pathlib.Path(user_path) parts = list(path.parts) if path.name == exe_name: return str(path.parent.resolve()) elif game_dir in parts: #get index of last occurence of game_dir in user path (+1) dir_idx = len(parts) - parts[::-1].index(game_dir) parts = parts[:dir_idx] return str(pathlib.Path(*parts).resolve()) else: raise Exception(f"Path to game files is incorrect! Ensure it points to {exe_name} or contains {game_dir}!") def retrieve_login_token(session : requests.Session): try: print("Retrieving login form") url = "https://accounts.dmm.com/service/login/password" result = session.get(url) result.raise_for_status() page = BeautifulSoup(result.content, 'html.parser') token = page.find('input', attrs={"name":"token"}).get("value") return token except Exception as e: print("Failed to retrieve login form:", e) def retrieve_captcha_token(): try: captcha = reCaptchaV3("https://www.google.com/recaptcha/enterprise/anchor?ar=1&k=6LfZLQEVAAAAAC-8pKwFNuzVoJW4tfUCghBX_7ZE&co=aHR0cHM6Ly9hY2NvdW50cy5kbW0uY29tOjQ0Mw..&hl=ja&v=1Bq_oiMBd4XPUhKDwr0YL1Js&size=invisible") return captcha except Exception as e: print("Failed to solve captcha:", e) def retrieve_auth_keys(login, password, token, captcha, session : requests.Session): try: print("Logging in") url = "https://accounts.dmm.com/service/login/password/authenticate" data = f"token={token}&login_id={login}&password={password}&prompt=&device=games-player&recaptchaToken={captcha}" headers = {"Content-Type": "application/x-www-form-urlencoded"} result = session.post(url, data, headers=headers) result.raise_for_status() cookies = result.cookies.get_dict() return cookies["login_secure_id"], cookies["login_session_id"] except Exception as e: print("Failed to log in:", e) return None, None def agree_to_game_terms(game_id, use_proxy): try: print("Accepting updated game terms of use") headers = {"User-Agent": "DMMGamePlayer5-Win/5.3.12 Electron/32.1.0", "Client-App": "DMMGamePlayer5", "Client-version": "5.3.12", "Content-Type": "application/json"} url = "" if use_proxy else "https://apidgp-gameplayer.games.dmm.com/v5/agreement/confirm/client" data = {"product_id":game_id,"is_notification":False,"is_myapp":False} result = requests.post(url, headers=headers, json=data) result.raise_for_status() result_json = result.json() if result_json["result_code"] != 100: raise Exception(f'{result_json["result_code"]}: {result_json["error"]}') return True except Exception as e: print("Failed to accept terms of use:", e) return False def retrieve_launch_params(game_id, mac_addr, hdd_serial, motherboard, login_secure, login_session, use_proxy, update_game): try: print("Retrieving launch arguments") data = {"product_id":game_id,"game_type":"GCL","game_os":"win","launch_type":"LIB","mac_address":mac_addr,"hdd_serial":hdd_serial,"motherboard":motherboard,"user_os":"win"} headers = {"User-Agent": "DMMGamePlayer5-Win/5.3.12 Electron/32.1.0", "Client-App": "DMMGamePlayer5", "Client-version": "5.3.12", "Content-Type": "application/json"} cookies = {"login_secure_id":login_secure, "login_session_id":login_session} if update_game: url = "https://katworks.sytes.net/KF/Api/DMM/update" if use_proxy else "https://apidgp-gameplayer.games.dmm.com/v5/r2/launch/cl" else: url = "https://katworks.sytes.net/KF/Api/DMM/launch" if use_proxy else "https://apidgp-gameplayer.games.dmm.com/v5/launch/cl" result = requests.post(url, cookies=cookies, headers=headers, json=data) result.raise_for_status() result_json = result.json() if result_json["result_code"] == 308: if agree_to_game_terms(game_id, use_proxy): result = requests.post(url, cookies=cookies, headers=headers, json=data) result.raise_for_status() result_json = result.json() if result_json["result_code"] != 100: raise Exception(f'{result_json["result_code"]}: {result_json["error"]}') else: raise Exception("Failed to agree to updated game terms of use. Use the DMM app to confirm.") elif result_json["result_code"] != 100: raise Exception(f'{result_json["result_code"]}: {result_json["error"]}') data = result_json["data"] return data except Exception as e: print("Failed to retrieve launch arguments:", e) def main(config): #required arguments game_id = config["game_id"] exe_location = config["file_path"] login = urllib.parse.quote_plus(config["dmm_login"]) password = urllib.parse.quote_plus(config["dmm_password"]) #optional arguments update_game = config.get("update_game", False) use_proxy = config.get("use_proxy", False) saved_login = config.get("saved_login", None) #dmm requires these values mac_addr = get_mac() hdd_serial = get_hash('') #DMM sends an empty hash as well motherboard = get_hash(getnode()) #actual moterboard serial is unknown, but this works current_time = datetime.now() with requests.Session() as session: if saved_login is not None: login_expiration = datetime.fromtimestamp(saved_login["expiration"]) if login_expiration < current_time: print("Login data has expired") saved_login = None if saved_login is not None: login_secure, login_session = saved_login["login_secure"], saved_login["login_session"] print("Loaded login data from previous session") else: token = retrieve_login_token(session) captcha = retrieve_captcha_token() if token == None or captcha == None: return login_secure, login_session = retrieve_auth_keys(login, password, token, captcha, session) if login_secure == None or login_session == None: return login_expiration = int((current_time + timedelta(days=364)).timestamp()) #expire in less than a year config["saved_login"] = saved_login = {"login_secure" : login_secure, "login_session" : login_session, "expiration": login_expiration} del(config["update_game"]) save_config(config_name, config) if not use_proxy: input("Enable VPN now and press Enter") #execute_args, file_list_url, file_access_params launch_data : dict = retrieve_launch_params(game_id, mac_addr, hdd_serial, motherboard, login_secure, login_session, use_proxy, update_game) execute_args : str = launch_data.get("execute_args", None) if execute_args == None: return if update_game: file_list_url = launch_data.get("file_list_url", None) file_list_params : str = launch_data.get("sign", None) if file_list_url == None or file_list_params == None: return print("Latest version:", launch_data["latest_version"]) file_list_params = "?" + file_list_params.replace(";", "&").replace("CloudFront-", "") exec_name = launch_data["exec_file_name"] install_dir = launch_data["install_dir"] game_root_path = get_game_root_path(exe_location, install_dir, exec_name) if not use_proxy: input("Disable VPN now and press Enter") if update_game: dmmUpdater.update_game(game_root_path, file_list_url, file_list_params) print("Starting game") args = [os.path.join(game_root_path, exec_name)] + execute_args.split() print(args) subprocess.Popen(args, start_new_session=True) input("Done. Press enter to exit (this will close the game)") main(config)