JapariBypass/dmmBypass.py

226 lines
9.8 KiB
Python

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)