You've already forked JapariBypass
Compare commits
1 Commits
master
...
06b4bca63a
| Author | SHA1 | Date | |
|---|---|---|---|
| 06b4bca63a |
10
README.md
10
README.md
@@ -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`.
|
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 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.
|
||||||
162
dmmBypass.py
162
dmmBypass.py
@@ -9,43 +9,43 @@ 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('-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(game_id):
|
def load_config(config_name):
|
||||||
config_name = game_id + ".cfg"
|
with open(config_name, "rt", encoding="utf-8") as f:
|
||||||
if not os.path.exists(config_name):
|
return json.load(f)
|
||||||
return None
|
|
||||||
else:
|
|
||||||
with open(config_name, "rt", encoding="utf-8") as f:
|
|
||||||
return json.load(f)
|
|
||||||
|
|
||||||
def save_config(game_id, config):
|
def save_config(config_name, config):
|
||||||
with open(game_id + '.cfg', 'wt', encoding="utf-8") as f:
|
with open(config_name, '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 = load_config(args.game)
|
config_name = args.game + '.cfg'
|
||||||
|
|
||||||
if config is None:
|
if not os.path.exists(config_name):
|
||||||
subprocess.check_call([sys.executable, "-m", "pip", "install", "requests", "beautifulsoup4"])
|
subprocess.check_call([sys.executable, "-m", "pip", "install", "requests", "beautifulsoup4", "PyPasser"])
|
||||||
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 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:
|
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["update_game"] = args.update
|
||||||
config["game_type"] = args.type
|
config["game_type"] = args.type
|
||||||
@@ -53,6 +53,7 @@ config["game_type"] = args.type
|
|||||||
import requests
|
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()
|
||||||
@@ -78,54 +79,44 @@ 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_oauth_code(login, password, session : requests.Session):
|
def retrieve_login_token(session : requests.Session):
|
||||||
try:
|
try:
|
||||||
print("Retrieving login url")
|
print("Retrieving login form")
|
||||||
url = "https://apidgp-gameplayer.games.dmm.com/v5/auth/login/url"
|
url = "https://accounts.dmm.com/service/login/password"
|
||||||
data = {"prompt":"choose"}
|
result = session.get(url)
|
||||||
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_access_token(oauth_token, session: requests.Session):
|
def retrieve_captcha_token():
|
||||||
url = "https://apidgp-gameplayer.games.dmm.com/v5/auth/accesstoken/issue"
|
try:
|
||||||
result = session.post(url, json={"code":oauth_token}, verify=SSL_VERIFY)
|
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.raise_for_status()
|
return captcha
|
||||||
data = result.json()["data"]
|
except Exception as e:
|
||||||
return data["access_token"], data["expires_in_seconds"]
|
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:
|
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 = "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}
|
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()
|
||||||
@@ -137,25 +128,22 @@ 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, 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:
|
try:
|
||||||
print("Retrieving launch arguments")
|
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"}
|
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",
|
headers = {"User-Agent": "DMMGamePlayer5-Win/5.3.12 Electron/32.1.0", "Client-App": "DMMGamePlayer5", "Client-version": "5.3.12", "Content-Type": "application/json"}
|
||||||
"Client-App": "DMMGamePlayer5",
|
cookies = {"login_secure_id":login_secure, "login_session_id":login_session}
|
||||||
"Client-version": "5.3.12",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"actauth":access_token}
|
|
||||||
if update_game:
|
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:
|
else:
|
||||||
url = "https://katworks.sytes.net/proxy/launch" if use_proxy else "https://apidgp-gameplayer.games.dmm.com/v5/launch/cl"
|
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 = session.post(url, headers=headers, json=data, verify=SSL_VERIFY)
|
result = requests.post(url, cookies=cookies, headers=headers, json=data)
|
||||||
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, trust_level):
|
||||||
result = session.post(url, headers=headers, json=data, verify=SSL_VERIFY)
|
result = requests.post(url, cookies=cookies, headers=headers, json=data)
|
||||||
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:
|
||||||
@@ -167,13 +155,7 @@ def retrieve_launch_params(game_id, game_type, mac_addr, hdd_serial, motherboard
|
|||||||
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:"):
|
print("Failed to retrieve launch arguments:", e)
|
||||||
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)
|
|
||||||
|
|
||||||
def main(config):
|
def main(config):
|
||||||
#required arguments
|
#required arguments
|
||||||
@@ -184,7 +166,7 @@ def main(config):
|
|||||||
password = urllib.parse.quote_plus(config["dmm_password"])
|
password = urllib.parse.quote_plus(config["dmm_password"])
|
||||||
#optional arguments
|
#optional arguments
|
||||||
update_game = config.get("update_game", False)
|
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)
|
saved_login = config.get("saved_login", None)
|
||||||
#dmm requires these values
|
#dmm requires these values
|
||||||
mac_addr = get_mac()
|
mac_addr = get_mac()
|
||||||
@@ -201,30 +183,30 @@ def main(config):
|
|||||||
saved_login = None
|
saved_login = None
|
||||||
|
|
||||||
if saved_login is not None:
|
if saved_login is not None:
|
||||||
access_token = saved_login["access_token"]
|
login_secure, login_session = saved_login["login_secure"], saved_login["login_session"]
|
||||||
print("Loaded token from previous session")
|
print("Loaded login data from previous session")
|
||||||
else:
|
else:
|
||||||
oauth_code = retrieve_oauth_code(login, password, session)
|
token = retrieve_login_token(session)
|
||||||
if oauth_code == None:
|
captcha = retrieve_captcha_token()
|
||||||
|
|
||||||
|
if token == None or captcha == None:
|
||||||
return
|
return
|
||||||
|
|
||||||
access_token, expiration_seconds = retrieve_access_token(oauth_code, session)
|
login_secure, login_session = retrieve_auth_keys(login, password, token, captcha, session)
|
||||||
|
if login_secure == None or login_session == None:
|
||||||
|
return
|
||||||
|
|
||||||
token_expiration = int((current_time + timedelta(seconds=expiration_seconds)).timestamp())
|
login_expiration = int((current_time + timedelta(days=364)).timestamp()) #expire in less than a year
|
||||||
config["saved_login"] = saved_login = {"access_token" : access_token, "expiration": token_expiration}
|
config["saved_login"] = saved_login = {"login_secure" : login_secure, "login_session" : login_session, "expiration": login_expiration}
|
||||||
del(config["update_game"])
|
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
|
#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)
|
launch_data : dict = retrieve_launch_params(game_id, game_type, mac_addr, hdd_serial, motherboard, login_secure, login_session, trust_level, 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
|
||||||
|
|
||||||
@@ -241,7 +223,7 @@ def main(config):
|
|||||||
install_dir = launch_data["install_dir"]
|
install_dir = launch_data["install_dir"]
|
||||||
game_root_path = get_game_root_path(exe_location, install_dir, exec_name)
|
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:
|
if update_game:
|
||||||
dmmUpdater.update_game(game_root_path, file_list_url, file_list_params)
|
dmmUpdater.update_game(game_root_path, file_list_url, file_list_params)
|
||||||
|
|||||||
@@ -6,14 +6,10 @@ 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, verify=SSL_VERIFY)
|
result = requests.get(url)
|
||||||
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"]
|
||||||
@@ -75,7 +71,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, verify=SSL_VERIFY)
|
response = requests.get(url, timeout=10, stream=True)
|
||||||
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