diff --git a/API_AuthLite.py b/API_AuthLite.py new file mode 100644 index 0000000..45422c9 --- /dev/null +++ b/API_AuthLite.py @@ -0,0 +1,125 @@ +# All.Net AuthLite 更新获取 + +from Crypto.Cipher import AES +from Crypto.Util.Padding import pad +import httpx +from loguru import logger +from urllib.parse import parse_qs +import configparser as ini + +def enc(key, iv, data): + cipher = AES.new(key, AES.MODE_CBC, iv) + encrypted = cipher.encrypt(data) + return encrypted + +def dec(key, iv ,data): + de_cipher = AES.new(key, AES.MODE_CBC, iv) + decrypted = de_cipher.decrypt(data) + return decrypted + +def getRawDelivery(): + key = bytes([47, 63, 106, 111, 43, 34, 76, 38, 92, 67, 114, 57, 40, 61, 107, 71]) + iv = bytes.fromhex('00000000000000000000000000000000') + content = bytes([0] * 16) + b'title_id=SDGB&title_ver=1.40&client_id=A63E01C2805' + header = bytes.fromhex('00000000000000000000000000000000') + bytes_data = pad(header + content, 16) + + encrypted = enc(key, iv, bytes_data) + + r = httpx.post( + 'http://at.sys-allnet.cn/net/delivery/instruction', + data = encrypted, + headers = { + 'User-Agent': "SDGB;Windows/Lite", + 'Pragma': 'DFI' + } + ) + + resp_data = r.content + decrypted = dec(key, resp_data[:16], resp_data) + decrypted_str = decrypted[16:].decode('UTF-8').strip() + # 过滤所有控制字符 + decrypted_str = ''.join([i for i in decrypted_str if 31 < ord(i) < 127]) + logger.info(f"RAW Response: {decrypted_str}") + + return decrypted_str + +def parseRawDelivery(deliveryStr): + """解析 RAW 的 Delivery 字符串,返回其中的有效的 instruction URL 的列表""" + parsedResponseDict = {key: value[0] for key, value in parse_qs(deliveryStr).items()} + urlList = parsedResponseDict['uri'].split('|') + # 过滤掉空字符串和内容为 null 的情况 + urlList = [url for url in urlList if url and url != 'null'] + logger.info(f"Parsed URL List: {urlList}") + validURLs = [] + for url in urlList: + # 检查是否是 HTTPS 的 URL,以及是否是 txt 文件,否则忽略 + if not url.startswith('https://') or not url.endswith('.txt'): + logger.warning(f"Invalid URL will be ignored: {url}") + continue + validURLs.append(url) + logger.info(f"Verified Valid URLs: {validURLs}") + return validURLs + +def getUpdateIniFromURL(url): + # 发送请求 + response = httpx.get(url, headers={ + 'User-Agent': 'SDGB;Windows/Lite', + 'Pragma': 'DFI' + }) + logger.info(f"成功自 {url} 获取更新信息") + return response.text + +def parseUpdateIni(iniText): + # 解析配置 + config = ini.ConfigParser(allow_no_value=True) + config.read_string(iniText) + logger.info(f"成功解析配置文件,包含的节有:{config.sections()}") + + # 获取 COMMON 节的配置 + common = config['COMMON'] + + # 初始化消息列表 + message = [] + + # 获取游戏描述并去除引号 + game_desc = common['GAME_DESC'].strip('"') + + # 根据前缀选择消息模板和图标 + prefix_icons = { + 'PATCH': ('💾 游戏程序更新 ', 'PATCH_'), + 'OPTION': ('📚 游戏内容更新 ', 'OPTION_') + } + icon, prefix = prefix_icons.get(game_desc.split('_')[0], ('📦 游戏更新 ', '')) + + # 构建消息标题 + game_title = game_desc.replace(prefix, '', 1) + message.append(f"{icon}{game_title}") + + # 添加主文件的下载链接 + main_file = common['INSTALL1'] + main_file_name = main_file.split('/')[-1] + message.append(f"下载: \n- [{main_file_name}]({main_file})") + + # 添加可选文件的下载链接(如果有) + if 'OPTIONAL' in config: + message.append("其它文件:") + optional_files = [f"- [{url.split('/')[-1]}]({url})" for _, url in config.items('OPTIONAL')] + message.extend(optional_files) + + # 添加发布时间信息 + release_time = common['RELEASE_TIME'].replace('T', ' ') + message.append(f"将于 {release_time} 发布。\n") + + # 构建最终的消息字符串 + final_message = '\n'.join(message) + logger.info(f"消息构建完成,最终的消息为:\n{final_message}") + + return final_message + + +if __name__ == '__main__': + urlList = parseRawDelivery(getRawDelivery()) + for url in urlList: + iniText = getUpdateIniFromURL(url) + message = parseUpdateIni(iniText) diff --git a/API_TitleServer.py b/API_TitleServer.py index e84855b..4d3e558 100644 --- a/API_TitleServer.py +++ b/API_TitleServer.py @@ -95,7 +95,9 @@ def apiSDGB(data:str, targetApi:str, userAgentExtraData:str, noLog:bool=False, t "Expect": "100-continue" }, content=reqData_deflated, - verify=certifi.where(), + # 经测试,加 Verify 之后速度慢好多,因此建议选择性开 + #verify=certifi.where(), + verify=False, timeout=timeout ) if not noLog: @@ -144,11 +146,11 @@ def apiSDGB(data:str, targetApi:str, userAgentExtraData:str, noLog:bool=False, t raise SDGBApiError("重试多次仍然无法成功请求服务器") -def calcSpecialNumber(): +def calcPlaySpecial(): """使用 c_int32 实现的 SpecialNumber 算法""" rng = random.SystemRandom() num2 = rng.randint(1, 1037933) * 2069 - num2 += 0x400 + num2 += 1024 #GameManager.CalcSpecialNum() num2 = c_int32(num2).value result = c_int32(0) for _ in range(32): @@ -157,8 +159,9 @@ def calcSpecialNumber(): num2 >>= 1 return c_int32(result.value).value +""" +DEPRECATED: 旧的 SpecialNumber 算法 def calcSpecialNumber2(): - """实验性替代 SpecialNumber 算法""" max = 1037933 num2 = random.randint(1, max) * 2069 @@ -169,4 +172,5 @@ def calcSpecialNumber2(): num3 += num2 % 2 num2 >>= 1 - return num3 \ No newline at end of file + return num3 +""" \ No newline at end of file diff --git a/ActionLoginBonus.py b/ActionLoginBonus.py index a716ae0..5cfb194 100644 --- a/ActionLoginBonus.py +++ b/ActionLoginBonus.py @@ -144,3 +144,11 @@ def generateLoginBonusList(UserLoginBonusList, generateMode=1): logger.debug(f"ログインボーナスリスト: {bonusList}") return bonusList + +if __name__ == "__main__": + # ログインボーナスデータをアップロードする + userId = testUid + currentLoginTimestamp = generateTimestamp() + currentLoginResult = apiLogin(currentLoginTimestamp, userId) + implLoginBonus(userId, currentLoginTimestamp, currentLoginResult, 2) + apiLogout(currentLoginTimestamp, userId) \ No newline at end of file diff --git a/AuthLite_GetUpdate.py b/AuthLite_GetUpdate.py deleted file mode 100644 index d6a38db..0000000 --- a/AuthLite_GetUpdate.py +++ /dev/null @@ -1 +0,0 @@ -# It will be finished very soon, I promise diff --git a/HashEntertainment/All_API_Names.txt b/HashEntertainment/All_API_Names.txt new file mode 100644 index 0000000..937d1b8 --- /dev/null +++ b/HashEntertainment/All_API_Names.txt @@ -0,0 +1,43 @@ +GetUserCardApi +GetUserCharacterApi +GetUserChargeApi +GetUserCourseApi +GetUserDataApi +GetUserExtendApi +GetUserFavoriteApi +GetUserFriendSeasonRankingApi +GetUserGhostApi +GetUserItemApi +GetGameChargeApi +GetUserLoginBonusApi +GetGameEventApi +GetUserMapApi +GetGameNgMusicIdApi +GetUserMusicApi +GetGameRankingApi +GetGameSettingApi +GetUserOptionApi +GetUserPortraitApi +GetGameTournamentInfoApi +UserLogoutApi +GetUserPreviewApi +GetTransferFriendApi +GetUserRatingApi +GetUserActivityApi +GetUserRecommendRateMusicApi +GetUserRecommendSelectMusicApi +GetUserRegionApi +GetUserScoreRankingApi +UploadUserPhotoApi +UploadUserPlaylogApi +UploadUserPortraitApi +UpsertClientBookkeepingApi +UpsertClientSettingApi +UpsertClientTestmodeApi +UpsertClientUploadApi +UpsertUserAllApi +UpsertUserChargelogApi +UserLoginApi +Ping +GetUserFavoriteItemApi +GetGameNgWordListApi diff --git a/HashEntertainment/GetMyHash.py b/HashEntertainment/GetMyHash.py new file mode 100644 index 0000000..5b5d56c --- /dev/null +++ b/HashEntertainment/GetMyHash.py @@ -0,0 +1,28 @@ +import os +import re + +pattern = re.compile(r'Maimai2Servlet/(.*?)MaimaiChn') + +# 获取目录 +dir = 'C:/Users/remik1r3n/Workspace/maimaiDX-Api/HashEntertainment' + +known_hashes = [] + +for filename in os.listdir(dir): + # 只处理.txt文件 + if filename.endswith('.txt'): + file_path = os.path.join(dir, filename) + with open(file_path, 'r', encoding='utf-8') as file: + content = file.read() + # 搜索匹配的模式 + matches = pattern.findall(content) + # 输出每个匹配中的不定内容 + for match in matches: + known_hashes.append(match) + +# 去重 +known_hashes = list(set(known_hashes)) + +# 输出 +for hash in known_hashes: + print(hash) \ No newline at end of file diff --git a/HelperFullPlay.py b/HelperFullPlay.py index 8d69212..482a574 100644 --- a/HelperFullPlay.py +++ b/HelperFullPlay.py @@ -63,9 +63,9 @@ def implFullPlayAction(userId: int, currentLoginTimestamp:int, currentLoginResul retries = 0 while retries < 3: # 计算一个特殊数 - currentSpecialNumber = calcSpecialNumber() + currentPlaySpecial = calcPlaySpecial() # 生成出 UserAll - currentUserAll = generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentSpecialNumber) + currentUserAll = generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentPlaySpecial) # 应用参数里的补丁 applyUserAllPatches(currentUserAll, userAllPatches) diff --git a/HelperLogInOut.py b/HelperLogInOut.py index 2f15013..aab428e 100644 --- a/HelperLogInOut.py +++ b/HelperLogInOut.py @@ -4,6 +4,7 @@ import rapidjson as json import time from loguru import logger +import random from Config import * from API_TitleServer import apiSDGB @@ -41,12 +42,19 @@ def apiLogout(timestamp:int, userId:int, noLog:bool=False) -> dict: logger.info("登出:结果:"+ str(logout_result)) return logout_result -def generateTimestamp() -> int: +def generateTimestampLegacy() -> int: """生成一个凑合用的时间戳""" timestamp = int(time.time()) - 60 logger.info(f"生成时间戳: {timestamp}") return timestamp +def generateTimestamp() -> int: + """生成一个今天早上 10:00 随机偏移的时间戳""" + timestamp = int(time.mktime(time.strptime(time.strftime("%Y-%m-%d 10:00:00"), "%Y-%m-%d %H:%M:%S"))) + random.randint(-600, 600) + logger.info(f"生成时间戳: {timestamp}") + logger.info(f"此时间戳对应的时间为: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(timestamp))}") + return timestamp + if __name__ == "__main__": print("强制登出 CLI") uid = testUid diff --git a/HelperUserAll.py b/HelperUserAll.py index bf52621..8f85d38 100644 --- a/HelperUserAll.py +++ b/HelperUserAll.py @@ -5,11 +5,11 @@ from datetime import datetime from Config import * from HelperGetUserThing import implGetUser_ -def generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentSpecialNumber): +def generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentPlaySpecial): """从服务器取得必要的数据并构建一个比较完整的 UserAll""" # 先构建一个基础 UserAll - currentUserAll = generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentSpecialNumber) + currentUserAll = generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentPlaySpecial) # 然后从服务器取得必要的数据 currentUserExtend = implGetUser_("Extend", userId, True) @@ -29,7 +29,7 @@ def generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, curre return currentUserAll -def generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentSpecialNumber): +def generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentPlaySpecial): """构建一个非常基础的 UserAll 数据,必须手动填充一些数据""" data = { @@ -149,7 +149,7 @@ def generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, curre "isEventMode": False, "isNewFree": False, "playCount": currentUserData2['playCount'], - "playSpecial": currentSpecialNumber, + "playSpecial": currentPlaySpecial, "playOtherUserId": 0 } ],