from flask import Flask, request, jsonify, send_file from config import Config import os import sqlite3 import binascii import bcrypt import re from datetime import datetime import xml.etree.ElementTree as ET from Crypto.Cipher import AES import urllib.parse import json # Found in: aesManager::initialize() # Used for: Crypting parameter bytes sent by client # Credit: https://github.com/Walter-o/gcm-downloader AES_CBC_KEY = b"oLxvgCJjMzYijWIldgKLpUx5qhUhguP1" # Found in: aesManager::decryptCBC() and aesManager::encryptCBC() # Used for: Crypting parameter bytes sent by client # Credit: https://github.com/Walter-o/gcm-downloader AES_CBC_IV = b"6NrjyFU04IO9j9Yo" # Decrypt AES encrypted data, takes in a hex string # Credit: https://github.com/Walter-o/gcm-downloader def decryptAES(data, key=AES_CBC_KEY, iv=AES_CBC_IV): return AES.new(key, AES.MODE_CBC, iv).decrypt(bytes.fromhex(data)) # Encrypt data with AES, takes in a bytes object # Credit: https://github.com/Walter-o/gcm-downloader def encryptAES(data, key=AES_CBC_KEY, iv=AES_CBC_IV): while len(data) % 16 != 0: data += b"\x00" encryptedData = AES.new(key, AES.MODE_CBC, iv).encrypt(data) return encryptedData.hex() start_stages = [3,4,5,7,9,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,214] # 214 is tutorial song. start_avatars = [] exclude_stage_exp_pre_100 = [0,1,2,6,8,10,11,12,13,14,15,16,17,18,19,20,21,53,74,75] # not used for now, just archiving exclude_stage_exp = [121,134,166,167,168,169,170,215,225] # 134 and 170 unoccupied dummy tracks (filled with Departure -Remix-), #121 (and 93-96 lady gaga songs) removed (can be enabled by patching stageParam:isAvailable, or change the last byte before next song's name - 1 from 01 to 03 in stage_param.dat. # Rest are exp unlocked songs. exclude_avatar_exp = [28,29] stage_price = 1 avatar_price = 1 item_price = 2 coin_reward = 1 app = Flask(__name__) app.config.from_object(Config) DATABASE = 'player.db' def create_table(): with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() cursor.execute( """ CREATE TABLE IF NOT EXISTS user ( id INTEGER PRIMARY KEY AUTOINCREMENT, username VARCHAR(20) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, device_id VARCHAR(512), data TEXT, crc DECIMAL(10,0), timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP );""") cursor.execute( """ CREATE TABLE IF NOT EXISTS daily_reward ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id VARCHAR(512), timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, my_stage TEXT, my_avatar TEXT, items TEXT, day INT );""") cursor.execute( """ CREATE TABLE IF NOT EXISTS result ( rid INTEGER PRIMARY KEY AUTOINCREMENT, vid VARCHAR(512) NOT NULL, tid VARCHAR(512) NOT NULL, sid VARCHAR(512) NOT NULL, stts VARCHAR(64), id VARCHAR(8), mode VARCHAR(4), avatar VARCHAR(4), score VARCHAR(16), high_score VARCHAR(128), play_rslt VARCHAR(128), item VARCHAR(16), os VARCHAR(16), os_ver VARCHAR(16), ver VARCHAR(16), mike VARCHAR(8), FOREIGN KEY (vid) REFERENCES user(device_id) );""") cursor.execute( """ CREATE TABLE IF NOT EXISTS whitelist ( id VARCHAR(512) PRIMARY KEY );""") cursor.execute( """ CREATE TABLE IF NOT EXISTS blacklist ( id VARCHAR(512) PRIMARY KEY, reason VARCHAR(256) );""") connection.commit() create_table() ## Helper function def crc32_decimal(data): crc32_hex = binascii.crc32(data.encode()) return int(crc32_hex & 0xFFFFFFFF) def hash_password(password): salt = bcrypt.gensalt() hashed_password = bcrypt.hashpw(password.encode('utf-8'), salt) return hashed_password def verify_password(password, hashed_password): return bcrypt.checkpw(password.encode('utf-8'), hashed_password) def is_alphanumeric(username): pattern = r"^[a-zA-Z0-9]+$" return bool(re.match(pattern, username)) decrypted_fields = "" original_field = "" @app.before_request def before_request(): url = request.url match = re.search(r'\?(.*)', url) if match: global decrypted_fields global original_field original_field = match.group(1) decrypted_fields = urllib.parse.parse_qs(decryptAES(match.group(1))[:-1]) def get_model_pak(host): mid = ET.Element("model_pak") rid = ET.Element("date") uid = ET.Element("url") rid.text = app.config['MODEL'] uid.text = host + "files/gc2/model" + app.config['MODEL'] + ".pak" mid.append(rid) mid.append(uid) return mid def get_tune_pak(host): mid = ET.Element("tuneFile_pak") rid = ET.Element("date") uid = ET.Element("url") rid.text = app.config['TUNEFILE'] uid.text = host + "files/gc2/tuneFile" + app.config['TUNEFILE'] + ".pak" mid.append(rid) mid.append(uid) return mid def get_skin_pak(host): mid = ET.Element("skin_pak") rid = ET.Element("date") uid = ET.Element("url") rid.text = app.config['SKIN'] uid.text = host + "files/gc2/skin" + app.config['SKIN'] + ".pak" mid.append(rid) mid.append(uid) return mid def get_m4a_path(host): mid = ET.Element("m4a_path") mid.text = host + "files/gc2/audio/" return mid def get_stage_path(host): mid = ET.Element("stage_path") mid.text = host + "files/gc2/stage/" return mid def get_stage_zero(): sid = ET.Element("my_stage") did = ET.Element("stage_id") cid = ET.Element("ac_mode") did.text = "0" cid.text = "0" sid.append(did) sid.append(cid) return sid def get_user_data(uid, data_field): with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() query = f"SELECT {data_field} FROM user WHERE device_id = ?" cursor.execute(query, (uid[b'vid'][0].decode(),)) result = cursor.fetchone() return result def set_user_data(uid, data_field, new_data): with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() query = f"UPDATE user SET {data_field} = ? WHERE device_id = ?" cursor.execute(query, (new_data, uid[b'vid'][0].decode())) connection.commit() def check_whitelist(uid): with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() cursor.execute("SELECT * FROM whitelist") for row in cursor.fetchall(): if row[0] == uid[b'vid'][0].decode(): return True return False def check_blacklist(uid): user = get_user_data(uid, "username") with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() cursor.execute("SELECT * FROM blacklist") for row in cursor.fetchall(): if len(row[0]) > 20: if row[0] == uid[b'vid'][0].decode(): return False elif user is not None: if row[0] == user[0]: return False return True def inform_page(text, mode): if mode == 0: mode = "/files/web/ttl_taitoid.png" elif mode == 1: mode = "/files/web/ttl_information.png" elif mode == 2: mode = "/files/web/ttl_buy.png" with open("files/inform.html", "r") as file: return file.read().format(text=text, img=mode) #Serving CDN files root_folder = os.path.dirname(os.path.abspath(__file__)) allowed_folders = ["files"] @app.route('/info.php', methods=['GET']) def info(): file_path = os.path.join(root_folder, "files/history.html") return send_file(file_path) @app.route('/history.php', methods=['GET']) def history(): file_path = os.path.join(root_folder, "files/history.html") return send_file(file_path) @app.route('/start.php', methods=['GET']) def start(): global decrypted_fields file_path = os.path.join(root_folder, "files/start.xml") # Parse the XML file try: tree = ET.parse(file_path) root = tree.getroot() except Exception as e: return jsonify({"error": "Failed to parse XML", "details": str(e)}), 500 # Retrieve user information user = get_user_data(decrypted_fields, "username") userID = get_user_data(decrypted_fields, "id") # Check authorization and access should_serve = True if app.config.get('AUTHORIZATION_NEEDED', False): should_serve = check_whitelist(decrypted_fields) and check_blacklist(decrypted_fields) if should_serve: # Base host string for generating URLs host_string = f"http://{app.config['HOST']}:{app.config['PORT']}/" device_id = decrypted_fields[b'vid'][0].decode() # Append additional XML nodes for generator in [get_model_pak, get_tune_pak, get_skin_pak, get_m4a_path, get_stage_path]: try: root.append(generator(host_string)) except Exception as e: return jsonify({"error": f"Failed to generate element from {generator.__name__}", "details": str(e)}), 500 daily_reward_elem = root.find(".//login_bonus") if daily_reward_elem is None: return jsonify({"error": "Missing element in XML"}), 500 # Parse last_count from last_count_elem = daily_reward_elem.find("last_count") if last_count_elem is None or not last_count_elem.text.isdigit(): return jsonify({"error": "Invalid or missing last_count in XML"}), 500 last_count = int(last_count_elem.text) now_count = 1 with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() # Check for an existing row cursor.execute("SELECT day, timestamp FROM daily_reward WHERE device_id = ?", (device_id,)) row = cursor.fetchone() if row: current_day, last_timestamp = row last_date = datetime.strptime(last_timestamp, "%Y-%m-%d %H:%M:%S") current_date = datetime.now() # Check if one day has passed if (current_date.date() - last_date.date()).days >= 1: now_count = current_day + 1 # Reset to day 1 if it exceeds last_count if now_count > last_count: now_count = 1 else: now_count = current_day # Add or update in now_count_elem = daily_reward_elem.find("now_count") if now_count_elem is None: now_count_elem = ET.Element("now_count") daily_reward_elem.append(now_count_elem) now_count_elem.text = str(now_count) with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() cursor.execute("SELECT my_avatar, my_stage, coin FROM daily_reward WHERE device_id = ?", (device_id,)) result = cursor.fetchone() if (result): my_avatar = set(json.loads(result[0])) if result[0] else start_avatars my_stage = set(json.loads(result[1])) if result[1] else start_stages coin = result[2] if result[2] is not None else 10 else: my_avatar = start_avatars my_stage = start_stages coin = 10 coin_elem = ET.Element("my_coin") coin_elem.text = str(coin) root.append(coin_elem) # Add `my_avatar` elements to XML for avatar_id in my_avatar: avatar_elem = ET.Element("my_avatar") avatar_elem.text = str(avatar_id) root.append(avatar_elem) # Add `my_stage` elements to XML for stage_id in my_stage: stage_elem = ET.Element("my_stage") stage_id_elem = ET.Element("stage_id") stage_id_elem.text = str(stage_id) stage_elem.append(stage_id_elem) ac_mode_elem = ET.Element("ac_mode") ac_mode_elem.text = "1" stage_elem.append(ac_mode_elem) root.append(stage_elem) if user: tid = ET.Element("taito_id") tid.text = user[0] root.append(tid) sid_elem = ET.Element("sid") sid_elem.text = str(userID[0]) root.append(sid_elem) try: sid = get_stage_zero() root.append(sid) except Exception as e: return jsonify({"error": "Failed to retrieve stage zero", "details": str(e)}), 500 # Serialize XML to string and return xml_response = ET.tostring(root, encoding='unicode') return xml_response else: return jsonify({"error": "Access denied"}), 403 @app.route('/sync.php', methods=['POST','GET']) def sync(): global decrypted_fields device_id = decrypted_fields[b'vid'][0].decode() file_path = os.path.join(root_folder, "files/sync.xml") tree = ET.parse(file_path) root = tree.getroot() user = get_user_data(decrypted_fields, "username") should_serve = True if app.config['AUTHORIZATION_NEEDED']: should_serve = check_whitelist(decrypted_fields) if should_serve: should_serve = check_blacklist(decrypted_fields) if should_serve: host_string = "http://" + app.config['HOST'] + ":" + str(app.config['PORT']) + "/" model_pak = get_model_pak(host_string) tune_pak = get_tune_pak(host_string) skin_pak = get_skin_pak(host_string) root.append(model_pak) root.append(tune_pak) root.append(skin_pak) m4a_path = get_m4a_path(host_string) stage_path = get_stage_path(host_string) root.append(m4a_path) root.append(stage_path) with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() cursor.execute("SELECT my_avatar, my_stage, coin, item FROM daily_reward WHERE device_id = ?", (device_id,)) result = cursor.fetchone() if (result): my_avatar = set(json.loads(result[0])) if result[0] else start_avatars my_stage = set(json.loads(result[1])) if result[1] else start_stages coin = result[2] if result[2] is not None else 10 items = json.loads(result[3]) if result[3] else [] else: my_avatar = start_avatars my_stage = start_stages coin = 10 items = [] coin_elem = ET.Element("my_coin") coin_elem.text = str(coin) root.append(coin_elem) for item in items: item_elem = ET.Element("add_item") item_id_elem = ET.Element("id") item_id_elem.text = str(item) item_elem.append(item_id_elem) item_num_elem = ET.Element("num") item_num_elem.text = "9" item_elem.append(item_num_elem) root.append(item_elem) if (len(items) > 0): with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() cursor.execute("""UPDATE daily_reward SET item = ? WHERE device_id = ?""", ("[]", device_id)) cursor.close() # Add `my_avatar` elements to XML for avatar_id in my_avatar: avatar_elem = ET.Element("my_avatar") avatar_elem.text = str(avatar_id) root.append(avatar_elem) # Add `my_stage` elements to XML for stage_id in my_stage: stage_elem = ET.Element("my_stage") stage_id_elem = ET.Element("stage_id") stage_id_elem.text = str(stage_id) stage_elem.append(stage_id_elem) ac_mode_elem = ET.Element("ac_mode") ac_mode_elem.text = "1" stage_elem.append(ac_mode_elem) root.append(stage_elem) if user: tid = ET.Element("taito_id") tid.text = user[0] root.append(tid) sid = get_stage_zero() root.append(sid) kid = ET.Element("friend_num") kid.text = "9" root.append(kid) xml = ET.tostring(tree.getroot(), encoding='unicode') return xml else: return jsonify({"error": "Access denied"}), 403 @app.route('/confirm_tier.php', methods=['GET']) def tier(): file_path = os.path.join(root_folder, "files/tier.xml") return send_file(file_path) @app.route('/login_bonus.php', methods=['GET']) def bonus(): global decrypted_fields device_id = decrypted_fields[b'vid'][0].decode() file_path = os.path.join(root_folder, "files/start.xml") # Parse the XML file try: tree = ET.parse(file_path) root = tree.getroot() except Exception as e: return jsonify({"error": "Failed to parse XML", "details": str(e)}), 500 daily_reward_elem = root.find(".//login_bonus") last_count_elem = daily_reward_elem.find("last_count") if last_count_elem is None or not last_count_elem.text.isdigit(): return jsonify({"error": "Invalid or missing last_count in XML"}), 500 last_count = int(last_count_elem.text) with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() # Check for an existing row cursor.execute("SELECT day, timestamp FROM daily_reward WHERE device_id = ?", (device_id,)) row = cursor.fetchone() time = datetime.now() formatted_time = time.strftime("%Y-%m-%d %H:%M:%S") if row: current_day, last_timestamp = row last_date = datetime.strptime(last_timestamp, "%Y-%m-%d %H:%M:%S") # Check if one day has passed if (time.date() - last_date.date()).days >= 1: current_day = current_day + 1 if (current_day > last_count): current_day = 1 # Update the table cursor.execute("""UPDATE daily_reward SET timestamp = ?, day = ? WHERE device_id = ?""", (formatted_time, current_day, device_id)) # return 0 obj xml_response = "0" else: now_count = current_day xml_response = "1" # return 1 obj else: # No row exists; create a new one cursor.execute(""" INSERT INTO daily_reward (device_id, day, timestamp, my_avatar, my_stage, coin, item) VALUES (?, ?, ?, ?, ?, ?) """, (device_id, 1, formatted_time, json.dumps(start_avatars), json.dumps(start_stages), 10, "[]")) xml_response = "0" # return 0 obj return xml_response, 200, {'Content-Type': 'application/xml'} @app.route('/gcm/php/register.php', methods=['GET']) def reg(): global decrypted_fields return "", 200 @app.route('/result.php', methods=['GET']) def result(): global decrypted_fields device_id = decrypted_fields[b'vid'][0].decode() file_path = os.path.join(root_folder, "files/result.xml") tree = ET.parse(file_path) root = tree.getroot() # Increment coin for user with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() cursor.execute("SELECT coin FROM daily_reward WHERE device_id = ?", (device_id,)) row = cursor.fetchone() if row: current_coin = row[0] if row[0] else 10 # Increment the coin value by 1 updated_coin = current_coin + coin_reward cursor.execute("UPDATE daily_reward SET coin = ? WHERE device_id = ?", (updated_coin, device_id)) # Save the record vid = decrypted_fields[b'vid'][0].decode() stts = decrypted_fields[b'stts'][0].decode() id = decrypted_fields[b'id'][0].decode() mode = decrypted_fields[b'mode'][0].decode() avatar = decrypted_fields[b'avatar'][0].decode() score = decrypted_fields[b'score'][0].decode() high_score = decrypted_fields[b'high_score'][0].decode() play_rslt = decrypted_fields[b'play_rslt'][0].decode() item = decrypted_fields[b'item'][0].decode() device_os = decrypted_fields[b'os'][0].decode() os_ver = decrypted_fields[b'os_ver'][0].decode() tid = decrypted_fields[b'tid'][0].decode() ver = decrypted_fields[b'ver'][0].decode() mike = decrypted_fields[b'mike'][0].decode() do_insert = False do_update_sid = False do_update_vid = False last_row_id = 0 #parse whether or not the device is logged in to taito ID sid = "" with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() select_command = """SELECT id FROM user WHERE device_id = ?;""" cursor.execute(select_command, (vid,)) result = cursor.fetchone() # Fetch one row from the result cursor.close() # Check if a row was found if result: sid = result[0] if sid != "": with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() check_command = """ SELECT rid, score FROM result WHERE id = ? and mode = ? and sid = ? ORDER BY CAST(score AS INTEGER) DESC; """ cursor.execute(check_command, (id, mode, sid)) records = cursor.fetchall() cursor.close() if len(records) > 0: # check to see if update is needed last_row_id = records[0][0] if (score > records[0][1]): do_update_sid = True else: # create the record do_insert = True else: with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() check_command = """ SELECT rid, score FROM result WHERE id = ? and mode = ? and sid = ? and vid = ? ORDER BY CAST(score AS INTEGER) DESC; """ cursor.execute(check_command, (id, mode, "", vid)) records = cursor.fetchall() cursor.close() if len(records) > 0: # check to see if update is needed last_row_id = records[0][0] if (score > records[0][1]): do_update_vid = True else: # create the record do_insert = True if do_insert: print("result - inserting") insert_command = """ INSERT INTO result (vid, stts, id, mode, avatar, score, high_score, play_rslt, item, os, os_ver, tid, sid, ver, mike) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); """ with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() cursor.execute(insert_command, (vid, stts, id, mode, avatar, score, high_score, play_rslt, item, device_os, os_ver, tid, sid, ver, mike)) last_row_id = cursor.lastrowid cursor.close() connection.commit() elif do_update_sid: print("result - update based off taito id") update_command = """UPDATE result SET stts = ?, avatar = ?, score = ?, high_score = ?, play_rslt = ?, item = ?, os = ?, os_ver = ?, tid = ?, ver = ?, mike = ?, vid = ? WHERE sid = ? AND id = ? AND mode = ?; """ with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() cursor.execute(update_command, (stts, avatar, score, high_score, play_rslt, item, device_os, os_ver, tid, ver, mike, vid, sid, id, mode)) cursor.close() connection.commit() elif do_update_vid: print("result - updatin based off device id") update_command = """UPDATE result SET stts = ?, avatar = ?, score = ?, high_score = ?, play_rslt = ?, item = ?, os = ?, os_ver = ?, sid = ?, ver = ?, mike = ? WHERE vid = ? AND id = ? AND mode = ?; """ with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() cursor.execute(update_command, (stts, avatar, score, high_score, play_rslt, item, device_os, os_ver, sid, ver, mike, vid, id, mode)) cursor.close() connection.commit() with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() select_command = """ SELECT rid, score FROM result WHERE id = ? and mode = ? ORDER BY CAST(score AS INTEGER) DESC; """ cursor.execute(select_command, (id, mode)) records = cursor.fetchall() cursor.close() rank = None for idx, record in enumerate(records, start=1): if record[0] == last_row_id: rank = idx break after_element = root.find('.//after') after_element.text = str(rank) xml = ET.tostring(tree.getroot(), encoding='unicode') return xml @app.route('/ttag.php', methods=['GET']) def ttag(): global decrypted_fields global original_field user = get_user_data(decrypted_fields, "username") if user: with open("files/profile.html", "r") as file: html_content = file.read().format(pid=original_field, user=user[0]) else: with open("files/register.html", "r") as file: html_content = file.read().format(pid=original_field) return html_content, 200 @app.route('/mission.php/', methods=['GET']) def mission(): return inform_page("This feature is not available in Private Server.", 1) @app.route('/status.php/', methods=['GET']) def status(): return inform_page("This feature is not available in Private Server.", 1) @app.route('/ranking.php/', methods=['GET']) def ranking(): return inform_page("This feature is not available in Private Server.", 1) @app.route('/web_shop.php', methods=['GET']) def web_shop(): global decrypted_fields should_serve = True if app.config['AUTHORIZATION_NEEDED']: should_serve = check_whitelist(decrypted_fields) if should_serve: should_serve = check_blacklist(decrypted_fields) if should_serve: cnt_type = decrypted_fields[b'cnt_type'][0].decode() device_id = decrypted_fields[b'vid'][0].decode() inc = 0 buttons_html = "" # Define the path for the HTML template html_path = f"files/web_shop_{cnt_type}.html" with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() query = "SELECT my_stage, my_avatar, coin FROM daily_reward WHERE device_id = ?" cursor.execute(query, (device_id,)) result = cursor.fetchone() cursor.close() # Parse my_stage field (if exists), default to an empty set my_stage = set(json.loads(result[0])) if result[0] else start_stages my_avatar = set(json.loads(result[1])) if result[1] else start_avatars coin = result[2] if result[2] else 0 if (cnt_type == "1"): for idx, i in enumerate(range(90, 616)): if i not in my_stage: if i not in exclude_stage_exp: # Add a button for this stage buttons_html += f""" """ # Add a new row every 4 buttons inc += 1 if inc % 4 == 0: buttons_html += "
" elif (cnt_type == "2"): for idx, i in enumerate(range(15, 173)): if i not in my_avatar: if i not in exclude_avatar_exp: buttons_html += f""" """ # Add a new row every 4 buttons inc += 1 if inc % 4 == 0: buttons_html += "
" elif (cnt_type == "3"): inc = 1 for idx, i in enumerate(range(1, 11)): buttons_html += f""" """ # Add a new row every 4 buttons if (idx + 1) % 4 == 0: buttons_html += "
" if (inc == 0): buttons_html += f"""
Everything has been purchased!
""" # Read and format the HTML template with open(html_path, "r", encoding="utf-8") as file: html_content = file.read().format(text=buttons_html, coin=coin) return html_content else: return jsonify({"error": "Access denied"}), 403 @app.route('/web_shop_detail.php', methods=['GET']) def web_shop_detail(): global decrypted_fields cnt_type = decrypted_fields[b'cnt_type'][0].decode() cnt_id = int(decrypted_fields[b'cnt_id'][0].decode()) device_id = decrypted_fields[b'vid'][0].decode() with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() query = "SELECT coin FROM daily_reward WHERE device_id = ?" cursor.execute(query, (device_id,)) result = cursor.fetchone() cursor.close() coin = result[0] if result and result[0] else 0 your_html = "" if (cnt_type == "1"): your_html = f"""
Item Image

