You've already forked JapariBypass
							
							Compare commits
	
		
			5 Commits
		
	
	
		
			a54c07a8ad
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 51aabe8b53 | |||
| c1b7056838 | |||
| 8f2c1e081b | |||
| 92e46a56f5 | |||
| ed77fc6b44 | 
| @@ -2,9 +2,10 @@ | |||||||
| Efficient KF3/DMM game launcher. | Efficient KF3/DMM game launcher. | ||||||
|  |  | ||||||
| ## Requirements | ## Requirements | ||||||
| Python 3 and pip. | - Python 3 and pip. | ||||||
|  | - DMM account that owns KF3 | ||||||
|  |  | ||||||
| KF3 installed from DMM. DMM itself does not need to be running. | KF3 installed from DMM is recommended but not required. | ||||||
|  |  | ||||||
| ## Instructions | ## Instructions | ||||||
| Download the files from this repo. Unzip them to a safe place. | Download the files from this repo. Unzip them to a safe place. | ||||||
| @@ -18,6 +19,3 @@ Input your DMM email and then password when prompted, and `yes` if you want to u | |||||||
| If you make a mistake or you want to change login details, edit the `kfp2g.cfg` config file, or delete it to run the configuration again. | If you make a mistake or you want to change login details, edit the `kfp2g.cfg` config file, or delete it to run the configuration again. | ||||||
|  |  | ||||||
| If KF3 needs to update, use `update_and_run_KF3.bat` and it will install any new updates. | If KF3 needs to update, use `update_and_run_KF3.bat` and it will install any new updates. | ||||||
|  |  | ||||||
| ## Known issues |  | ||||||
| Retrieving launch arguments - error 308: Every once in a while, DMM games may have updated terms of service that need to be accepted. This can only be done via the official DMM app. |  | ||||||
							
								
								
									
										148
									
								
								dmmBypass.py
									
									
									
									
									
								
							
							
						
						
									
										148
									
								
								dmmBypass.py
									
									
									
									
									
								
							| @@ -4,46 +4,55 @@ import json | |||||||
