Compare commits

..

1 Commits

Author SHA1 Message Date
06b4bca63a Added trust levels for the public VPN 2025-03-11 00:36:06 +01:00
3 changed files with 85 additions and 99 deletions

View File

@@ -14,8 +14,16 @@ Run `run_KF3.bat`, it will ask you for some information.
It will ask for the location of the KF3 executable. Generally this will be `C:\users\[your name]\KFP2G\けもフレ3.exe`.
Input your DMM email and then password when prompted, and `yes` if you want to use the public VPN to connect to DMM's server. If you choose not to use the public VPN, then you will need to enable your own Japanese VPN while logging in to the game.
Input your DMM email and then password when prompted, and set the desired trust level (more info on that below). If you choose not to use the public VPN, then you will need to enable your own Japanese VPN while logging in to the game.
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.
## Trust levels
DMM uses Geo-blocking for some requests, that requires the user to be in Japan. This can be bypassed with a VPN. I have access to an OpenVPN server owned by HAV0X that can be used to bypass this restriction. However, this comes with several security vulnerabilities for the user:
- trust level 2 - Your login and password are sent to my device. The request is processed and sent through the VPN to DMM server, and my device sends you the response. I could access the information sent in the request if I wanted to (I will not do that though).
- trust level 1 - My device is used as a proxy. A connection is made between your device, my device, and DMM, with no way of me viewing the request data (that I know of). This takes longer.
- trust level 0 - You use your own VPN. The launcher will prompt you to enable/disable VPN when needed.
I do not intentionally view or store any data sent through my server. There is no guarantee of my and HAV0X's proxy server being active at any given time.

View File