Would you like to purchase this song?{cnt_id}

Coin Icon {stage_price}
""" elif (cnt_type == "2"): your_html = f"""
Item Image

Would you like to purchase this avatar?

Coin Icon {avatar_price}
""" elif (cnt_type == "3"): your_html = f"""
Item Image

Would you like to purchase this item?

Coin Icon {item_price}
""" your_html += f"""
""" html_path = f"files/web_shop_detail.html" with open(html_path, "r", encoding="utf-8") as file: html_content = file.read().format(text=your_html, coin=coin) return html_content @app.route('/buy_by_coin.php', methods=['GET']) def buy_by_coin(): global decrypted_fields cnt_type = decrypted_fields[b'cnt_type'][0].decode() cnt_id = int(decrypted_fields[b'cnt_id'][0].decode()) num = int(decrypted_fields[b'num'][0].decode()) device_id = decrypted_fields[b'vid'][0].decode() fail_url = """1coin_error.php""" with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() # Fetch user data query = "SELECT my_stage, my_avatar, coin, item FROM daily_reward WHERE device_id = ?" cursor.execute(query, (device_id,)) result = cursor.fetchone() if not result: return fail_url my_stage, my_avatar, coin, item = result coin = int(coin) if coin else 0 # Process based on cnt_type if cnt_type == "1": if coin < stage_price: return fail_url stages = set(json.loads(my_stage)) if my_stage else set() if cnt_id not in stages: coin -= stage_price stages.add(cnt_id) else: return fail_url my_stage = json.dumps(list(stages)) elif cnt_type == "2": if coin < avatar_price: return fail_url avatars = set(json.loads(my_avatar)) if my_avatar else set() if cnt_id not in avatars: coin -= avatar_price avatars.add(cnt_id) else: return fail_url my_avatar = json.dumps(list(avatars)) elif cnt_type == "3": if coin < item_price: return fail_url # Deduct coin and append to item (no duplicate check) coin -= item_price items = json.loads(item) if item else [] items.append(cnt_id) item = json.dumps(items) else: return fail_url # Update the database with the new values update_query = """ UPDATE daily_reward SET my_stage = ?, my_avatar = ?, coin = ?, item = ? WHERE device_id = ? """ cursor.execute(update_query, (my_stage, my_avatar, coin, item, device_id)) connection.commit() response = ET.Element("response") # Add the main elements ET.SubElement(response, "code").text = "0" ET.SubElement(response, "result_url").text = "web_shop_result.php" ET.SubElement(response, "cnt_type").text = cnt_type ET.SubElement(response, "cnt_id").text = str(cnt_id) ET.SubElement(response, "num").text = str(num) # Conditionally add the stage_id if cnt_type == "1": ET.SubElement(response, "stage_id").text = str(cnt_id) # Convert the XML tree to a string response_string = ET.tostring(response, encoding="utf-8", method="xml").decode("utf-8") return response_string @app.route('/web_shop_result.php', methods=['GET']) def web_shop_result(): return inform_page("SUCCESS:
Purchase successful.
Please close this page and the reward will arrive shortly.
If it took too long, try restarting the game.", 2) @app.route('/coin_error.php', methods=['GET']) def coin_error(): return inform_page("FAILED:
Either you don't have enough coin,
or there were a duplicate order, and the reward will arrive shortly.", 2) @app.route('/name_reset/', methods=['POST']) def name_reset(): global decrypted_fields username = request.form['username'] password = request.form['password'] if len(username) < 6 or len(username) > 21: return inform_page("FAILED:
Username must be between 6 and 20
characters long.", 0) if is_alphanumeric(username) == False: return inform_page("FAILED:
Username must consist entirely of
alphanumeric character.", 0) if username == password: return inform_page("FAILED:
Username cannot be the same as password.", 0) if check_blacklist(decrypted_fields) == False: return inform_page("FAILED:
Your account is banned and you are
not allowed to perform this action.", 0) user = get_user_data(decrypted_fields, "username") if user: user = user[0] with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() cursor.execute("SELECT id FROM user WHERE username=?", (username,)) exist = cursor.fetchone() cursor.close() if exist: return inform_page("FAILED:
Another user already has this name.", 0) password_hash = get_user_data(decrypted_fields, "password_hash") if password_hash: if verify_password(password, password_hash[0]): set_user_data(decrypted_fields, "username", username) return inform_page("SUCCESS:
Username updated.", 0) else: return inform_page("FAILED:
Password is not correct.
Please try again.", 0) else: return inform_page("FAILED:
User have no password hash.
This should not happen.", 0) else: return inform_page("FAILED:
User does not exist.
This should not happen.", 0) @app.route('/password_reset/', methods=['POST']) def password_reset(): global decrypted_fields old_password = request.form['old'] password = request.form['new'] user = get_user_data(decrypted_fields, "username") if user: user = user[0] if user == password: return inform_page("FAILED:
Username cannot be the same as password.", 0) if len(password) < 6: return inform_page("FAILED:
Password must have 6 or above characters.", 0) old_hash = get_user_data(decrypted_fields, "password_hash") if old_hash: if verify_password(old_password, old_hash[0]): hash_new = hash_password(password) set_user_data(decrypted_fields, "password_hash", hash_new) return inform_page("SUCCESS:
Password updated.", 0) else: return inform_page("FAILED:
Old password is not correct
Please try again.", 0) else: return inform_page("FAILED:
User have no password hash
This should not happen.", 0) else: return inform_page("FAILED:
User does not exist
This should not happen.", 0) @app.route('/register/', methods=['POST']) def register(): global decrypted_fields username = request.form['username'] password = request.form['password'] if username == password: return inform_page("FAILED:
Username cannot be the same as password.", 0) if len(username) < 6 or len(username) > 20: return inform_page("FAILED:
Username must be between 6 and 20
characters long.", 0) if len(password) < 6: return inform_page("FAILED:
Password must have
6 or above characters.", 0) if is_alphanumeric(username) == False: return inform_page("FAILED:
Username must consist entirely of
alphanumeric character.", 0) with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() cursor.execute("SELECT id FROM user WHERE username=?", (username,)) user = cursor.fetchone() cursor.close() if user: return inform_page("FAILED:
Another user already has this name.", 0) cursor = connection.cursor() cursor.execute('''INSERT INTO user (username, password_hash, device_id, data, crc, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)''', (username, hash_password(password), decrypted_fields[b'vid'][0].decode(), "", 0, "")) connection.commit() cursor.close() return inform_page("SUCCESS:
Account is registered.
You can now backup/restore your save file.
You can only log into one device at a time.", 0) @app.route('/logout/', methods=['POST']) def logout(): global decrypted_fields if check_blacklist(decrypted_fields) == False: return inform_page("FAILED:
Your account is banned and you are
not allowed to perform this action.", 0) set_user_data(decrypted_fields, "device_id", '') return inform_page("Logout success.", 0) @app.route('/login/', methods=['POST']) def login(): global decrypted_fields username = request.form['username'] password = request.form['password'] with sqlite3.connect(DATABASE) as connection: cursor = connection.cursor() cursor.execute("SELECT id FROM user WHERE username = ?", (username,)) user = cursor.fetchone() ##side channel attack goes brrrrr if user: cursor = connection.cursor() cursor.execute("SELECT password_hash FROM user WHERE id = ?", (user[0],)) password_hash = cursor.fetchone()[0] if verify_password(password, password_hash): cursor.execute("UPDATE user SET device_id = ? WHERE id = ?", (decrypted_fields[b'vid'][0].decode(), user[0],)) connection.commit() cursor.close() return inform_page("SUCCESS:
You are logged in.", 0) else: return inform_page("FAILED:
Username or password incorrect.", 0) else: return inform_page("FAILED:
Username or password incorrect.", 0) @app.route('/load.php', methods=['GET']) def load(): global decrypted_fields data = get_user_data(decrypted_fields, "data") if data is not None: if data[0] == "": return """12セーブデータが無いか、セーブデータが破損しているため、ロードできませんでした。Unable to load; either no save data exists, or the save data is corrupted.Chargement impossible : les données de sauvegarde sont absentes ou corrompues.Impossibile caricare. Non esistono dati salvati o quelli esistenti sono danneggiati.""" else: crc = get_user_data(decrypted_fields, "crc") date = get_user_data(decrypted_fields, "timestamp") data = data[0] date = date[0] crc = crc[0] xml_data = """0 {data} {crc} {date} """.format(data=data,crc=crc,date=date) return xml_data else: return """10この機能を使用するには、まずアカウントを登録する必要があります。You need to register an account first before this feature can be used.Vous devez d'abord créer un compte avant de pouvoir utiliser cette fonctionnalité.È necessario registrare un account prima di poter utilizzare questa funzione.""" @app.route('/save.php', methods=['POST']) def save(): global decrypted_fields data = request.data.decode('utf-8') with sqlite3.connect(DATABASE) as connection: username = get_user_data(decrypted_fields, "username") if username: ## calculate crc and update both crc = crc32_decimal(data) time = datetime.now() formatted_time = time.strftime("%Y-%m-%d %H:%M:%S") cursor = connection.cursor() cursor.execute("UPDATE user SET data = ?, crc = ?, timestamp = ? WHERE device_id = ?", (data, crc, formatted_time , decrypted_fields[b'vid'][0].decode(),)) connection.commit() cursor.close() return """0""" else: return """10""" @app.route('/delete_account.php', methods=['GET']) def delete_account(): return """0""" @app.route('/', methods=['GET']) def get_file(path): file_path = os.path.join(root_folder, path) f_path = path.split("/")[0] if f_path not in allowed_folders: return jsonify({"error": "Access denied"}), 403 if os.path.exists(file_path): return send_file(file_path) else: return jsonify({"error": "File not found"}), 404 #Main if __name__ == '__main__': if app.config['SSL_CERT'] and app.config['SSL_KEY']: app.run(host=app.config['HOST'], port=app.config['PORT'], debug=app.config['DEBUG'], ssl_context=(app.config['SSL_CERT'], app.config['SSL_KEY'])) else: app.run(host=app.config['HOST'], port=app.config['PORT'], debug=app.config['DEBUG'])