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"
|
||||
},
|
||||
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
|
||||
|
||||
@ -170,3 +173,4 @@ def calcSpecialNumber2():
|
||||
num2 >>= 1
|
||||
|
||||
return num3
|
||||
"""
|
@ -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)
|
@ -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
|
||||
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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
],
|
||||
|
Loading…
x
Reference in New Issue
Block a user