@@ -9,43 +9,43 @@ import urllib.parse
from uuid import getnode
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.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')
args = parser.parse_args()
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:
return json.load(f)
def load_config(config_name):
with open(config_name, "rt", encoding="utf-8") as f:
return json.load(f)
def save_config(game_id, config):
with open(game_id + '.cfg', 'wt', encoding="utf-8") as 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 = load_config(args.game)
config_name = args.game + '.cfg'
if config is None:
subprocess.check_call([sys.executable, "-m", "pip", "install", "requests", "beautifulsoup4"])
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 data will be sent through my VPN.\nIs that okay? (yes/no): ').lower() == "yes"
}
while True:
try:
trust_level = int(input('\t0 - Use your own VPN\n\t1 - Use Katboi\'s VPN as a proxy (slower, more secure)\n\t2 - Use Katboi\'s VPN to process the request (fastest, unsecure)\nTrust level (0-2): '))
if trust_level < 0 or trust_level > 2: raise Exception()
config["trust_level"] = trust_level
break
except:
print("Value must be in 0-2 range")
save_config(args.game, config)
save_config(config_name, config)
else:
print(f'Loaded settings from {args.game}.cfg')
config = load_config(config_name)
print(f'Loaded settings from {config_name}')
config["update_game"] = args.update
config["game_type"] = args.type
@@ -53,6 +53,7 @@ config["game_type"] = args.type
import requests
import dmmUpdater
from bs4 import BeautifulSoup
from pypasser import reCaptchaV3
def get_hash(data):
sha_obj = hashlib.sha256()
@@ -78,54 +79,44 @@ def get_game_root_path(user_path, game_dir, exe_name):
else:
raise Exception(f"Path to game files is incorrect! Ensure it points to {exe_name} or contains {game_dir}!")
def retrieve_oauth_code(login, password, session : requests.Session):
def retrieve_login_token(session : requests.Session):
try:
print("Retrieving login url")
url = "https://apidgp-gameplayer.games.dmm.com/v5/auth/login/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)
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")
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
return token
except Exception as e:
print("Failed to retrieve login form:", e)
def retrieve_access_token(oauth_token, session: requests.Session):
url = "https://apidgp-gameplayer.games.dmm.com/v5/auth/accesstoken/issue"
result = session.post(url, json={"code":oauth_token}, verify=SSL_VERIFY)
result.raise_for_status()
data = result.json()["data"]
return data["access_token"], data["expires_in_seconds"]
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 agree_to_game_terms(game_id, use_proxy):
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, trust_level):
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 = "https://katworks.sytes.net/proxy/agreement" if use_proxy else "https://apidgp-gameplayer.games.dmm.com/v5/agreement/confirm/client"
url = "https://katworks.sytes.net/KF/Api/DMM/agreement" if trust_level == 2 else "https://katworks.sytes.net/proxy/agreement" if trust_level == 1 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()
@@ -137,25 +128,22 @@ def agree_to_game_terms(game_id, use_proxy):
print("Failed to accept terms of use:", e)
return False
def retrieve_launch_params(game_id, game_type, mac_addr, hdd_serial, motherboard, access_token, session: requests.Session, use_proxy = False, update_game = False):
def retrieve_launch_params(game_id, game_type, mac_addr, hdd_serial, motherboard, login_secure, login_session, trust_level, update_game):
try:
print("Retrieving launch arguments")
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",
"actauth":access_token}
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/proxy/launchAndUpdate" if use_proxy else "https://apidgp-gameplayer.games.dmm.com/v5/r2/launch/cl"
url = "https://katworks.sytes.net/KF/Api/DMM/update" if trust_level == 2 else "https://katworks.sytes.net/proxy/launchAndUpdate" if trust_level == 1 else "https://apidgp-gameplayer.games.dmm.com/v5/r2/launch/cl"
else:
url = "https://katworks.sytes.net/proxy/launch" if use_proxy else "https://apidgp-gameplayer.games.dmm.com/v5/launch/cl"
result = session.post(url, headers=headers, json=data, verify=SSL_VERIFY)
url = "https://katworks.sytes.net/KF/Api/DMM/launch" if trust_level == 2 else "https://katworks.sytes.net/proxy/launch" if trust_level == 1 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 = session.post(url, headers=headers, json=data, verify=SSL_VERIFY)
if agree_to_game_terms(game_id, trust_level):
result = requests.post(url, cookies=cookies, headers=headers, json=data)
result.raise_for_status()
result_json = result.json()
if result_json["result_code"] != 100:
@@ -167,13 +155,7 @@ def retrieve_launch_params(game_id, game_type, mac_addr, hdd_serial, motherboard
data = result_json["data"]
return data
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):
#required arguments
@@ -184,7 +166,7 @@ def main(config):
password = urllib.parse.quote_plus(config["dmm_password"])
#optional arguments
update_game = config.get("update_game", False)
use_proxy = config.get("use_proxy", False)
trust_level = config.get("trust_level", 0)
saved_login = config.get("saved_login", None)
#dmm requires these values
mac_addr = get_mac()
@@ -201,30 +183,30 @@ def main(config):
saved_login = None
if saved_login is not None:
access_token = saved_login["access_token"]
print("Loaded token from previous session")
login_secure, login_session = saved_login["login_secure"], saved_login["login_session"]
print("Loaded login data from previous session")
else:
oauth_code = retrieve_oauth_code(login, password, session)
if oauth_code == None:
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
access_token, expiration_seconds = retrieve_access_token(oauth_code, session)
token_expiration = int((current_time + timedelta(seconds=expiration_seconds)).timestamp())
config["saved_login"] = saved_login = {"access_token" : access_token, "expiration": token_expiration}
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(game_id, config)
save_config(config_name, config)
if not use_proxy: input("Enable VPN now and press Enter")
if trust_level == 0: input("Enable VPN now and press Enter")
#execute_args, file_list_url, file_access_params
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
launch_data : dict = retrieve_launch_params(game_id, game_type, mac_addr, hdd_serial, motherboard, login_secure, login_session, trust_level, update_game)
execute_args : str = launch_data.get("execute_args", None)
if execute_args == None:
return
@@ -241,7 +223,7 @@ def main(config):
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 trust_level == 0: input("Disable VPN now and press Enter")
if update_game:
dmmUpdater.update_game(game_root_path, file_list_url, file_list_params)

View File

@@ -6,14 +6,10 @@ import urllib.parse
from urllib.request import urlretrieve
from pathlib import Path
# Set to False to allow packet sniffing/capture.
# Set separately for dmmBypass
SSL_VERIFY = True
def get_file_list(url):
url = "https://apidgp-gameplayer.games.dmm.com" + url
print("Retrieving file list from " + url)
result = requests.get(url, verify=SSL_VERIFY)
result = requests.get(url)
result.raise_for_status()
data = result.json()["data"]
return data["domain"], data["file_list"]
@@ -75,7 +71,7 @@ def update_game(game_path, files_url, files_param):
file = files_to_download[index]
url, path = file["url"], file["path"]
response = requests.get(url, timeout=10, stream=True, verify=SSL_VERIFY)
response = requests.get(url, timeout=10, stream=True)
total_size = int(response.headers.get("content-length", 0))
block_size = 1024 * 1024
downloaded = 0