mirror of
https://github.com/Remik1r3n/maimaiDX-Api.git
synced 2025-05-20 04:17:28 +08:00
AuthLite实现,ApiHash草稿,各种小改进
This commit is contained in:
parent
32d566841c
commit
7570fbc8f4
125
API_AuthLite.py
Normal file
125
API_AuthLite.py
Normal file
@ -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)
|
@ -95,7 +95,9 @@ def apiSDGB(data:str, targetApi:str, userAgentExtraData:str, noLog:bool=False, t
|
|||||||
"Expect": "100-continue"
|
"Expect": "100-continue"
|
||||||
},
|
},
|
||||||
content=reqData_deflated,
|
content=reqData_deflated,
|
||||||
verify=certifi.where(),
|
# 经测试,加 Verify 之后速度慢好多,因此建议选择性开
|
||||||
|
#verify=certifi.where(),
|
||||||
|
verify=False,
|
||||||
timeout=timeout
|
timeout=timeout
|
||||||
)
|
)
|
||||||
if not noLog:
|
if not noLog:
|
||||||
@ -144,11 +146,11 @@ def apiSDGB(data:str, targetApi:str, userAgentExtraData:str, noLog:bool=False, t
|
|||||||
|
|
||||||
raise SDGBApiError("重试多次仍然无法成功请求服务器")
|
raise SDGBApiError("重试多次仍然无法成功请求服务器")
|
||||||
|
|
||||||
def calcSpecialNumber():
|
def calcPlaySpecial():
|
||||||
"""使用 c_int32 实现的 SpecialNumber 算法"""
|
"""使用 c_int32 实现的 SpecialNumber 算法"""
|
||||||
rng = random.SystemRandom()
|
rng = random.SystemRandom()
|
||||||
num2 = rng.randint(1, 1037933) * 2069
|
num2 = rng.randint(1, 1037933) * 2069
|
||||||
num2 += 0x400
|
num2 += 1024 #GameManager.CalcSpecialNum()
|
||||||
num2 = c_int32(num2).value
|
num2 = c_int32(num2).value
|
||||||
result = c_int32(0)
|
result = c_int32(0)
|
||||||
for _ in range(32):
|
for _ in range(32):
|
||||||
@ -157,8 +159,9 @@ def calcSpecialNumber():
|
|||||||
num2 >>= 1
|
num2 >>= 1
|
||||||
return c_int32(result.value).value
|
return c_int32(result.value).value
|
||||||
|
|
||||||
|
"""
|
||||||
|
DEPRECATED: 旧的 SpecialNumber 算法
|
||||||
def calcSpecialNumber2():
|
def calcSpecialNumber2():
|
||||||
"""实验性替代 SpecialNumber 算法"""
|
|
||||||
max = 1037933
|
max = 1037933
|
||||||
num2 = random.randint(1, max) * 2069
|
num2 = random.randint(1, max) * 2069
|
||||||
|
|
||||||
@ -169,4 +172,5 @@ def calcSpecialNumber2():
|
|||||||
num3 += num2 % 2
|
num3 += num2 % 2
|
||||||
num2 >>= 1
|
num2 >>= 1
|
||||||
|
|
||||||
return num3
|
return num3
|
||||||
|
"""
|
@ -144,3 +144,11 @@ def generateLoginBonusList(UserLoginBonusList, generateMode=1):
|
|||||||
|
|
||||||
logger.debug(f"ログインボーナスリスト: {bonusList}")
|
logger.debug(f"ログインボーナスリスト: {bonusList}")
|
||||||
return bonusList
|
return bonusList
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# ログインボーナスデータをアップロードする
|
||||||
|
userId = testUid
|
||||||
|
currentLoginTimestamp = generateTimestamp()
|
||||||
|
currentLoginResult = apiLogin(currentLoginTimestamp, userId)
|
||||||
|
implLoginBonus(userId, currentLoginTimestamp, currentLoginResult, 2)
|
||||||
|
apiLogout(currentLoginTimestamp, userId)
|
@ -1 +0,0 @@
|
|||||||
# It will be finished very soon, I promise
|
|
43
HashEntertainment/All_API_Names.txt
Normal file
43
HashEntertainment/All_API_Names.txt
Normal file
@ -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
|
28
HashEntertainment/GetMyHash.py
Normal file
28
HashEntertainment/GetMyHash.py
Normal file
@ -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)
|
@ -63,9 +63,9 @@ def implFullPlayAction(userId: int, currentLoginTimestamp:int, currentLoginResul
|
|||||||
retries = 0
|
retries = 0
|
||||||
while retries < 3:
|
while retries < 3:
|
||||||
# 计算一个特殊数
|
# 计算一个特殊数
|
||||||
currentSpecialNumber = calcSpecialNumber()
|
currentPlaySpecial = calcPlaySpecial()
|
||||||
# 生成出 UserAll
|
# 生成出 UserAll
|
||||||
currentUserAll = generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentSpecialNumber)
|
currentUserAll = generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentPlaySpecial)
|
||||||
# 应用参数里的补丁
|
# 应用参数里的补丁
|
||||||
applyUserAllPatches(currentUserAll, userAllPatches)
|
applyUserAllPatches(currentUserAll, userAllPatches)
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
import rapidjson as json
|
import rapidjson as json
|
||||||
import time
|
import time
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
import random
|
||||||
|
|
||||||
from Config import *
|
from Config import *
|
||||||
from API_TitleServer import apiSDGB
|
from API_TitleServer import apiSDGB
|
||||||
@ -41,12 +42,19 @@ def apiLogout(timestamp:int, userId:int, noLog:bool=False) -> dict:
|
|||||||
logger.info("登出:结果:"+ str(logout_result))
|
logger.info("登出:结果:"+ str(logout_result))
|
||||||
return logout_result
|
return logout_result
|
||||||
|
|
||||||
def generateTimestamp() -> int:
|
def generateTimestampLegacy() -> int:
|
||||||
"""生成一个凑合用的时间戳"""
|
"""生成一个凑合用的时间戳"""
|
||||||
timestamp = int(time.time()) - 60
|
timestamp = int(time.time()) - 60
|
||||||
logger.info(f"生成时间戳: {timestamp}")
|
logger.info(f"生成时间戳: {timestamp}")
|
||||||
return 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__":
|
if __name__ == "__main__":
|
||||||
print("强制登出 CLI")
|
print("强制登出 CLI")
|
||||||
uid = testUid
|
uid = testUid
|
||||||
|
@ -5,11 +5,11 @@ from datetime import datetime
|
|||||||
from Config import *
|
from Config import *
|
||||||
from HelperGetUserThing import implGetUser_
|
from HelperGetUserThing import implGetUser_
|
||||||
|
|
||||||
def generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentSpecialNumber):
|
def generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentPlaySpecial):
|
||||||
"""从服务器取得必要的数据并构建一个比较完整的 UserAll"""
|
"""从服务器取得必要的数据并构建一个比较完整的 UserAll"""
|
||||||
|
|
||||||
# 先构建一个基础 UserAll
|
# 先构建一个基础 UserAll
|
||||||
currentUserAll = generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentSpecialNumber)
|
currentUserAll = generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentPlaySpecial)
|
||||||
|
|
||||||
# 然后从服务器取得必要的数据
|
# 然后从服务器取得必要的数据
|
||||||
currentUserExtend = implGetUser_("Extend", userId, True)
|
currentUserExtend = implGetUser_("Extend", userId, True)
|
||||||
@ -29,7 +29,7 @@ def generateFullUserAll(userId, currentLoginResult, currentLoginTimestamp, curre
|
|||||||
return currentUserAll
|
return currentUserAll
|
||||||
|
|
||||||
|
|
||||||
def generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentSpecialNumber):
|
def generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, currentUserData2, currentPlaySpecial):
|
||||||
"""构建一个非常基础的 UserAll 数据,必须手动填充一些数据"""
|
"""构建一个非常基础的 UserAll 数据,必须手动填充一些数据"""
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -149,7 +149,7 @@ def generateUserAllData(userId, currentLoginResult, currentLoginTimestamp, curre
|
|||||||
"isEventMode": False,
|
"isEventMode": False,
|
||||||
"isNewFree": False,
|
"isNewFree": False,
|
||||||
"playCount": currentUserData2['playCount'],
|
"playCount": currentUserData2['playCount'],
|
||||||
"playSpecial": currentSpecialNumber,
|
"playSpecial": currentPlaySpecial,
|
||||||
"playOtherUserId": 0
|
"playOtherUserId": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user