2025-01-24 19:11:57 +08:00
|
|
|
import os
|
|
|
|
import sys
|
2025-01-25 07:55:47 +08:00
|
|
|
import json
|
2025-01-24 19:11:57 +08:00
|
|
|
import hashlib
|
2025-01-30 09:06:13 +08:00
|
|
|
import pathlib
|
2025-01-25 07:55:47 +08:00
|
|
|
import argparse
|
2025-01-24 19:11:57 +08:00
|
|
|
import requests
|
|
|
|
import subprocess
|
|
|
|
import urllib.parse
|
|
|
|
from uuid import getnode
|
2025-01-30 09:06:13 +08:00
|
|
|
from datetime import datetime, timedelta
|
2025-01-25 07:55:47 +08:00
|
|
|
|
|
|
|
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()
|
|
|
|
|
2025-01-30 09:06:13 +08:00
|
|
|
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)
|
|
|
|
|
2025-01-25 07:55:47 +08:00
|
|
|
config_name = args.game + '.cfg'
|
|
|
|
|
|
|
|
if not os.path.exists(config_name):
|
2025-01-25 08:23:27 +08:00
|
|
|
subprocess.check_call([sys.executable, "-m", "pip", "install", "requests", "beautifulsoup4", "PyPasser"])
|
2025-01-25 07:55:47 +08:00
|
|
|
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"
|
|
|
|
}
|
2025-01-30 09:06:13 +08:00
|
|
|
save_config(config_name, config)
|
2025-01-25 07:55:47 +08:00
|
|
|
else:
|
2025-01-30 09:06:13 +08:00
|
|
|
config = load_config(config_name)
|
2025-01-25 07:55:47 +08:00
|
|
|
print(f'Loaded settings from {config_name}')
|
|
|
|
|
|
|
|
config["update_game"] = args.update
|
|
|
|
|
|
|
|
import dmmUpdater
|
2025-01-24 19:11:57 +08:00
|
|
|
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()
|
|
|
|
|
2025-01-30 09:06:13 +08:00
|
|
|
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}!")
|
|
|
|
|
2025-01-24 19:11:57 +08:00
|
|
|
def retrieve_login_token(session : requests.Session):
|
|
|
|
try:
|
|
|
|
print("Retrieving login form")
|
|
|
|
url = "https://accounts.dmm.com/service/login/password"
|
|
|
|
result = session.get(url)
|
2025-01-24 20:31:49 +08:00
|
|
|
result.raise_for_status()
|
2025-01-24 19:11:57 +08:00
|
|
|
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)
|
2025-01-24 20:31:49 +08:00
|
|
|
result.raise_for_status()
|
2025-01-24 19:11:57 +08:00
|
|
|
cookies = result.cookies.get_dict()
|
|
|
|
return cookies["login_secure_id"], cookies["login_session_id"]
|
|
|
|
except Exception as e:
|
|
|
|
print("Failed to log in:", e)
|
2025-01-25 11:28:38 +08:00
|
|
|
return None, None
|
2025-01-30 09:06:13 +08:00
|
|
|
|
2025-01-27 21:04:42 +08:00
|
|
|
def agree_to_game_terms(game_id, use_proxy):
|
2025-01-24 19:11:57 +08:00
|
|
|
try:
|
2025-01-27 21:04:42 +08:00
|
|
|
print("Accepting updated game terms of use")
|
2025-01-24 19:11:57 +08:00
|
|
|
headers = {"User-Agent": "DMMGamePlayer5-Win/5.3.12 Electron/32.1.0", "Client-App": "DMMGamePlayer5", "Client-version": "5.3.12", "Content-Type": "application/json"}
|
2025-01-27 21:04:42 +08:00
|
|
|
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)
|
2025-01-24 20:31:49 +08:00
|
|
|
result.raise_for_status()
|
2025-01-25 11:28:38 +08:00
|
|
|
result_json = result.json()
|
|
|
|
if result_json["result_code"] != 100:
|
|
|
|
raise Exception(f'{result_json["result_code"]}: {result_json["error"]}')
|
2025-01-27 21:04:42 +08:00
|
|
|
return True
|
2025-01-24 19:11:57 +08:00
|
|
|
except Exception as e:
|
2025-01-27 21:04:42 +08:00
|
|
|
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):
|
2025-01-24 19:11:57 +08:00
|
|
|
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}
|
2025-01-27 21:04:42 +08:00
|
|
|
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"
|
2025-01-24 19:11:57 +08:00
|
|
|
result = requests.post(url, cookies=cookies, headers=headers, json=data)
|
2025-01-24 20:31:49 +08:00
|
|
|
result.raise_for_status()
|
2025-01-25 11:28:38 +08:00
|
|
|
result_json = result.json()
|
2025-01-27 21:04:42 +08:00
|
|
|
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:
|
2025-01-25 11:28:38 +08:00
|
|
|
raise Exception(f'{result_json["result_code"]}: {result_json["error"]}')
|
|
|
|
data = result_json["data"]
|
2025-01-30 09:06:13 +08:00
|
|
|
return data
|
2025-01-24 19:11:57 +08:00
|
|
|
except Exception as e:
|
|
|
|
print("Failed to retrieve launch arguments:", e)
|
|
|
|
|
2025-01-25 07:55:47 +08:00
|
|
|
def main(config):
|
2025-01-30 09:06:13 +08:00
|
|
|
#required arguments
|
|
|
|
game_id = config["game_id"]
|
2025-01-25 07:55:47 +08:00
|
|
|
exe_location = config["file_path"]
|
2025-01-30 09:06:13 +08:00
|
|
|
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
|
2025-01-24 19:11:57 +08:00
|
|
|
mac_addr = get_mac()
|
2025-01-30 09:06:13 +08:00
|
|
|
hdd_serial = get_hash('') #DMM sends an empty hash as well
|
|
|
|
motherboard = get_hash(getnode()) #actual moterboard serial is unknown, but this works
|
2025-01-24 19:11:57 +08:00
|
|
|
|
2025-01-30 09:06:13 +08:00
|
|
|
current_time = datetime.now()
|
2025-01-24 19:11:57 +08:00
|
|
|
|
2025-01-30 09:06:13 +08:00
|
|
|
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)
|
2025-01-24 19:11:57 +08:00
|
|
|
|
|
|
|
if not use_proxy: input("Enable VPN now and press Enter")
|
|
|
|
|
2025-01-30 09:06:13 +08:00
|
|
|
#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:
|
2025-01-24 20:31:49 +08:00
|
|
|
return
|
2025-01-30 09:06:13 +08:00
|
|
|
|
|
|
|
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)
|
2025-01-24 20:31:49 +08:00
|
|
|
|
2025-01-24 19:11:57 +08:00
|
|
|
if not use_proxy: input("Disable VPN now and press Enter")
|
|
|
|
|
2025-01-24 20:31:49 +08:00
|
|
|
if update_game:
|
2025-01-30 09:06:13 +08:00
|
|
|
dmmUpdater.update_game(game_root_path, file_list_url, file_list_params)
|
2025-01-24 20:31:49 +08:00
|
|
|
|
2025-01-24 19:11:57 +08:00
|
|
|
print("Starting game")
|
2025-01-30 09:06:13 +08:00
|
|
|
args = [os.path.join(game_root_path, exec_name)] + execute_args.split()
|
2025-01-24 19:11:57 +08:00
|
|
|
print(args)
|
|
|
|
subprocess.Popen(args, start_new_session=True)
|
2025-01-25 07:55:47 +08:00
|
|
|
input("Done. Press enter to exit (this will close the game)")
|
2025-01-24 19:11:57 +08:00
|
|
|
|
2025-01-25 07:55:47 +08:00
|
|
|
main(config)
|