Files
Groove_Coaster_2_Server/7001.py
2025-04-28 18:44:53 +08:00

1841 lines
84 KiB
Python

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
import requests
try:
with open("song_list.json", 'r', encoding="utf-8") as file:
song_list = json.load(file)
except Exception as e:
print(f"An unexpected error occurred when loading song_list.json: {e}")
try:
with open("avatar_list.json", 'r', encoding="utf-8") as file:
avatar_list = json.load(file)
except Exception as e:
print(f"An unexpected error occurred when loading avatar_list.json: {e}")
try:
with open("item_list.json", 'r', encoding="utf-8") as file:
item_list = json.load(file)
except Exception as e:
print(f"An unexpected error occurred when loading item_list.json: {e}")
# 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 = [7,23,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,88,89,90,91,92,93,94,95,96,97,98,99,214]
# 214 is tutorial song.
start_avatars = []
exclude_stage_exp = [121,134,166,167,168,169,170,213,214,215,225,277] # 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]
try:
with open("exp_unlocked_songs.json", 'r', encoding="utf-8") as file:
exp_unlocked_songs = json.load(file)
except Exception as e:
print(f"An unexpected error occurred when loading exp_unlocked_songs.json: {e}")
special_titles = [1, 2, 4431, 4432, 4601, 4602, 4611, 4612, 4621, 4622, 4631, 4632, 5111, 5112, 5121, 5122, 5131, 5132, 10001, 10002, 20001, 20002, 20003, 20004, 20005, 20006, 30001, 30002, 40001, 40002, 50001, 50002, 60001, 60002, 70001, 70002, 80001, 80002, 90001, 90002, 100001, 100002, 110001, 110002, 120001, 120002, 130001, 130002, 140001, 140002, 140003, 140004, 150001, 150002, 150003, 150004, 160001, 160002, 160003, 160004, 170001, 170002, 170003, 170004, 180001, 180002, 180003, 180004, 190001, 190002, 190003, 190004, 200001, 200002, 200003, 200004, 210001, 210002, 210003, 210004, 210005, 210006, 210007, 210008, 210009, 210010, 210011, 210012, 210013, 210014, 240001, 240002, 240003, 240004, 240005, 240006, 240007, 240008, 240009, 240010, 240011, 240012]
god_titles = [220001, 220002, 220003, 220004, 220005, 220006, 220007, 220008, 220009, 220010, 220011, 220012, 220013, 220014, 220015, 220016, 220017, 220018, 220019, 220020, 220021, 220022, 220023, 220024, 220025, 220026, 220027, 220028, 220029, 220030, 220031, 220032, 220033, 220034, 220035, 220036, 220037, 220038, 220039, 220040, 220041, 220042, 220043, 220044, 220045, 220046, 220047, 220048, 220049, 220050, 220051, 220052, 220053, 220054, 220055, 220056, 220057, 220058, 220059, 220060, 220061, 220062, 220063, 220064, 220065, 220066, 220067, 220068, 220069, 220070, 220071, 220072, 220073, 220074, 220075, 220076, 220077, 220078, 220079, 220080, 220081, 220082, 220083, 220084, 220085, 220086, 220087, 220088, 220089, 220090, 220091, 220092, 220093, 220094, 220095, 220096, 220097, 220098, 220099, 220100, 220101, 220102]
master_titles = [12, 22, 32, 42, 52, 62, 72, 82, 92, 102, 112, 122, 132, 142, 152, 162, 172, 182, 192, 202, 212, 222, 232, 242, 252, 262, 272, 282, 292, 302, 312, 322, 332, 342, 352, 362, 372, 382, 392, 402, 412, 422, 432, 442, 452, 462, 472, 482, 492, 502, 512, 522, 532, 542, 552, 562, 572, 582, 592, 602, 612, 622, 632, 642, 652, 662, 672, 682, 692, 702, 712, 722, 732, 742, 752, 762, 772, 782, 792, 802, 812, 822, 832, 842, 852, 862, 872, 882, 892, 902, 912, 922, 972, 982, 992, 1002, 1012, 1022, 1032, 1042, 1052, 1062, 1072, 1082, 1092, 1102, 1112, 1122, 1132, 1142, 1152, 1162, 1172, 1182, 1192, 1202, 1222, 1232, 1242, 1252, 1262, 1272, 1282, 1292, 1302, 1312, 1322, 1332, 1342, 1352, 1362, 1372, 1382, 1392, 1402, 1412, 1422, 1432, 1442, 1452, 1462, 1472, 1482, 1492, 1502, 1512, 1522, 1532, 1542, 1552, 1562, 1572, 1582, 1592, 1602, 1612, 1622, 1632, 1642, 1652, 1662, 1672, 1682, 1692, 1702, 1712, 1722, 1732, 1742, 1752, 1762, 1772, 1782, 1792, 1802, 1812, 1822, 1832, 1842, 1852, 1862, 1872, 1882, 1892, 1902, 1912, 1922, 1932, 1942, 1952, 1962, 1972, 1982, 1992, 2002, 2012, 2022, 2032, 2042, 2052, 2062, 2072, 2082, 2092, 2102, 2112, 2122, 2132, 2152, 2162, 2172, 2182, 2192, 2202, 2212, 2222, 2232, 2242, 2252, 2262, 2272, 2282, 2292, 2302, 2312, 2322, 2332, 2342, 2352, 2362, 2372, 2382, 2392, 2402, 2412, 2422, 2432, 2442, 2452, 2462, 2472, 2482, 2492, 2502, 2512, 2522, 2532, 2542, 2552, 2562, 2572, 2582, 2592, 2602, 2612, 2622, 2632, 2642, 2652, 2662, 2672, 2682, 2692, 2702, 2712, 2722, 2732, 2742, 2752, 2762, 2782, 2792, 2802, 2812, 2822, 2832, 2842, 2852, 2862, 2872, 2882, 2892, 2902, 2912, 2922, 2932, 2942, 2952, 2962, 2972, 2982, 2992, 3002, 3012, 3022, 3032, 3042, 3052, 3062, 3072, 3082, 3092, 3102, 3112, 3122, 3132, 3142, 3152, 3162, 3172, 3182, 3192, 3202, 3212, 3222, 3232, 3242, 3252, 3262, 3272, 3282, 3292, 3302, 3312, 3322, 3332, 3342, 3352, 3362, 3372, 3382, 3392, 3402, 3412, 3422, 3432, 3442, 3452, 3462, 3472, 3482, 3492, 3502, 3512, 3522, 3532, 3542, 3552, 3562, 3572, 3582, 3592, 3602, 3612, 3622, 3632, 3642, 3652, 3662, 3672, 3682, 3692, 3702, 3712, 3722, 3732, 3742, 3752, 3762, 3772, 3782, 3792, 3802, 3812, 3822, 3832, 3842, 3852, 3862, 3872, 3882, 3892, 3902, 3912, 3922, 3932, 3942, 3952, 3962, 3982, 3992, 4002, 4012, 4022, 4032, 4042, 4052, 4062, 4072, 4082, 4092, 4102, 4112, 4122, 4132, 4142, 4152, 4162, 4172, 4182, 4192, 4202, 4212, 4222, 4232, 4242, 4252, 4262, 4272, 4282, 4292, 4302, 4312, 4322, 4332, 4342, 4352, 4362, 4372, 4382, 4392, 4402, 4412, 4422, 4442, 4452, 4462, 4472, 4482, 4492, 4502, 4512, 4522, 4532, 4542, 4552, 4562, 4572, 4582, 4592, 4642, 4652, 4662, 4672, 4682, 4692, 4702, 4712, 4722, 4732, 4742, 4752, 4762, 4772, 4782, 4792, 4802, 4812, 4822, 4832, 4842, 4862, 4872, 4882, 4892, 4902, 4912, 4922, 4932, 4942, 4952, 4962, 4972, 4982, 4992, 5002, 5012, 5022, 5032, 5042, 5052, 5062, 5072, 5082, 5092, 5102, 5142, 5152, 5162, 5172, 5182, 5192, 5202, 5212, 5222, 5232, 5242, 5252, 5262, 5272, 5282, 5292, 5302, 5312, 5322, 5332, 5342, 5352, 5362, 5372, 5382, 5392, 5402, 5412, 5422, 5432, 5442, 5452, 5462, 5472, 5482, 5492, 5502, 5512, 5522, 5532, 5542, 5552, 5562, 5572, 5582, 5592, 5602, 5612, 5622, 5632, 5642, 5652, 5662, 5672, 5682, 5692, 5702, 5712, 5722, 5732, 5742, 5752, 5762, 5772, 5782, 5792, 5802, 5812, 5822, 5832, 5842, 5852, 5862, 5872, 5882, 5892, 5902, 5912, 5922, 5932, 5942, 5952, 5962, 5972, 5982, 5992, 6002, 6012, 6022, 6032, 6042, 6052, 6062, 6072, 6082, 6092, 6102, 6112, 6122, 6132, 6142, 6152]
normal_titles = [11, 21, 31, 41, 51, 61, 71, 81, 91, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191, 201, 211, 221, 231, 241, 251, 261, 271, 281, 291, 301, 311, 321, 331, 341, 351, 361, 371, 381, 391, 401, 411, 421, 431, 441, 451, 461, 471, 481, 491, 501, 511, 521, 531, 541, 551, 561, 571, 581, 591, 601, 611, 621, 631, 641, 651, 661, 671, 681, 691, 701, 711, 721, 731, 741, 751, 761, 771, 781, 791, 801, 811, 821, 831, 841, 851, 861, 871, 881, 891, 901, 911, 921, 971, 981, 991, 1001, 1011, 1021, 1031, 1041, 1051, 1061, 1071, 1081, 1091, 1101, 1111, 1121, 1131, 1141, 1151, 1161, 1171, 1181, 1191, 1201, 1221, 1231, 1241, 1251, 1261, 1271, 1281, 1291, 1301, 1311, 1321, 1331, 1341, 1351, 1361, 1371, 1381, 1391, 1401, 1411, 1421, 1431, 1441, 1451, 1461, 1471, 1481, 1491, 1501, 1511, 1521, 1531, 1541, 1551, 1561, 1571, 1581, 1591, 1601, 1611, 1621, 1631, 1641, 1651, 1661, 1671, 1681, 1691, 1701, 1711, 1721, 1731, 1741, 1751, 1761, 1771, 1781, 1791, 1801, 1811, 1821, 1831, 1841, 1851, 1861, 1871, 1881, 1891, 1901, 1911, 1921, 1931, 1941, 1951, 1961, 1971, 1981, 1991, 2001, 2011, 2021, 2031, 2041, 2051, 2061, 2071, 2081, 2091, 2101, 2111, 2121, 2131, 2151, 2161, 2171, 2181, 2191, 2201, 2211, 2221, 2231, 2241, 2251, 2261, 2271, 2281, 2291, 2301, 2311, 2321, 2331, 2341, 2351, 2361, 2371, 2381, 2391, 2401, 2411, 2421, 2431, 2441, 2451, 2461, 2471, 2481, 2491, 2501, 2511, 2521, 2531, 2541, 2551, 2561, 2571, 2581, 2591, 2601, 2611, 2621, 2631, 2641, 2651, 2661, 2671, 2681, 2691, 2701, 2711, 2721, 2731, 2741, 2751, 2761, 2781, 2791, 2801, 2811, 2821, 2831, 2841, 2851, 2861, 2871, 2881, 2891, 2901, 2911, 2921, 2931, 2941, 2951, 2961, 2971, 2981, 2991, 3001, 3011, 3021, 3031, 3041, 3051, 3061, 3071, 3081, 3091, 3101, 3111, 3121, 3131, 3141, 3151, 3161, 3171, 3181, 3191, 3201, 3211, 3221, 3231, 3241, 3251, 3261, 3271, 3281, 3291, 3301, 3311, 3321, 3331, 3341, 3351, 3361, 3371, 3381, 3391, 3401, 3411, 3421, 3431, 3441, 3451, 3461, 3471, 3481, 3491, 3501, 3511, 3521, 3531, 3541, 3551, 3561, 3571, 3581, 3591, 3601, 3611, 3621, 3631, 3641, 3651, 3661, 3671, 3681, 3691, 3701, 3711, 3721, 3731, 3741, 3751, 3761, 3771, 3781, 3791, 3801, 3811, 3821, 3831, 3841, 3851, 3861, 3871, 3881, 3891, 3901, 3911, 3921, 3931, 3941, 3951, 3961, 3981, 3991, 4001, 4011, 4021, 4031, 4041, 4051, 4061, 4071, 4081, 4091, 4101, 4111, 4121, 4131, 4141, 4151, 4161, 4171, 4181, 4191, 4201, 4211, 4221, 4231, 4241, 4251, 4261, 4271, 4281, 4291, 4301, 4311, 4321, 4331, 4341, 4351, 4361, 4371, 4381, 4391, 4401, 4411, 4421, 4441, 4451, 4461, 4471, 4481, 4491, 4501, 4511, 4521, 4531, 4541, 4551, 4561, 4571, 4581, 4591, 4641, 4651, 4661, 4671, 4681, 4691, 4701, 4711, 4721, 4731, 4741, 4751, 4761, 4771, 4781, 4791, 4801, 4811, 4821, 4831, 4841, 4861, 4871, 4881, 4891, 4901, 4911, 4921, 4931, 4941, 4951, 4961, 4971, 4981, 4991, 5001, 5011, 5021, 5031, 5041, 5051, 5061, 5071, 5081, 5091, 5101, 5141, 5151, 5161, 5171, 5181, 5191, 5201, 5211, 5221, 5231, 5241, 5251, 5261, 5271, 5281, 5291, 5301, 5311, 5321, 5331, 5341, 5351, 5361, 5371, 5381, 5391, 5401, 5411, 5421, 5431, 5441, 5451, 5461, 5471, 5481, 5491, 5501, 5511, 5521, 5531, 5541, 5551, 5561, 5571, 5581, 5591, 5601, 5611, 5621, 5631, 5641, 5651, 5661, 5671, 5681, 5691, 5701, 5711, 5721, 5731, 5741, 5751, 5761, 5771, 5781, 5791, 5801, 5811, 5821, 5831, 5841, 5851, 5861, 5871, 5881, 5891, 5901, 5911, 5921, 5931, 5941, 5951, 5961, 5971, 5981, 5991, 6001, 6011, 6021, 6031, 6041, 6051, 6061, 6071, 6081, 6091, 6101, 6111, 6121, 6131, 6141, 6151]
title_lists = {
0: special_titles,
1: normal_titles,
2: master_titles,
3: god_titles,
}
fmax_res = []
fmax_ver = 0
def get_4max_version_string():
url = "https://studio.code.org/v3/sources/3-aKHy16Y5XaAPXQHI95RnFOKlyYT2O95ia2HN2jKIs/main.json"
global fmax_ver
try:
with open("./files/4max_ver.txt", 'r') as file:
fmax_ver = file.read().strip()
except Exception as e:
print(f"An unexpected error occurred when loading files/4max_ver.txt: {e}")
def fetch():
global fmax_res
try:
response = requests.get(url)
if 200 <= response.status_code <= 207:
try:
fmax_res = json.loads(json.loads(response.text)['source'])
except (json.JSONDecodeError, KeyError):
fmax_res = 500
else:
fmax_res = response.status_code
except requests.RequestException:
fmax_res = 400
fetch()
if (os.path.isfile('./files/dlc_4max.html')):
get_4max_version_string()
def parse_res(res):
parsed_data = []
if isinstance(res, int):
return "Failed to fetch version info: Error " + str(res)
for item in res:
if item.get("isOpen"):
version = item.get("version", "Unknown Version")
changelog = "<br>".join(item.get("changeLog", {}).get("en", []))
parsed_data.append(f"<strong>Version: {version}</strong><p><strong>Changelog:</strong><br>{changelog}</p>")
return "".join(parsed_data)
app = Flask(__name__)
app.config.from_object(Config)
stage_price = app.config['STAGE_PRICE']
avatar_price = app.config['AVATAR_PRICE']
item_price = app.config['ITEM_PRICE']
coin_reward = app.config['COIN_REWARD']
start_coin = app.config['START_COIN']
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,
item TEXT,
day INT,
coin INT,
lvl INT,
title INT,
avatar 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.encode('utf-8'))
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"
elif mode == 3:
mode = "/files/web/ttl_title.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 <daily_reward> element in XML"}), 500
# Parse last_count from <daily_reward>
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
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 start_coin
else:
my_avatar = start_avatars
my_stage = start_stages
coin = start_coin
coin_elem = ET.Element("my_coin")
coin_elem.text = str(coin)
root.append(coin_elem)
for avatar_id in my_avatar:
avatar_elem = ET.Element("my_avatar")
avatar_elem.text = str(avatar_id)
root.append(avatar_elem)
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
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.get('AUTHORIZATION_NEEDED', False):
should_serve = check_whitelist(decrypted_fields) and 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 start_coin
items = json.loads(result[3]) if result[3] else []
else:
my_avatar = start_avatars
my_stage = start_stages
coin = start_coin
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()
for avatar_id in my_avatar:
avatar_elem = ET.Element("my_avatar")
avatar_elem.text = str(avatar_id)
root.append(avatar_elem)
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, my_avatar, my_stage 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 is not None:
current_day, last_timestamp, my_avatar, my_stage = 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
reward_elem = daily_reward_elem.find(f".//reward[count='{current_day}']")
if reward_elem is not None:
cnt_type = int(reward_elem.find("cnt_type").text)
cnt_id = int(reward_elem.find("cnt_id").text)
if cnt_type == 1:
stages = set(json.loads(my_stage)) if my_stage else set()
if cnt_id not in stages:
stages.add(cnt_id)
my_stage = json.dumps(list(stages))
elif cnt_type == 2:
avatars = set(json.loads(my_avatar)) if my_avatar else set()
if cnt_id not in avatars:
avatars.add(cnt_id)
my_avatar = json.dumps(list(avatars))
# Update the table
cursor.execute("""UPDATE daily_reward SET timestamp = ?, day = ?, my_avatar = ?, my_stage = ? WHERE device_id = ?""", (formatted_time, current_day, my_avatar, my_stage, device_id))
# return 0 obj
xml_response = "<response><code>0</code></response>"
else:
now_count = current_day
xml_response = "<response><code>1</code></response>"
# return 1 obj
else:
# No row exists; create a new one
print("bonus-add profile")
cursor.execute("""
INSERT INTO daily_reward (device_id, day, timestamp, my_avatar, my_stage, coin, item, lvl, title, avatar)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (device_id, 1, formatted_time, json.dumps(start_avatars), json.dumps(start_stages), start_coin, "[]", 1, 1, 1))
xml_response = "<response><code>0</code></response>"
# 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():
should_serve = True
global decrypted_fields
if app.config.get('AUTHORIZATION_NEEDED', False):
should_serve = check_whitelist(decrypted_fields) and check_blacklist(decrypted_fields)
if should_serve:
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()
# 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
print(int(id), int(mode))
if (int(id) not in range(616, 1024) or int(mode) not in range(0, 4)):
# Increment coin for user, if track is not 4max dlx pack mobile difficulty.
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 start_coin
updated_coin = current_coin + coin_reward
cursor.execute("UPDATE daily_reward SET coin = ? WHERE device_id = ?", (updated_coin, device_id))
#Try to add play record to result table (don't add if playing placeholder songs)
#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:
last_row_id = records[0][0]
if (score > records[0][1]):
do_update_sid = True
else:
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:
last_row_id = records[0][0]
if (score > records[0][1]):
do_update_vid = True
else:
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()
# Update player profile regardless. Check for exp unlocked songs too
with sqlite3.connect(DATABASE) as connection:
cursor = connection.cursor()
query_command = "SELECT my_stage FROM daily_reward WHERE device_id = ?"
cursor.execute(query_command, (device_id,))
result = cursor.fetchone()
my_stage = set(json.loads(result[0])) if result and result[0] else set(start_stages)
current_exp = int(stts.split(",")[0])
for song in exp_unlocked_songs:
if song["lvl"] <= current_exp:
my_stage.add(song["id"])
my_stage = sorted(my_stage)
update_command = """
UPDATE daily_reward SET lvl = ?, avatar = ?, my_stage = ? WHERE device_id = ?;
"""
cursor.execute(update_command, (current_exp, int(avatar), json.dumps(my_stage), device_id))
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
else:
return jsonify({"error": "Access denied"}), 403
@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('/web_shop.php', methods=['GET', 'POST'])
def web_shop():
should_serve = True
global decrypted_fields
if app.config.get('AUTHORIZATION_NEEDED', False):
should_serve = check_whitelist(decrypted_fields) and 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
fmax_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"):
if (700 not in my_stage and os.path.isfile('./files/dlc_4max.html')):
buttons_html += """
<a href="wwic://web_shop_detail?&cnt_type=1&cnt_id=-1">
<img src="/files/web/dlc_4max.jpg" style="width: 84%; margin-bottom: 20px; margin-top: -100px;" />
</a><br>
"""
fmax_inc = 1
elif (700 in my_stage and os.path.isfile('./files/dlc_4max.html')):
buttons_html += """
<a href="wwic://web_shop_detail?&cnt_type=1&cnt_id=-2">
<img src="/files/web/dlc_4max.jpg" style="width: 84%; margin-bottom: 20px; margin-top: -100px;" />
</a><br>
"""
for idx, i in enumerate(range(100, 616)):
if i not in my_stage:
if i not in exclude_stage_exp:
# Add a button for this stage
buttons_html += f"""
<button style="width: 180px; height: 180px; margin: 10px; background-size: cover; background-image: url('/files/image/icon/shop/{i}.jpg');"
onclick="window.location.href='wwic://web_shop_detail?&cnt_type={cnt_type}&cnt_id={i}'">
</button>
"""
inc += 1
if inc % 4 == 0:
buttons_html += "<br>"
elif (cnt_type == "2"):
up_range = 173
if (fmax_ver != 0):
up_range = 267
for idx, i in enumerate(range(15, up_range)):
if i not in my_avatar:
if i not in exclude_avatar_exp:
buttons_html += f"""
<button style="width: 180px; height: 180px; margin: 10px; background-color: black; background-size: contain; background-repeat: no-repeat; background-position: center center; background-image: url('/files/image/icon/avatar/{i}.png');"
onclick="window.location.href='wwic://web_shop_detail?&cnt_type={cnt_type}&cnt_id={i}'">
</button>
"""
inc += 1
if inc % 4 == 0:
buttons_html += "<br>"
elif (cnt_type == "3"):
inc = 1
for idx, i in enumerate(range(1, 11)):
buttons_html += f"""
<button style="width: 180px; height: 180px; margin: 10px; background-size: cover; background-image: url('/files/image/icon/item/{i}.png');"
onclick="window.location.href='wwic://web_shop_detail?&cnt_type={cnt_type}&cnt_id={i}'">
</button>
"""
if (idx + 1) % 4 == 0:
buttons_html += "<br>"
if (inc == 0 and fmax_inc == 0):
buttons_html += f"""<div>Everything has been purchased!</div>"""
# 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', 'POST'])
def web_shop_detail():
should_serve = True
global decrypted_fields
if app.config.get('AUTHORIZATION_NEEDED', False):
should_serve = check_whitelist(decrypted_fields) and check_blacklist(decrypted_fields)
if should_serve:
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
html = ""
if (cnt_type == "1"):
if (cnt_id > -1):
song = song_list[cnt_id]
difficulty_levels = "/".join(map(str, song.get("difficulty_levels", [])))
song_stage_price = stage_price
if (len(song["difficulty_levels"]) == 6):
song_stage_price = stage_price * 2
html = f"""
<div class="image-container">
<img src="/files/image/icon/shop/{cnt_id}.jpg" alt="Item Image" style="width: 180px; height: 180px;" />
</div>
<p>Would you like to purchase this song?</p>
<div>
<p>{song.get("name_en")} - {song.get("author_en")}</p>
<p>Difficulty Levels: {difficulty_levels}</p>
</div>
<div>
<img src="/files/web/coin_icon.png" class="coin-icon" style="width: 40px; height: 40px;" alt="Coin Icon" />
<span style="color: #FFFFFF; font-size: 44px; font-family: Hiragino Kaku Gothic ProN, sans-serif;">{song_stage_price}</span>
</div>
"""
else:
if cnt_id == -2:
log = parse_res(fmax_res)
html = f"""
<div class="text-content">
<p>You have unlocked the GC4MAX expansion!</p>
<p>Please report bugs/missing tracks to Discord: #AnTcfgss, or QQ 3421587952.</p>
<button class="quit-button" onclick="window.location.href='wwic://web_shop?&cnt_type=1'">
Go Back
</button><br>
<strong>This server has version {fmax_ver}.</strong>
<p>Update log: </p>
<p>{log}<p><br>
</div>
"""
elif cnt_id == -1:
html = f"""
<div class="text-content">
<p>Experience the arcade with the GC4MAX expansion! This DLC unlocks 320+ exclusive songs for your 2OS experience.</p>
<p>Note that these songs don't have mobile difficulties. A short placeholder is used, and GCoin reward is not available for playing them. You must clear the Normal difficulty to unlock AC content.</p>
<p>Due to technical limitations, Extra level charts cannot be ported as of now. After purchasing, you will have access to support information and update logs.</p>
</div>
<button class="buy-button" onclick="window.location.href='wwic://web_purchase_coin?&cnt_type=1&cnt_id=-1&num=1'">
Buy
<div class="coin-container">
<img src="/files/web/coin_icon.png" alt="Coin Icon" class="coin-icon">
<span style="font-size: 22px; font-weight: bold;"> 300</span>
</div>
</button>
<br><br>
<button class="quit-button" onclick="window.location.href='wwic://web_shop?&cnt_type=1'">
Go Back
</button>
"""
elif (cnt_type == "2"):
avatar = next((item for item in avatar_list if item.get("id") == cnt_id), None)
if avatar:
html = f"""
<div class="image-container">
<img src="/files/image/icon/avatar/{cnt_id}.png" alt="Item Image" style="width: 180px; height: 180px; background-color: black; object-fit: contain;" />
</div>
<p>Would you like to purchase this avatar?</p>
<div>
<p>{avatar.get("name")}</p>
<p>Effect: {avatar.get("effect")}</p>
</div>
<div>
<img src="/files/web/coin_icon.png" class="coin-icon" style="width: 40px; height: 40px;" alt="Coin Icon" />
<span>{avatar_price}</span>
</div>
"""
else:
html = "<p>Avatar not found.</p>"
elif (cnt_type == "3"):
item = next((item for item in item_list if item.get("id") == cnt_id), None)
if item:
html = f"""
<div class="image-container">
<img src="/files/image/icon/item/{cnt_id}.png" alt="Item Image" style="width: 180px; height: 180px;" />
</div>
<p>Would you like to purchase this item?</p>
<div>
<p>{item.get("name")}</p>
<p>Effect: {item.get("effect")}</p>
</div>
<div>
<img src="/files/web/coin_icon.png" class="coin-icon" style="width: 40px; height: 40px;" alt="Coin Icon" />
<span>{item_price}</span>
</div>
"""
else:
html = "<p>Item not found.</p>"
if (cnt_type == "1" and cnt_id < 0):
source_html = f"files/dlc_4max.html"
else:
source_html = f"files/web_shop_detail.html"
html += f"""
<br>
<div class="buttons" style="margin-top: 20px;">
<a href="wwic://web_purchase_coin?cnt_type={cnt_type}&cnt_id={cnt_id}&num=1" class="bt_bg01" >Buy</a><br>
<a href="wwic://web_shop?cnt_type={cnt_type}" class="bt_bg01" >Go Back</a>
</div>
"""
with open(source_html, "r", encoding="utf-8") as file:
html_content = file.read().format(text=html, coin=coin)
return html_content
else:
return jsonify({"error": "Access denied"}), 403
@app.route('/buy_by_coin.php', methods=['GET'])
def buy_by_coin():
should_serve = True
global decrypted_fields
if app.config.get('AUTHORIZATION_NEEDED', False):
should_serve = check_whitelist(decrypted_fields) and check_blacklist(decrypted_fields)
if should_serve:
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 = """<?xml version="1.0" encoding="UTF-8"?><response><code>1</code><result_url>coin_error.php</result_url></response>"""
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 cnt_id == -1:
song_stage_price = 300
if coin < song_stage_price:
return fail_url
stages = set(json.loads(my_stage)) if my_stage else set()
for i in range(616, 950):
if i not in stages:
stages.add(i)
coin -= song_stage_price
my_stage = json.dumps(list(stages))
else:
song_stage_price = stage_price
if (len(song_list[cnt_id]["difficulty_levels"]) == 6):
song_stage_price = song_stage_price * 2
if coin < song_stage_price:
return fail_url
stages = set(json.loads(my_stage)) if my_stage else set()
if cnt_id not in stages:
coin -= song_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_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
else:
return jsonify({"error": "Access denied"}), 403
@app.route('/web_shop_result.php', methods=['GET'])
def web_shop_result():
global decrypted_fields
cnt_type = decrypted_fields[b'cnt_type'][0].decode()
return inform_page(f"""SUCCESS:<br>Purchase successful.<br>Please close this page and the reward will arrive shortly.<br>If it took too long, try restarting the game.<br><a href='wwic://web_shop?cnt_type={cnt_type}' class='bt_bg01' >Go Back</a>""", 2)
@app.route('/coin_error.php', methods=['GET'])
def coin_error():
return inform_page(f"""FAILED:<br>Either you don't have enough coin,<br>or there were a duplicate order, and the reward will arrive shortly.""", 2)
@app.route('/ranking.php/', methods=['GET'])
def ranking():
should_serve = True
global decrypted_fields
if app.config.get('AUTHORIZATION_NEEDED', False):
should_serve = check_whitelist(decrypted_fields) and check_blacklist(decrypted_fields)
if should_serve:
device_id = decrypted_fields[b'vid'][0].decode()
html = "<ul class='song-list'>"
encrypted_mass = encryptAES(("vid=" + device_id + "&song_id=-1&mode=1&dummy=").encode("utf-8"))
href = f"/ranking_detail.php?{encrypted_mass}"
html += f'''
<li class="song-item">
<a href="{href}" class="song-button">Total Score</a>
</li>
'''
for index, song in enumerate(song_list):
encrypted_mass = encryptAES(("vid=" + device_id + "&song_id=" + str(index) + "&mode=3&dummy=").encode("utf-8"))
song_name = song.get("name_en", "Unknown")
href = f"/ranking_detail.php?{encrypted_mass}"
html += f'''
<li class="song-item">
<a href="{href}" class="song-button">{song_name}</a>
</li>
'''
html += "</ul>"
with open(f"files/ranking.html", "r", encoding="utf-8") as file:
html_content = file.read().format(text=html)
return html_content
else:
return jsonify({"error": "Access denied"}), 403
@app.route('/ranking_detail.php/', methods=['GET'])
def ranking_detail():
should_serve = True
global decrypted_fields
if app.config.get('AUTHORIZATION_NEEDED', False):
should_serve = check_whitelist(decrypted_fields) and check_blacklist(decrypted_fields)
if should_serve:
device_id = decrypted_fields[b'vid'][0].decode()
song_id = int(decrypted_fields[b'song_id'][0].decode())
mode = int(decrypted_fields[b'mode'][0].decode())
button_labels = []
difficulty_levels = []
song_name = ""
if (song_id == -1):
song_name = "Total Score"
difficulty_levels = []
button_labels = ["All", "Mobile", "Arcade"]
else:
song_name = song_list[song_id]["name_en"]
difficulty_levels = song_list[song_id]["difficulty_levels"]
button_labels = ["Easy", "Normal", "Hard"]
html = f"""<div style="text-align: center; font-size: 36px; margin-bottom: 20px;">{song_name}</div>"""
button_modes = [1, 2, 3]
if (len(difficulty_levels) == 6):
button_labels.extend(["AC-Easy", "AC-Normal", "AC-Hard"])
button_modes.extend([11, 12, 13])
if song_id > 615:
button_modes = [x for x in button_modes if x not in [1, 2, 3]]
button_labels = [x for x in button_labels if x not in ["Easy", "Normal", "Hard"]]
row_start = '<div class="button-row">'
row_end = '</div>'
row_content = []
for i, (label, mode_value) in enumerate(zip(button_labels, button_modes)):
if mode_value == mode:
row_content.append(f"""<div class="bt_bg01_ac">{label}</div>""")
else:
encrypted_mass = encryptAES(("vid=" + device_id + "&song_id=" + str(song_id) + "&mode=" + str(mode_value) + "&dummy=").encode("utf-8"))
row_content.append(f"""<a href="/ranking_detail.php?{encrypted_mass}" class="bt_bg01">{label}</a>""")
if len(row_content) == 3:
html += row_start + ''.join(row_content) + row_end
row_content = [] # Reset row content
play_results = None
user_result = None
device_result = None
if (song_id == -1):
# Filter out the mobile/AC modes
if (mode == 1):
exclude = []
elif mode == 2:
exclude = [11, 12, 13]
else:
exclude = [1, 2, 3]
with sqlite3.connect(DATABASE) as connection:
cursor = connection.cursor()
cursor.execute("SELECT vid, sid, mode, avatar, score FROM result")
play_results = cursor.fetchall()
cursor.execute("SELECT device_id, title, avatar FROM daily_reward")
device_results = {row[0]: {"title": row[1], "avatar": row[2]} for row in cursor.fetchall()}
cursor.execute("SELECT id, username, device_id FROM user")
user_results = {row[0]: {"username": row[1], "device_id": row[2]} for row in cursor.fetchall()}
cursor.execute("SELECT * FROM user WHERE device_id = ?", (device_id,))
cur_user = cursor.fetchone()
player_scores = {}
filtered_play_results = [play for play in play_results if int(play[2]) not in exclude]
for play in filtered_play_results:
did, sid, _, avatar, score = play
username, title = None, None
if sid: # Registered user
sid = int(sid)
if sid in user_results:
username = user_results[sid]["username"]
did = user_results[sid]["device_id"]
else: # Guest
username = f"Guest({did[-6:]})"
# title is device-specific
title = device_results.get(did, {}).get("title", "1")
if username in player_scores:
player_scores[username]["score"] += int(score)
player_scores[username]["avatar"] = avatar # But avatar is based on latest play submission
player_scores[username]["title"] = title
else:
player_scores[username] = {"score": int(score), "avatar": avatar, "title": title}
sorted_players = sorted(player_scores.items(), key=lambda x: x[1]["score"], reverse=True)
username = cur_user[1] if cur_user else f"Guest({device_id[-6:]})"
player_rank = None
user_score = 0
avatar = "1"
title = "1"
for rank, (player_name, data) in enumerate(sorted_players, start=1):
if player_name == username:
player_rank = rank
user_score = data["score"]
avatar = data["avatar"]
title = data["title"]
break
if player_rank is None:
device_data = next((device for device in device_results if device[1] == device_id), None)
if device_data:
avatar = device_data["avatar"]
title = device_data["title"]
# Generate player element
html += f"""
<div class="player-element">
<span class="rank">You<br>{"#" + str(player_rank) if player_rank else "N/A"}</span>
<img src="/files/image/icon/avatar/{avatar}.png" class="avatar" alt="Player Avatar">
<div class="player-info">
<div class="name">{username}</div>
<img src="/files/image/title/{title}.png" class="title" alt="Player Title">
</div>
<div class="player-score">{user_score}</div>
</div>
"""
html += """
<div class="leaderboard-container">
"""
# Loop leaderboard
for rank, (username, data) in enumerate(sorted_players, start=1):
html += f"""
<div class="leaderboard-player">
<div class="rank">#{rank}</div>
<img class="avatar" src="/files/image/icon/avatar/{data['avatar']}.png" alt="Avatar">
<div class="leaderboard-info">
<div class="name">{username}</div>
<div class="title"><img src="/files/image/title/{data['title']}.png" alt="Title"></div>
</div>
<div class="leaderboard-score">{data['score']}</div>
</div>
"""
else:
with sqlite3.connect(DATABASE) as connection:
cursor = connection.cursor()
query = "SELECT * FROM result WHERE id = ? AND mode = ? ORDER BY CAST(score AS INTEGER) DESC"
cursor.execute(query, (song_id, mode))
play_results = cursor.fetchall()
query = "SELECT * FROM user WHERE device_id = ?"
cursor.execute(query, (device_id,))
user_result = cursor.fetchone()
query = "SELECT * FROM daily_reward WHERE device_id = ?"
cursor.execute(query, (device_id,))
device_result = cursor.fetchone()
user_id = user_result[0] if user_result else None
username = user_result[1] if user_result else f"Guest({device_id[-6:]})"
play_record = None
# Check if the user's device ID is in play_results
if user_id:
play_record = next((record for record in play_results if int(record[3]) == user_id), None)
if not play_record:
play_record = next((record for record in play_results if record[1] == device_id and record[3] is None), None)
player_rank = None
avatar_index = str(play_record[7]) if play_record else "1"
user_score = play_record[8] if play_record else 0
for rank, result in enumerate(play_results, start=1):
if user_result and int(result[3]) == user_id:
player_rank = rank
break
elif result[1] == device_id and result[3] is None:
player_rank = rank
break
# Generate player element
html += f"""
<div class="player-element">
<span class="rank">You<br>{"#" + str(player_rank) if player_rank else "N/A"}</span>
<img src="/files/image/icon/avatar/{avatar_index}.png" class="avatar" alt="Player Avatar">
<div class="player-info">
<div class="name">{username}</div>
<img src="/files/image/title/{device_result[9]}.png" class="title" alt="Player Title">
</div>
<div class="player-score">{user_score}</div>
</div>
"""
# main leaderboard
html += """
<div class="leaderboard-container">
"""
for rank, record in enumerate(play_results, start=1):
username = f"Guest({record[1][-6:]})"
device_info = None
if record[3]:
cursor.execute("SELECT username FROM user WHERE id = ?", (record[3],))
user_data = cursor.fetchone()
if user_data:
username = user_data[0]
cursor.execute("SELECT title FROM daily_reward WHERE device_id = ?", (record[1],))
device_title = cursor.fetchone()
if device_title:
device_info = device_title[0]
else:
device_info = "1"
avatar_id = record[7] if record[7] else 1
avatar_url = f"/files/image/icon/avatar/{avatar_id}.png"
score = record[8]
html += f"""
<div class="leaderboard-player">
<div class="rank">#{rank}</div>
<img class="avatar" src="{avatar_url}" alt="Avatar">
<div class="leaderboard-info">
<div class="name">{username}</div>
<div class="title"><img src="/files/image/title/{device_info}.png" alt="Title"></div>
</div>
<div class="leaderboard-score">{score}</div>
</div>
"""
html += "</div>"
encrypted_mass = encryptAES(("vid=" + device_id + "&dummy=").encode("utf-8"))
html += f"""
<a href="/ranking.php?{encrypted_mass}" class="bt_bg01_ifedup" style="margin: 20px auto; display: block; text-align: center;">
Go Back
</a>
"""
with open(f"files/ranking.html", "r", encoding="utf-8") as file:
html_content = file.read().format(text=html)
return html_content
else:
return jsonify({"error": "Access denied"}), 403
@app.route('/status.php/', methods=['GET'])
def status():
should_serve = True
global decrypted_fields
if app.config.get('AUTHORIZATION_NEEDED', False):
should_serve = check_whitelist(decrypted_fields) and check_blacklist(decrypted_fields)
if should_serve:
device_id = decrypted_fields[b'vid'][0].decode()
set_title = int(decrypted_fields[b'set_title'][0].decode()) if b'set_title' in decrypted_fields else None
page_id = int(decrypted_fields[b'page_id'][0].decode()) if b'page_id' in decrypted_fields else 0
if (set_title):
with sqlite3.connect(DATABASE) as connection:
cursor = connection.cursor()
update_query = """
UPDATE daily_reward SET title = ? WHERE device_id = ?
"""
cursor.execute(update_query, (str(set_title), device_id))
connection.commit()
html = ""
with sqlite3.connect(DATABASE) as connection:
cursor = connection.cursor()
query = "SELECT * FROM daily_reward WHERE device_id = ?"
cursor.execute(query, (device_id,))
user_data = cursor.fetchone()
user_name = "Guest(" + user_data[1][-6:] + ")"
query = "SELECT username FROM user WHERE device_id = ?"
cursor.execute(query, (user_data[1],))
user_result = cursor.fetchone()
if user_result:
user_name = user_result[0]
if user_data:
player_element = f"""
<div class="player-element">
<img src="/files/image/icon/avatar/{user_data[10]}.png" class="avatar" alt="Player Avatar">
<div class="player-info">
<div class="name">{user_name}</div>
<img src="/files/image/title/{user_data[9]}.png" class="title" alt="Player Title">
</div>
<div class="player-score">Level {user_data[8]}</div>
</div>
"""
html += player_element
page_name = ["Special", "Normal", "Master", "God"]
buttons_html = '<div class="button-row">'
for i in range(0, 4):
if i == page_id:
# Current button
buttons_html += f"""
<div class="bt_bg01_ac" >
{page_name[i]}
</div>
"""
else:
encrypted_mass = encryptAES(f"vid={device_id}&page_id={i}&dummy=".encode("utf-8"))
buttons_html += f"""
<a href="/status.php?{encrypted_mass}" class="bt_bg01" >
{page_name[i]}
</a>
"""
buttons_html += '</div>'
html += f"<div style='text-align: center; margin-top: 20px;'>{buttons_html}</div>"
selected_titles = title_lists.get(page_id, [])
titles_html = '<div class="title-list">'
for index, num in enumerate(selected_titles):
if index % 2 == 0:
if index != 0:
titles_html += '</div>'
titles_html += '<div class="title-row">'
if num == int(user_data[9]):
titles_html += f"""
<img src="/files/image/title/{num}.png" alt="Title {num}" class="title-image-selected">
"""
else:
encrypted_mass = encryptAES(f"vid={device_id}&title_id={num}&page_id={page_id}&dummy=".encode("utf-8"))
titles_html += f"""
<a href="/set_title.php?{encrypted_mass}" class="title-link">
<img src="/files/image/title/{num}.png" alt="Title {num}" class="title-image">
</a>
"""
titles_html += '</div></div>'
html += titles_html
with open(f"files/status.html", "r", encoding="utf-8") as file:
html_content = file.read().format(text=html)
return html_content
else:
return jsonify({"error": "Access denied"}), 403
@app.route('/set_title.php/', methods=['GET'])
def set_title():
should_serve = True
global decrypted_fields
if app.config.get('AUTHORIZATION_NEEDED', False):
should_serve = check_whitelist(decrypted_fields) and check_blacklist(decrypted_fields)
if should_serve:
device_id = decrypted_fields[b'vid'][0].decode()
page_id = decrypted_fields[b'page_id'][0].decode()
title_id = decrypted_fields[b'title_id'][0].decode()
current_title = 1
html = ""
with sqlite3.connect(DATABASE) as connection:
cursor = connection.cursor()
query = "SELECT title FROM daily_reward WHERE device_id = ?"
cursor.execute(query, (device_id,))
user_data = cursor.fetchone()
if user_data:
current_title = user_data[0]
confirm_url = encryptAES(
f"vid={device_id}&page_id={page_id}&set_title={title_id}&dummy=".encode("utf-8")
)
go_back_url = encryptAES(
f"vid={device_id}&page_id={page_id}&dummy=".encode("utf-8")
)
html += f"""
<p>Would you like to change your title?<br>Current Title:</p>
<img src="/files/image/title/{current_title}.png" alt="Current Title" class="title-image">
<p>New Title:</p>
<img src="/files/image/title/{title_id}.png" alt="New Title" class="title-image">
<div class="button-container">
<a href="/status.php?{confirm_url}" class="bt_bg01">Confirm</a>
<a href="/status.php?{go_back_url}" class="bt_bg01">Go back</a>
</div>
"""
return inform_page(html, 1)
else:
return jsonify({"error": "Access denied"}), 403
@app.route('/mission.php/', methods=['GET'])
def mission():
should_serve = True
global decrypted_fields
if app.config.get('AUTHORIZATION_NEEDED', False):
should_serve = check_whitelist(decrypted_fields) and check_blacklist(decrypted_fields)
if should_serve:
global exp_unlocked_songs, song_list
html = f"""<div class="f90 a_center pt50" >Play Music to level up and unlock free songs!<br>Songs can only be unlocked when you play online.</div><div class='mission-list'>"""
# Render the mission list
for song in exp_unlocked_songs:
song_id = song["id"]
level_required = song["lvl"]
song_name = song_list[song_id]["name_en"] if song_id < len(song_list) else "Unknown Song"
html += f"""
<div class="mission-row">
<div class="mission-level">Level {level_required}</div>
<div class="mission-song">{song_name}</div>
</div>
"""
html += "</div>"
with open(f"files/mission.html", "r", encoding="utf-8") as file:
html_content = file.read().format(text=html)
return html_content
else:
return jsonify({"error": "Access denied"}), 403
@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:<br>Username must be between 6 and 20 characters long.", 0)
if is_alphanumeric(username) == False:
return inform_page("FAILED:<br>Username must consist entirely of alphanumeric character.", 0)
if username == password:
return inform_page("FAILED:<br>Username cannot be the same as password.", 0)
if check_blacklist(decrypted_fields) == False:
return inform_page("FAILED:<br>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:<br>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:<br>Username updated.", 0)
else:
return inform_page("FAILED:<br>Password is not correct.<br>Please try again.", 0)
else:
return inform_page("FAILED:<br>User have no password hash.<br>This should not happen.", 0)
else:
return inform_page("FAILED:<br>User does not exist.<br>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:<br>Username cannot be the same as password.", 0)
if len(password) < 6:
return inform_page("FAILED:<br>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:<br>Password updated.", 0)
else:
return inform_page("FAILED:<br>Old password is not correct<br>Please try again.", 0)
else:
return inform_page("FAILED:<br>User have no password hash<br>This should not happen.", 0)
else:
return inform_page("FAILED:<br>User does not exist<br>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:<br>Username cannot be the same as password.", 0)
if len(username) < 6 or len(username) > 20:
return inform_page("FAILED:<br>Username must be between 6 and 20<br>characters long.", 0)
if len(password) < 6:
return inform_page("FAILED:<br>Password must have<br>6 or above characters.", 0)
if is_alphanumeric(username) == False:
return inform_page("FAILED:<br>Username must consist entirely of<br>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:<br>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:<br>Account is registered.<br>You can now backup/restore your save file.<br>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:<br>Your account is banned and you are<br>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:<br>You are logged in.", 0)
else:
return inform_page("FAILED:<br>Username or password incorrect.", 0)
else:
return inform_page("FAILED:<br>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 """<response><code>12</code><message><ja>セーブデータが無いか、セーブデータが破損しているため、ロードできませんでした。</ja><en>Unable to load; either no save data exists, or the save data is corrupted.</en><fr>Chargement impossible : les données de sauvegarde sont absentes ou corrompues.</fr><it>Impossibile caricare. Non esistono dati salvati o quelli esistenti sono danneggiati.</it></message></response>"""
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 = """<?xml version="1.0" encoding="UTF-8"?><response><code>0</code>
<data>{data}</data>
<crc>{crc}</crc>
<date>{date}</date>
</response>""".format(data=data,crc=crc,date=date)
return xml_data
else:
return """<response><code>10</code><message><ja>この機能を使用するには、まずアカウントを登録する必要があります。</ja><en>You need to register an account first before this feature can be used.</en><fr>Vous devez d'abord créer un compte avant de pouvoir utiliser cette fonctionnalité.</fr><it>È necessario registrare un account prima di poter utilizzare questa funzione.</it></message></response>"""
@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 """<response><code>0</code></response>"""
else:
return """<response><code>10</code></response>"""
@app.route('/delete_account.php', methods=['GET'])
def delete_account():
# this only tricks the client to clear its local data for now
return """<?xml version="1.0" encoding="UTF-8"?><response><code>0</code><taito_id></taito_id></response>"""
@app.route('/<path:path>', 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'])