| import hashlib | import hashlib | ||||||
| import pathlib | import pathlib | ||||||
| import argparse | import argparse | ||||||
| import requests |  | ||||||
| import subprocess | import subprocess | ||||||
| import urllib.parse | import urllib.parse | ||||||
| from uuid import getnode | from uuid import getnode | ||||||
| from datetime import datetime, timedelta | from datetime import datetime, timedelta | ||||||
|  |  | ||||||
|  | # Set to False to allow packet sniffing/capture. | ||||||
|  | # Set separately for dmmUpdater | ||||||
|  | SSL_VERIFY = True | ||||||
|  |  | ||||||
| parser = argparse.ArgumentParser(description='DMM bypass script') | 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('-g', '--game', type=str, help="DMM code name of the game", default="kfp2g") | ||||||
|  | parser.add_argument('-t', '--type', help="DMM game type (ACL/GCL)", default="GCL") | ||||||
| parser.add_argument('-u', '--update', help="Check for game update before launching", action='store_true') | parser.add_argument('-u', '--update', help="Check for game update before launching", action='store_true') | ||||||
| args = parser.parse_args() | args = parser.parse_args() | ||||||
|  |  | ||||||
| def load_config(config_name): | def load_config(game_id): | ||||||
|  |     config_name = game_id + ".cfg" | ||||||
|  |     if not os.path.exists(config_name): | ||||||
|  |         return None | ||||||
|  |     else: | ||||||
|         with open(config_name, "rt", encoding="utf-8") as f: |         with open(config_name, "rt", encoding="utf-8") as f: | ||||||
|             return json.load(f) |             return json.load(f) | ||||||
|  |  | ||||||
| def save_config(config_name, config): | def save_config(game_id, config): | ||||||
|     with open(config_name, 'wt', encoding="utf-8") as f: |     with open(game_id + '.cfg', 'wt', encoding="utf-8") as f: | ||||||
|         json.dump(config, f, indent=1, ensure_ascii=False) |         json.dump(config, f, indent=1, ensure_ascii=False) | ||||||
|  |  | ||||||
| config_name = args.game + '.cfg' | config = load_config(args.game) | ||||||
|  |  | ||||||
| if not os.path.exists(config_name): | if config is None: | ||||||
|     subprocess.check_call([sys.executable, "-m", "pip", "install", "requests", "beautifulsoup4", "PyPasser"]) |     subprocess.check_call([sys.executable, "-m", "pip", "install", "requests", "beautifulsoup4"]) | ||||||
|     config = { |     config = { | ||||||
|         "game_id" : args.game, |         "game_id" : args.game, | ||||||
|         "file_path" : input('Enter full file path to KF3 .exe: ').strip('\"'), |         "file_path" : input('Enter full file path to KF3 .exe: ').strip('\"'), | ||||||
|         "dmm_login" : input('DMM Login (email): '), |         "dmm_login" : input('DMM Login (email): '), | ||||||
|         "dmm_password" : input('DMM Password: '), |         "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" |         "use_proxy" : input('Your login data will be sent through my VPN.\nIs that okay? (yes/no): ').lower() == "yes" | ||||||
|     } |     } | ||||||
|     save_config(config_name, config) |  | ||||||
|  |     save_config(args.game, config) | ||||||
| else: | else: | ||||||
|     config = load_config(config_name) |     print(f'Loaded settings from {args.game}.cfg') | ||||||
|     print(f'Loaded settings from {config_name}') |  | ||||||
|  |  | ||||||
| config["update_game"] = args.update | config["update_game"] = args.update | ||||||
|  | config["game_type"] = args.type | ||||||
|  |  | ||||||
|  | import requests | ||||||
| import dmmUpdater | import dmmUpdater | ||||||
| from bs4 import BeautifulSoup | from bs4 import BeautifulSoup | ||||||
| from pypasser import reCaptchaV3 |  | ||||||
|  |  | ||||||
| def get_hash(data): | def get_hash(data): | ||||||
|     sha_obj = hashlib.sha256() |     sha_obj = hashlib.sha256() | ||||||
| @@ -69,44 +78,54 @@ def get_game_root_path(user_path, game_dir, exe_name): | |||||||
|     else: |     else: | ||||||
|         raise Exception(f"Path to game files is incorrect! Ensure it points to {exe_name} or contains {game_dir}!") |         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): | def retrieve_oauth_code(login, password, session : requests.Session): | ||||||
|     try: |     try: | ||||||
|         print("Retrieving login form") |         print("Retrieving login url") | ||||||
|         url = "https://accounts.dmm.com/service/login/password" |         url = "https://apidgp-gameplayer.games.dmm.com/v5/auth/login/url" | ||||||
|         result = session.get(url) |         data = {"prompt":"choose"} | ||||||
|  |         result = session.post(url, json=data, verify=SSL_VERIFY) | ||||||
|  |         result.raise_for_status() | ||||||
|  |         url = result.json()["data"]["url"] | ||||||
|  |         encoded_url = urllib.parse.quote_plus(url.replace("https://accounts.dmm.com/service/oauth/select/=/path=", "").replace("oauth/select/", "oauth/").replace("accounts?prompt=choose", "accounts")) | ||||||
|  |  | ||||||
|  |         print("Retrieving login form token") | ||||||
|  |         result = session.get(url, verify=SSL_VERIFY) | ||||||
|         result.raise_for_status() |         result.raise_for_status() | ||||||
|         page = BeautifulSoup(result.content, 'html.parser') |         page = BeautifulSoup(result.content, 'html.parser') | ||||||
|         token = page.find('input', attrs={"name":"token"}).get("value") |         token = page.find('input', attrs={"name":"token"}).get("value") | ||||||
|         return token |  | ||||||
|  |         print("Retrieving oauth link") | ||||||
|  |         url = "https://accounts.dmm.com/service/oauth/authenticate" | ||||||
|  |         headers = {"Content-Type": "application/x-www-form-urlencoded", | ||||||
|  |                    "check_done_login":"true"} | ||||||
|  |         data = f"token={token}&login_id={login}&password={password}&use_auto_login=1&path={encoded_url}&recaptchaToken=" | ||||||
|  |         result = session.post(url, data, headers=headers, verify=SSL_VERIFY) | ||||||
|  |         result.raise_for_status() | ||||||
|  |         page = BeautifulSoup(result.content, 'html.parser') | ||||||
|  |         final_url = page.find('input', attrs={"id":"ga-param-service-url"}).get("value") | ||||||
|  |  | ||||||
|  |         print("Retrieving oauth token") | ||||||
|  |         result = session.get(final_url, allow_redirects=False, verify=SSL_VERIFY) | ||||||
|  |         redirect_url = result.next.url | ||||||
|  |         oauth_token = redirect_url.split("=")[-1] | ||||||
|  |  | ||||||
|  |         return oauth_token | ||||||
|  |      | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|         print("Failed to retrieve login form:", e) |         print("Failed to retrieve login form:", e) | ||||||
|  |  | ||||||
| def retrieve_captcha_token(): | def retrieve_access_token(oauth_token, session: requests.Session): | ||||||
|     try: |     url = "https://apidgp-gameplayer.games.dmm.com/v5/auth/accesstoken/issue" | ||||||
|         captcha = reCaptchaV3("https://www.google.com/recaptcha/enterprise/anchor?ar=1&k=6LfZLQEVAAAAAC-8pKwFNuzVoJW4tfUCghBX_7ZE&co=aHR0cHM6Ly9hY2NvdW50cy5kbW0uY29tOjQ0Mw..&hl=ja&v=1Bq_oiMBd4XPUhKDwr0YL1Js&size=invisible") |     result = session.post(url, json={"code":oauth_token}, verify=SSL_VERIFY) | ||||||
|         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() |     result.raise_for_status() | ||||||
|         cookies = result.cookies.get_dict() |     data = result.json()["data"] | ||||||
|         return cookies["login_secure_id"], cookies["login_session_id"] |     return data["access_token"], data["expires_in_seconds"] | ||||||
|     except Exception as e: |  | ||||||
|         print("Failed to log in:", e) |  | ||||||
|         return None, None |  | ||||||
|  |  | ||||||
| def agree_to_game_terms(game_id, use_proxy): | def agree_to_game_terms(game_id, use_proxy): | ||||||
|     try: |     try: | ||||||
|         print("Accepting updated game terms of use") |         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"} |         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" |         url = "https://katworks.sytes.net/proxy/agreement" 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} |         data = {"product_id":game_id,"is_notification":False,"is_myapp":False} | ||||||
|         result = requests.post(url, headers=headers, json=data) |         result = requests.post(url, headers=headers, json=data) | ||||||
|         result.raise_for_status() |         result.raise_for_status() | ||||||
| @@ -118,22 +137,25 @@ def agree_to_game_terms(game_id, use_proxy): | |||||||
|         print("Failed to accept terms of use:", e) |         print("Failed to accept terms of use:", e) | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
| def retrieve_launch_params(game_id, mac_addr, hdd_serial, motherboard, login_secure, login_session, use_proxy, update_game): | def retrieve_launch_params(game_id, game_type, mac_addr, hdd_serial, motherboard, access_token, session: requests.Session, use_proxy = False, update_game = False): | ||||||
|     try: |     try: | ||||||
|         print("Retrieving launch arguments") |         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"} |         data = {"product_id":game_id,"game_type":game_type,"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"} |         headers = {"User-Agent": "DMMGamePlayer5-Win/5.3.12 Electron/32.1.0", | ||||||
|         cookies = {"login_secure_id":login_secure, "login_session_id":login_session} |                    "Client-App": "DMMGamePlayer5", | ||||||
|  |                    "Client-version": "5.3.12", | ||||||
|  |                    "Content-Type": "application/json", | ||||||
|  |                    "actauth":access_token} | ||||||
|         if update_game: |         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" |             url = "https://katworks.sytes.net/proxy/launchAndUpdate" if use_proxy else "https://apidgp-gameplayer.games.dmm.com/v5/r2/launch/cl"  | ||||||
|         else: |         else: | ||||||
|             url = "https://katworks.sytes.net/KF/Api/DMM/launch" if use_proxy else "https://apidgp-gameplayer.games.dmm.com/v5/launch/cl" |             url = "https://katworks.sytes.net/proxy/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 = session.post(url, headers=headers, json=data, verify=SSL_VERIFY) | ||||||
|         result.raise_for_status() |         result.raise_for_status() | ||||||
|         result_json = result.json() |         result_json = result.json() | ||||||
|         if result_json["result_code"] == 308: |         if result_json["result_code"] == 308: | ||||||
|             if agree_to_game_terms(game_id, use_proxy): |             if agree_to_game_terms(game_id, use_proxy): | ||||||
|                 result = requests.post(url, cookies=cookies, headers=headers, json=data) |                 result = session.post(url, headers=headers, json=data, verify=SSL_VERIFY) | ||||||
|                 result.raise_for_status() |                 result.raise_for_status() | ||||||
|                 result_json = result.json() |                 result_json = result.json() | ||||||
|                 if result_json["result_code"] != 100: |                 if result_json["result_code"] != 100: | ||||||
| @@ -145,11 +167,18 @@ def retrieve_launch_params(game_id, mac_addr, hdd_serial, motherboard, login_sec | |||||||
|         data = result_json["data"] |         data = result_json["data"] | ||||||
|         return data |         return data | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|  |         if str(e).startswith("203:"): | ||||||
|  |             config = load_config(game_id) | ||||||
|  |             config["saved_login"] = None | ||||||
|  |             save_config(game_id, config) | ||||||
|  |             print("DMM session has expired. Saved login data cleared. Please run the program again.") | ||||||
|  |         else: | ||||||
|             print("Failed to retrieve launch arguments:", e) |             print("Failed to retrieve launch arguments:", e) | ||||||
|  |  | ||||||
| def main(config): | def main(config): | ||||||
|     #required arguments |     #required arguments | ||||||
|     game_id     = config["game_id"] |     game_id     = config["game_id"] | ||||||
|  |     game_type   = config["game_type"] | ||||||
|     exe_location = config["file_path"] |     exe_location = config["file_path"] | ||||||
|     login       = urllib.parse.quote_plus(config["dmm_login"]) |     login       = urllib.parse.quote_plus(config["dmm_login"]) | ||||||
|     password    = urllib.parse.quote_plus(config["dmm_password"]) |     password    = urllib.parse.quote_plus(config["dmm_password"]) | ||||||
| @@ -172,30 +201,30 @@ def main(config): | |||||||
|                 saved_login = None |                 saved_login = None | ||||||
|  |  | ||||||
|         if saved_login is not None: |         if saved_login is not None: | ||||||
|             login_secure, login_session = saved_login["login_secure"], saved_login["login_session"] |             access_token = saved_login["access_token"] | ||||||
|             print("Loaded login data from previous session") |             print("Loaded token from previous session") | ||||||
|         else: |         else: | ||||||
|             token = retrieve_login_token(session) |             oauth_code = retrieve_oauth_code(login, password, session) | ||||||
|             captcha = retrieve_captcha_token() |             if oauth_code == None: | ||||||
|  |  | ||||||
|             if token == None or captcha == None: |  | ||||||
|                 return |                 return | ||||||
|              |              | ||||||
|             login_secure, login_session = retrieve_auth_keys(login, password, token, captcha, session) |             access_token, expiration_seconds = retrieve_access_token(oauth_code, 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 |             token_expiration = int((current_time + timedelta(seconds=expiration_seconds)).timestamp()) | ||||||
|             config["saved_login"] = saved_login = {"login_secure" : login_secure, "login_session" : login_session, "expiration": login_expiration} |             config["saved_login"] = saved_login = {"access_token" : access_token, "expiration": token_expiration} | ||||||
|             del(config["update_game"]) |             del(config["update_game"]) | ||||||
|             save_config(config_name, config) |             save_config(game_id, config) | ||||||
|          |          | ||||||
|         if not use_proxy: input("Enable VPN now and press Enter") |         if not use_proxy: input("Enable VPN now and press Enter") | ||||||
|  |  | ||||||
|         #execute_args, file_list_url, file_access_params |         #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) |         launch_data : dict = retrieve_launch_params(game_id, game_type, mac_addr, hdd_serial, motherboard, access_token, session, use_proxy, update_game) | ||||||
|  |                  | ||||||
|  |         if launch_data == None: | ||||||
|  |             return | ||||||
|          |          | ||||||
|         execute_args : str = launch_data.get("execute_args", None) |         execute_args : str = launch_data.get("execute_args", None) | ||||||
|  |  | ||||||
|         if execute_args == None: |         if execute_args == None: | ||||||
|             return |             return | ||||||
|          |          | ||||||
| @@ -219,8 +248,7 @@ def main(config): | |||||||
|  |  | ||||||
|         print("Starting game") |         print("Starting game") | ||||||
|         args = [os.path.join(game_root_path, exec_name)] + execute_args.split() |         args = [os.path.join(game_root_path, exec_name)] + execute_args.split() | ||||||
|         print(args) |  | ||||||
|         subprocess.Popen(args, start_new_session=True)  |         subprocess.Popen(args, start_new_session=True)  | ||||||
|         input("Done. Press enter to exit (this will close the game)") |         input("Done. Press enter to exit") | ||||||
|  |  | ||||||
| main(config) | main(config) | ||||||
| @@ -6,10 +6,14 @@ import urllib.parse | |||||||
| from urllib.request import urlretrieve | from urllib.request import urlretrieve | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
|  |  | ||||||
|  | # Set to False to allow packet sniffing/capture. | ||||||
|  | # Set separately for dmmBypass | ||||||
|  | SSL_VERIFY = True | ||||||
|  |  | ||||||
| def get_file_list(url): | def get_file_list(url): | ||||||
|     url = "https://apidgp-gameplayer.games.dmm.com" + url |     url = "https://apidgp-gameplayer.games.dmm.com" + url | ||||||
|     print("Retrieving file list from " + url) |     print("Retrieving file list from " + url) | ||||||
|     result = requests.get(url) |     result = requests.get(url, verify=SSL_VERIFY) | ||||||
|     result.raise_for_status() |     result.raise_for_status() | ||||||
|     data = result.json()["data"] |     data = result.json()["data"] | ||||||
|     return data["domain"], data["file_list"] |     return data["domain"], data["file_list"] | ||||||
| @@ -71,7 +75,7 @@ def update_game(game_path, files_url, files_param): | |||||||
|                 file = files_to_download[index] |                 file = files_to_download[index] | ||||||
|                 url, path = file["url"], file["path"] |                 url, path = file["url"], file["path"] | ||||||
|                  |                  | ||||||
|                 response = requests.get(url, timeout=10, stream=True) |                 response = requests.get(url, timeout=10, stream=True, verify=SSL_VERIFY) | ||||||
|                 total_size = int(response.headers.get("content-length", 0)) |                 total_size = int(response.headers.get("content-length", 0)) | ||||||
|                 block_size = 1024 * 1024 |                 block_size = 1024 * 1024 | ||||||
|                 downloaded = 0 |                 downloaded = 0 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user