commit 222c73f667a99a9ec6e159f965baf2e59be1b8d5 Author: katboi01 Date: Fri Jan 24 12:11:57 2025 +0100 init diff --git a/dmmBypass.py b/dmmBypass.py new file mode 100644 index 0000000..119e476 --- /dev/null +++ b/dmmBypass.py @@ -0,0 +1,145 @@ +import os +import sys +import wmi +import hashlib +import requests +import subprocess +import urllib.parse +import dmmUpdater +from uuid import getnode +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_motherboard(): + return wmi.WMI().Win32_BaseBoard()[0].SerialNumber + +def retrieve_login_token(session : requests.Session): + try: + print("Retrieving login form") + url = "https://accounts.dmm.com/service/login/password" + result = session.get(url) + 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) + return None + +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) + return None + +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) + 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 retrieve_update_params(game_id, login_secure, login_session, use_proxy): + try: + print("Retrieving update file list") + data = {"product_id":game_id,"game_type":"GCL","game_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} + url = "https://katworks.sytes.net/KF/Api/DMM/filelist" if use_proxy else "https://apidgp-gameplayer.games.dmm.com/v5/r2/filelist/cl" + result = requests.post(url, cookies=cookies, headers=headers, json=data) + + data = result.json()["data"] + game_version = data["latest_version"] + print("Latest version:", game_version) + file_list_url = data["file_list_url"] + file_list_params = data["sign"] + file_list_params = "?" + file_list_params.replace(";", "&").replace("CloudFront-", "") + return file_list_url, file_list_params + except Exception as e: + print("Failed to retrieve update file list:", e) + return + +def retrieve_launch_params(game_id, mac_addr, hdd_serial, motherboard, login_secure, login_session, use_proxy): + 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} + url = "https://katworks.sytes.net/KF/Api/DMM/launch" if use_proxy else "https://apidgp-gameplayer.games.dmm.com/v5/r2/launch/cl" + result = requests.post(url, cookies=cookies, headers=headers, json=data) + + data = result.json()["data"] + return data["execute_args"] + except Exception as e: + print("Failed to retrieve launch arguments:", e) + return None + +def main(args): + if len(args) != 7: + print("Usage:", + "\tpython dmmBypass.py game_id game_path email password update_game use_proxy", + "\t- game_id: DMM code name of the game", + "\t- game_path: full path to the game .exe. Wrap in \" if there are spaces in the path", + "\t- email, password: dmm credentials", + "\t- update_game: \"true\" to check for game update before launching", + "\t- use_proxy: \"true\" to send required request through Katboi VPN. Otherwise use your own VPN", + "\texample: python dmmBypass.py kfp2g \"D:\Games\KFP2G\けもフレ3.exe\" kat@email.com abc123 true", sep="\n") + return + + game_id = args[1] + exe_location = args[2] + login = urllib.parse.quote_plus(args[3]) + password = urllib.parse.quote_plus(args[4]) + update_game = args[5].lower() == "true" + use_proxy = args[6].lower() == "true" + mac_addr = get_mac() + hdd_serial = get_hash('') + motherboard = get_hash(get_motherboard()) + + with requests.Session() as session: + token = retrieve_login_token(session) + captcha = retrieve_captcha_token() + + if token == None or captcha == None: + return + + #auth keys are also saved as cookies in session + login_secure, login_session = retrieve_auth_keys(login, password, token, captcha, session) + + if not use_proxy: input("Enable VPN now and press Enter") + + if update_game: + file_list_url, file_access_params = retrieve_update_params(game_id, login_secure, login_session, use_proxy) + if file_list_url == None or file_access_params == None: + return + dmmUpdater.update_game(os.path.dirname(exe_location), file_list_url, file_access_params) + + execute_args = retrieve_launch_params(game_id, mac_addr, hdd_serial, motherboard, login_secure, login_session, use_proxy) + + if not use_proxy: input("Disable VPN now and press Enter") + + print("Starting game") + args = [exe_location] + execute_args.split() + print(args) + subprocess.Popen(args, start_new_session=True) + input("Done. Press enter to exit") + +args = sys.argv +main(args) \ No newline at end of file diff --git a/dmmUpdater.py b/dmmUpdater.py new file mode 100644 index 0000000..b30ca07 --- /dev/null +++ b/dmmUpdater.py @@ -0,0 +1,80 @@ +import os +import hashlib +import requests +import urllib.parse +from urllib.request import urlretrieve + +import sys +def progressbar(it, prefix="", size=60, out=sys.stdout): # Python3.3+ + """https://stackoverflow.com/questions/3160699/python-progress-bar""" + count = len(it) + if count == 0: return + def show(j): + x = int(size*j/count) + print("{}[{}{}] {}/{}".format(prefix, "#"*x, "."*(size-x), j, count), + end='\r', file=out, flush=True) + show(0) + for i, item in enumerate(it): + yield item + show(i+1) + print("\n", flush=True, file=out) + +def get_file_list(url): + url = "https://apidgp-gameplayer.games.dmm.com" + url + print("Retrieving file list from " + url) + data = requests.get(url).json()["data"] + return data["domain"], data["file_list"] + +def get_file_hash(file_path): + if not os.path.exists(file_path): + return None + + with open(file_path, "rb") as f: + file_hash = hashlib.md5() + while chunk := f.read(8192): + file_hash.update(chunk) + + return file_hash.hexdigest() + +def update_game(game_path, files_url, files_param): + server_url, server_files = get_file_list(files_url) + server_file_dict = {file["local_path"]: file for file in server_files} + + local_files = [os.path.join(dp, f).replace("\\", "/") for dp, dn, filenames in os.walk(game_path) for f in filenames] + local_file_dict = {"/" + os.path.relpath(r, game_path).replace("\\", "/"): {"abs_path":r, "hash":""} for r in local_files if not "BepInEx" in r} + + files_to_download = [] + files_to_delete = [] + for server_file_key in server_file_dict.keys(): + server_file = server_file_dict[server_file_key] + + if server_file_key in local_file_dict: + local_file = local_file_dict[server_file_key] + if server_file["force_delete_flg"]: + files_to_delete.append(local_file["abs_path"]) + else: + local_file["hash"] = get_file_hash(local_file["abs_path"]) + + if server_file["check_hash_flg"] and local_file["hash"] == server_file["hash"]: + continue + + download_url = urllib.parse.urljoin(server_url, server_file["path"]) + files_param + download_path = game_path.replace("\\", "/") + server_file_key + files_to_download.append({"url":download_url, "path":download_path}) + else: + download_url = urllib.parse.urljoin(server_url, server_file["path"]) + files_param + download_path = game_path.replace("\\", "/") + server_file_key + files_to_download.append({"url":download_url, "path":download_path}) + + print("Files to download:", len(files_to_download)) + + for file in progressbar(files_to_download, "Downloading: ", 40): + url, path = file["url"], file["path"] + os.makedirs(os.path.dirname(path), exist_ok=True) + urlretrieve(url, path) + + # #files_to_delete is unused until fully tested + # for file in files_to_delete: + # os.remove(file) + + return True \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2e72b53 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +beautifulsoup4 +PyPasser +wmi \ No newline at end of file diff --git a/run_KF3.bat b/run_KF3.bat new file mode 100644 index 0000000..7233642 --- /dev/null +++ b/run_KF3.bat @@ -0,0 +1,36 @@ +@echo off +chcp 65001 +setlocal enabledelayedexpansion + +set file_name="kfp2g.cfg" + +IF NOT EXIST %file_name% ( + set /p null="Make sure python is installed. In next step, required packages will be installed. Press Enter to continue" + pip install -r requirements.txt + set /p file_path=Enter full file path to KF3 .exe: + set /p dmm_login=DMM Login: + set /p dmm_password=DMM Password: + set /p confirm="Your login tokens will be sent through Katboi's VPN machine, is that ok? Personal VPN is required if not (yes/no):" + echo !confirm! + if /i "!confirm!"=="yes" ( + set use_proxy=true + ) else ( + set use_proxy=false + ) + echo !file_path!> %file_name% + echo !dmm_login!>> %file_name% + echo !dmm_password!>> %file_name% + echo !use_proxy!>> %file_name% +) ELSE ( + < %file_name% ( + set /p file_path= + set /p dmm_login= + set /p dmm_password= + set /p use_proxy= + ) + echo Loaded settings from %file_name% +) + +python dmmBypass.py kfp2g %file_path% %dmm_login% %dmm_password% false %use_proxy% + +pause \ No newline at end of file diff --git a/update_and_run_KF3.bat b/update_and_run_KF3.bat new file mode 100644 index 0000000..27ea157 --- /dev/null +++ b/update_and_run_KF3.bat @@ -0,0 +1,36 @@ +@echo off +chcp 65001 +setlocal enabledelayedexpansion + +set file_name="kfp2g.cfg" + +IF NOT EXIST %file_name% ( + set /p null="Make sure python is installed. In next step, required packages will be installed. Press Enter to continue" + pip install -r requirements.txt + set /p file_path=Enter full file path to KF3 .exe: + set /p dmm_login=DMM Login: + set /p dmm_password=DMM Password: + set /p confirm="Your login tokens will be sent through Katboi's VPN machine, is that ok? Personal VPN is required if not (yes/no):" + echo !confirm! + if /i "!confirm!"=="yes" ( + set use_proxy=true + ) else ( + set use_proxy=false + ) + echo !file_path!> %file_name% + echo !dmm_login!>> %file_name% + echo !dmm_password!>> %file_name% + echo !use_proxy!>> %file_name% +) ELSE ( + < %file_name% ( + set /p file_path= + set /p dmm_login= + set /p dmm_password= + set /p use_proxy= + ) + echo Loaded settings from %file_name% +) + +python dmmBypass.py kfp2g %file_path% %dmm_login% %dmm_password% true %use_proxy% + +pause \ No newline at end of file