add diff files for auto updating client, readme for installation and running tutorial

This commit is contained in:
raphaeIl
2025-01-16 08:49:31 -05:00
parent 582d91d2a5
commit fa75125d65
29 changed files with 30550 additions and 5686 deletions

View File

@@ -0,0 +1,146 @@
using Microsoft.AspNetCore.Mvc;
using Novaria.Common.Crypto;
using System.Text.Json;
using Serilog;
using Novaria.Common.Core;
using Proto;
using Google.Protobuf;
using Novaria.GameServer.Controllers.Api.ProtocolHandlers;
using Novaria.Common.Util;
namespace Novaria.GameServer.Controllers.Api
{
[Route("/agent-zone-1")]
public class GatewayController : ControllerBase
{
private readonly IProtocolHandlerFactory protocolHandlerFactory;
private readonly ILogger<GatewayController> logger;
public GatewayController(IProtocolHandlerFactory _protocolHandlerFactory, ILogger<GatewayController> _logger)
{
protocolHandlerFactory = _protocolHandlerFactory;
logger = _logger;
}
[HttpPost]
public IActionResult PostRequest()
{
Log.Information("Received Gateway Post Request, Payload");
using var memoryStream = new MemoryStream();
Request.Body.CopyTo(memoryStream); // Copy request body to MemoryStream
byte[] rawPayload = memoryStream.ToArray(); // Get raw bytes from MemoryStream
Utils.PrintByteArray(rawPayload);
Packet requestPacket = HttpNetworkManager.Instance.ParseRequest(rawPayload);
Log.Information("");
Log.Information("msgs body: ");
Utils.PrintByteArray(requestPacket.msgBody.ToArray());
Log.Information("Sucessfully parsed request packet, id: " + requestPacket.msgId);
byte[] responsePackeBytes = null;
NetMsgId msgid = (NetMsgId)requestPacket.msgId;
Log.Information("Received protocol msgid: " + msgid);
Type requestType = ProtocolHandlerFactory.GetRequestPacketTypeByProtocol(msgid);
if (requestType is null)
{
Log.Error($"MsgId {msgid} doesn't have corresponding type registered");
throw new InvalidDataException("invalid protocol msgid");
}
IMessage decodedPayload = HttpNetworkManager.Instance.DecodePacket(requestType, requestPacket);
// originally i returned a IMessage from handlers,
// but since in this game apprently one req can return diff resp msgids thus they can not be hardcoded in the packet or increments,
// so we just return packet, bit tedius but inside handlers will determine the response msgid
var responsePacketObj = protocolHandlerFactory.Invoke(msgid, decodedPayload);
if (responsePacketObj is null)
{
Log.Error($"Protocol {msgid}: {ProtocolHandlerFactory.NetMsgIdToNameMappings[(short)msgid]} is unimplemented and left unhandled");
//throw new ArgumentNullException("handler does not exist");
return new EmptyResult();
}
// -- changed to manually set in handler
// succeed_ack = req_msg_id + 1, failed_ack = req_msg_id - 1, for special stuff like gm its -1/-2
//NetMsgId respMsgId = msgid + 1; // this is probably good for most cases but idk if theres any that doesnt follow this
Packet responsePacket = (Packet)responsePacketObj;
// maybe just change invoke to return Packet
Log.Information($"Response Packet msgid: {responsePacket.msgId}: {(short)responsePacket.msgId}");
responsePackeBytes = HttpNetworkManager.Instance.BuildResponse(responsePacket);
if (responsePackeBytes == null)
{
Log.Error("Unable to build response for packet msgid: " + responsePacket.msgId);
throw new InvalidOperationException("something went wrong during building the response packet!");
}
Log.Information("Sucessfully responsed with a respone packet msgid: " + (short)responsePacket.msgId);
Response.Body.Write(responsePackeBytes, 0, responsePackeBytes.Length);
return new EmptyResult();
}
[HttpOptions] // Ike
public IActionResult OptionsRequest()
{
Log.Information("Received Gateway Ike Options Request, Payload: ");
// store key which is used in AeadTool
using var memoryStream = new MemoryStream();
Request.Body.CopyTo(memoryStream); // Copy request body to MemoryStream
byte[] rawPayload = memoryStream.ToArray(); // Get raw bytes from MemoryStream
Utils.PrintByteArray(rawPayload);
IKEReq ikeRequest = HttpNetworkManager.Instance.ParseIkeRequest(rawPayload);
Log.Information("Decoded Packet: " + JsonSerializer.Serialize(ikeRequest));
byte[] clientPublicKey = ikeRequest.PubKey.ToArray();
Log.Information("RECEIVED client public key: ");
Utils.PrintByteArray(clientPublicKey);
AeadTool.key3 = DiffieHellman.Instance.CalculateKey(clientPublicKey);
Log.Information("KEY3 (chacha20 key) calculated: ");
Utils.PrintByteArray(AeadTool.key3);
IKEResp ikeResponse = new IKEResp()
{
PubKey = ByteString.CopyFrom(AeadTool.PS_PUBLIC_KEY.Reverse().ToArray()),
Token = AeadTool.TOKEN
};
Log.Information("Sending ps server public key: ");
Utils.PrintByteArray(AeadTool.PS_PUBLIC_KEY);
Packet ikePacket = new Packet()
{
msgId = 2,
msgBody = ikeResponse.ToByteArray()
};
var responsePayload = HttpNetworkManager.Instance. BuildIkeResponse(ikePacket);
Log.Information("Sending ike response packet: " + JsonSerializer.Serialize(ikeResponse));
Log.Information("Built bytes: ");
Utils.PrintByteArray(responsePayload);
Response.Body.Write(responsePayload, 0, responsePayload.Length);
return new EmptyResult();
}
}
}

View File

@@ -0,0 +1,20 @@
using Novaria.Common.Core;
using Proto;
using Serilog;
namespace Novaria.GameServer.Controllers.Api.ProtocolHandlers
{
public class ActivityDetail : ProtocolHandlerBase
{
public ActivityDetail(IProtocolHandlerFactory protocolHandlerFactory) : base(protocolHandlerFactory)
{
}
[ProtocolHandler(NetMsgId.activity_detail_req)] // req id goes here
public Packet PlayerLoginHandler(Nil req)
{
return Packet.Create(NetMsgId.activity_detail_succeed_ack, PcapParser.PcapParser.Instance.GetPcapPacket(NetMsgId.activity_detail_succeed_ack));
}
}
}

View File

@@ -0,0 +1,91 @@
using Google.Protobuf;
using Novaria.Common.Core;
using Proto;
using Serilog;
using System.Text.Json;
using Novaria.PcapParser;
using Novaria.Common.Crypto;
namespace Novaria.GameServer.Controllers.Api.ProtocolHandlers
{
public class Login : ProtocolHandlerBase
{
public Login(IProtocolHandlerFactory protocolHandlerFactory) : base(protocolHandlerFactory)
{
}
[ProtocolHandler(NetMsgId.player_login_req)] // req id goes here
public Packet PlayerLoginHandler(LoginReq req)
{
Log.Information("login_req received, contents: " + JsonSerializer.Serialize(req));
Log.Information("Building login resp...");
LoginResp loginResp = new LoginResp()
{
Token = "seggstoken",
};
Log.Information("Sending login_resp packet: " + JsonSerializer.Serialize(loginResp));
return Packet.Create(NetMsgId.player_login_succeed_ack, loginResp);
}
[ProtocolHandler(NetMsgId.player_data_req)]
public Packet PlayerDataHandler(Nil req)
{
// example: different netmsgid returned, if new player player_new_notify, other wise player_data_ack
AccInfo accountInfo = new AccInfo()
{
Id = 1,
Hashtag = 4562,
HeadIcon = 100101,
NickName = "seggs",
Gender = false,
Signature = "",
TitlePrefix = 1,
TitleSuffix = 1,
SkinId = 10301,
CreateTime = DateTime.Now.Ticks,
};
accountInfo.Newbies.Add(new NewbieInfo() { GroupId = 101, StepId = -1 });
accountInfo.Newbies.Add(new NewbieInfo() { GroupId = 102, StepId = -1 });
byte[] real_key = AeadTool.key3;
ClientType real_client_type = AeadTool.clientType;
// load from pcap
AeadTool.clientType = ClientType.Mobile; // my pcap were from mobile so gotta switch it over
AeadTool.InitAeadTool();
PcapParser.PcapParser.Instance.Parse("all_mainmenu_packets.json");
// after pcap parse switch it back, this should rlly be done inside PcapParser.Parse()
AeadTool.clientType = real_client_type;
AeadTool.InitAeadTool();
PlayerInfo pcapPlayerInfo = (PlayerInfo)PcapParser.PcapParser.Instance.GetPcapPacket(NetMsgId.player_data_succeed_ack);
PlayerInfo playerInfoResponse = new PlayerInfo()
{
Acc = pcapPlayerInfo.Acc
};
AeadTool.key3 = real_key;
Log.Information("Sending player_new_notify packet: " + JsonSerializer.Serialize(pcapPlayerInfo));
return Packet.Create(NetMsgId.player_data_succeed_ack, pcapPlayerInfo);
}
[ProtocolHandler(NetMsgId.player_ping_req)]
public Packet PlayerRegHandler(Ping req)
{
return Packet.Create(NetMsgId.player_ping_succeed_ack, new Pong()
{
ServerTs = DateTime.Now.Ticks,
});
}
}
}

View File

@@ -0,0 +1,26 @@
using Novaria.Common.Core;
using Proto;
using Serilog;
namespace Novaria.GameServer.Controllers.Api.ProtocolHandlers
{
public class Mail : ProtocolHandlerBase
{
public Mail(IProtocolHandlerFactory protocolHandlerFactory) : base(protocolHandlerFactory)
{
}
[ProtocolHandler(NetMsgId.mail_list_req)] // req id goes here
public Packet PlayerLoginHandler(Nil req)
{
Mails mailListResp = (Mails)PcapParser.PcapParser.Instance.GetPcapPacket(NetMsgId.mail_list_succeed_ack);
mailListResp.List[0].Subject = "seggs";
mailListResp.List[1].Subject = "seggs";
mailListResp.List[2].Subject = "seggs";
return Packet.Create(NetMsgId.mail_list_succeed_ack, mailListResp);
}
}
}

View File

@@ -0,0 +1,677 @@
using Google.Protobuf;
using Novaria.Common.Core;
using Proto;
using Serilog;
using System.Reflection;
using Novaria.PcapParser;
namespace Novaria.GameServer.Controllers.Api.ProtocolHandlers
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
internal class ProtocolHandlerAttribute : Attribute
{
public NetMsgId NetMsgId { get; }
public ProtocolHandlerAttribute(NetMsgId protocol)
{
NetMsgId = protocol;
}
}
public interface IProtocolHandlerFactory
{
public object? Invoke(NetMsgId protocol, IMessage? req);
public MethodInfo? GetProtocolHandler(NetMsgId protocol);
public void RegisterInstance(Type t, object? inst);
}
public class ProtocolHandlerFactory : IProtocolHandlerFactory
{
public static readonly Dictionary<short, string> NetMsgIdToNameMappings = new Dictionary<short, string>()
{
[-10034] = "GmCharUpNotify", // 角色数据变更
[-10033] = "GmTowerGrowthNodesNotify", // 星塔养成节点变化通知
[-10032] = "GmHarmonySkillNotify", // 共鸣技能新增
[-10031] = "GmStory", // 一键通关故事并获得所有证据
[-10030] = "GmStory", // 通关指定的故事并获取证据
[-10029] = "InfinityTowerLevelInfo", // 无尽之塔跳层
[-10028] = "GmAllInfinityTowerInfos", // 全通所有无尽之塔
[-10027] = "GmClearAllEquipmentInstance", // 全通所有装备副本
[-10026] = "StarTowerBuildInfo", // 返回星塔build导入的build信息
[-10025] = "String", // 返回星塔build的json序列化字符串
[-10024] = "StarTowerRoomCase", // 返回操作添加的新case
[-10023] = "STAddTeamExpNtf", // 队伍等级经验最终值及当前未处理的所有case
[-10022] = "StarTowerInfo", // 星塔跳层
[-10021] = "GmSTClearAllStarTower", // 全通所有星塔(返回通关星塔ID列表)
[-10020] = "ChangeInfo", // 星塔道具变化通知
[-10019] = "CharAffinity", // 添加角色好感度
[-10017] = "Disc", // 星盘信息通知
[-10016] = "GmClearAllTravelerDue", // 全通所有旅人对决
[-10015] = "GmClearAllRegionBossLevel", // 全通所有强敌讨伐
[-10013] = "GmClearAllDailyInstance", // 一键通关所有日常副本
[-10009] = "UI32", // 增加吸血鬼模式副本赛季积分 返回当前总分
[-10008] = "GmClearAllVampireSurvivor", // 一键通关所有吸血鬼模式副本
[-10007] = "RegionBossLevel", // 地区boss关卡最终数据通知
[-10006] = "Chars", // 角色列表最终数据通知
[-10005] = "GmWorldClass", // 世界等级最终值通知
[-10004] = "Char", // 角色数据变化通知
[-10003] = "Char", // 角色信息通知
[-10001] = "ChangeInfo", // 道具变化通知
[-3] = "Error", // 请求失败
[-2] = "Nil", // 成功,响应会走notify附加数据流程
[-1] = "SudoCommand", // 客户端内置GM命令请求
[1] = "IKEReq", // internet key exchange
[2] = "IKEResp", // 成功,返回服务器方的秘钥,之后每次请求都需要将Token写入header X-Token段,服务器以此识别用户
[3] = "Error", // 失败
[4] = "LoginReq", // 登录
[5] = "LoginResp", // 成功,将返回的新token替换之前ike阶段的token,放置于header X-Token段,服务器以此识别用户
[6] = "Error", // 失败
[1001] = "Nil", // 获取用户全量数据
[1002] = "PlayerInfo", // 成功,如果是新账号会返回player_new_notify协议
[1003] = "Error", // 请求失败
[1004] = "PlayerReg", // 注册角色
[1005] = "Error", // 注册失败 返回错误反之成功会调用player_data_succeed_ack
[1006] = "PlayerNameEditReq", // 请求修改昵称
[1007] = "PlayerNameEditResp", // 请求修改昵称成功
[1008] = "Error", // 请求修改昵称失败
[1009] = "PlayerHeadIconSetReq", // 请求设置头像
[1010] = "Nil", // 设置头像成功
[1011] = "Error", // 设置头像失败
[1012] = "Ping", // 心跳
[1013] = "Pong", // 心跳回馈
[1014] = "Error", // 不会返回失败
[1015] = "NewbieInfo", // 新手教学,提交教学信息
[1016] = "Nil", // 教学步骤记录成功
[1017] = "Error", // 教学记录失败
[1018] = "Nil", // 获取注销账号数据NotifyUrl
[1019] = "PlayerDestroy", // 生成注销回调地址以提交到sdk server
[1020] = "Error", // 获取失败
[1021] = "PlayerBoardSetReq", // 请求设置看板
[1022] = "Nil", // 设置看板成功
[1023] = "Error", // 设置看板失败
[1024] = "UI32", // 请求领取世界等级奖励
[1025] = "ChangeInfo", // 请求领取世界等级奖励成功
[1026] = "Error", // 请求领取世界等级奖励失败
[1027] = "PlayerSignatureEditReq", // 请求修改签名
[1028] = "Nil", // 请求修改签名成功
[1029] = "Error", // 请求修改签名失败
[1030] = "PlayerTitleEditReq", // 请求修改头衔
[1031] = "Nil", // 请求修改头衔成功
[1032] = "Error", // 请求修改头衔失败
[1033] = "PlayerCharsShowReq", // 请求展示角色
[1034] = "Nil", // 请求展示角色成功
[1035] = "Error", // 请求展示角色失败
[1036] = "PlayerSkinShowReq", // 请求展示皮肤
[1037] = "Nil", // 请求展示皮肤成功
[1038] = "Error", // 请求展示皮肤失败
[1039] = "Nil", // 请求切换性别
[1040] = "Nil", // 切换性别成功
[1041] = "Error", // 切换性别失败
[1042] = "PlayerSurveyReq", // 申请发起调查问卷,填写问卷ID
[1043] = "PlayerSurveyResp", // 返回第三方问卷ID和回调通知地址
[1044] = "Error", // 申请失败,比如过期,已经完成过
[1045] = "Nil", // 退出游戏
[1046] = "Nil", // 退出成功
[1047] = "Error", // 退出失败
[1048] = "PlayerHonorEditReq", // 荣誉称号最新的列表
[1049] = "Nil", // 修改成功
[1050] = "Error", // 修改失败
[1101] = "ItemUseReq", // 道具使用
[1102] = "ChangeInfo", // 道具使用成功返回ChangeInfo
[1103] = "Error", // 道具使用失败,返回错误信息
[1104] = "UI32", // 传入钻石数量,兑换心相碎片,默认规则(免费钻不够使用付费钻)
[1105] = "ChangeInfo", // 兑换成功
[1106] = "Error", // 兑换失败
[1107] = "ItemProductReq", // 材料合成
[1108] = "ChangeInfo", // 材料合成成功
[1109] = "Error", // 材料合成失败
[1110] = "Nil", // 所有角色溢出碎片兑换
[1111] = "ChangeInfo", // 兑换成功
[1112] = "Error", // 兑换失败
[1113] = "Nil", // 领取商店每日免费赠礼
[1114] = "ChangeInfo", // 领取成功
[1115] = "Error", // 领取失败
[1201] = "Nil", // 请求获取好友/好友申请列表
[1202] = "FriendListGetResp", // 获取好友/好友申请列表成功
[1203] = "Error", // 获取好友/好友申请列表失败
[1204] = "FriendUIdSearchReq", // 请求通过UId搜索用户信息
[1205] = "FriendUIdSearchResp", // 通过UId搜索用户信息成功
[1206] = "Error", // 通过UId搜索用户信息失败
[1207] = "FriendNameSearchReq", // 请求通过用户昵称搜索用户信息
[1208] = "FriendNameSearchResp", // 通过用户昵称搜索用户信息成功
[1209] = "Error", // 通过用户昵称搜索用户信息失败
[1210] = "FriendAddReq", // 请求申请添加好友
[1211] = "Nil", // 申请添加好友成功
[1212] = "Error", // 申请添加好友失败
[1213] = "FriendAddAgreeReq", // 同意添加好友请求
[1214] = "FriendAddAgreeResp", // 同意添加好友成功
[1215] = "Error", // 同意添加好友失败
[1216] = "Nil", // 请求一键添加好友
[1217] = "FriendAllAgreeResp", // 一键添加好友成功
[1218] = "Error", // 一键添加好友失败
[1219] = "FriendDeleteReq", // 请求删除好友
[1220] = "Nil", // 删除好友成功
[1221] = "Error", // 删除好友失败
[1222] = "FriendInvitesDeleteReq", // 请求删除好友申请
[1223] = "Nil", // 删除好友申请成功
[1224] = "Error", // 删除好友申请失败
[1225] = "FriendStarSetReq", // 请求设置星级好友
[1226] = "Nil", // 请求设置星级好友成功
[1227] = "Error", // 请求设置星级好友失败
[1228] = "FriendReceiveEnergyReq", // 请求领取好友赠送体力
[1229] = "FriendReceiveEnergyResp", // 请求领取好友赠送体力成功
[1230] = "Error", // 请求领取好友赠送体力失败
[1231] = "FriendSendEnergyReq", // 请求赠送好友体力
[1232] = "FriendSendEnergyResp", // 请求赠送好友体力成功
[1233] = "Error", // 请求赠送好友体力失败
[1234] = "Nil", // 请求好友推荐列表
[1235] = "FriendRecommendationGetResp", // 请求好友推荐列表成功
[1236] = "Error", // 请求好友推荐列表失败
[1301] = "Nil", // 获取星塔养成详细信息
[1302] = "TowerGrowthDetailResp", // 获取成功
[1303] = "Error", // 获取信息失败,返回错误
[1304] = "UI32", // 传入节点ID,解锁对应的养成节点
[1305] = "ChangeInfo", // 解锁成功
[1306] = "Error", // 解锁失败,返回错误信息
[2001] = "PlayerFormationReq", // 设置编队
[2002] = "Nil", // 设置编队成功
[2003] = "Error", // 设置编队失败
[2104] = "CharEquipmentChangeReq", // 装备穿上/替换/卸下
[2105] = "CharEquipmentChangeResp", // 装备穿上/替换/卸下成功
[2106] = "Error", // 装备穿上/替换/卸下失败
[2301] = "CharUpgradeReq", // 角色升级
[2302] = "CharUpgradeResp", // 角色升级成功
[2303] = "Error", // 角色升级失败
[2304] = "UI32", // 角色进阶,传入角色ID
[2305] = "ChangeInfo", // 进阶成功
[2306] = "Error", // 进阶失败
[2307] = "CharSkillUpgradeReq", // 角色技能升级
[2308] = "ChangeInfo", // 升级成功
[2309] = "Error", // 升级失败
[2313] = "CharAdvanceRewardReceiveReq", // 请求领取角色进阶奖励
[2314] = "CharAdvanceRewardReceiveResp", // 请求领取角色进阶奖励成功
[2315] = "Error", // 请求领取角色进阶奖励失败
[2316] = "CharSkinSetReq", // 设置角色皮肤
[2317] = "Nil", // 设置成功
[2318] = "Error", // 设置失败
[2322] = "CharAffinityQuestRewardReceiveReq", // 请求领取角色好感度任务奖励
[2323] = "CharAffinityQuestRewardReceiveResp", // 请求领取角色好感度任务成功
[2324] = "Error", // 请求领取角色好感度任务失败
[2325] = "UI32", // 传入角色ID招募角色
[2326] = "ChangeInfo", // 招募成功
[2327] = "Error", // 招募失败
[2328] = "CharAffinityGiftSendReq", // 请求赠送好感度礼物
[2329] = "CharAffinityGiftSendResp", // 请求赠送好感度礼物成功
[2330] = "Error", // 请求赠送好感度礼物失败
[2401] = "CharDatingLandmarkSelectReq", // 选择地点邀约角色
[2402] = "CharDatingLandmarkSelectResp", // 选择地点邀约角色成功
[2403] = "Error", // 选择地点邀约角色失败
[2404] = "CharDatingGiftSendReq", // 请求邀约赠礼
[2405] = "CharDatingGiftSendResp", // 请求邀约赠礼成功
[2406] = "Error", // 请求邀约赠礼失败
[2407] = "CharDatingEventRewardReceiveReq", // 请求领取特殊事件奖励
[2408] = "ChangeInfo", // 请求领取特殊事件奖励成功
[2409] = "Error", // 请求领取特殊事件奖励失败
[3119] = "DiscStrengthenReq", // 星盘强化
[3120] = "DiscStrengthenResp", // 星盘强化成功
[3121] = "Error", // 星盘强化失败
[3122] = "DiscPromoteReq", // 星盘升阶
[3123] = "DiscPromoteResp", // 星盘升阶成功
[3124] = "Error", // 星盘升阶失败
[3125] = "DiscLimitBreakReq", // 星盘突破
[3126] = "DiscLimitBreakResp", // 星盘突破成功
[3127] = "Error", // 星盘突破失败
[3128] = "DiscReadRewardReceiveReq", // 请求领取星盘阅读奖励
[3129] = "ChangeInfo", // 请求领取星盘阅读奖励成功
[3130] = "Error", // 请求领取星盘阅读奖励失败
[3201] = "EquipmentUpgradeReq", // 装备强化
[3202] = "EquipmentUpgradeResp", // 装备强化成功
[3203] = "Error", // 装备强化失败
[3204] = "EquipmentDismantleReq", // 装备分解
[3205] = "ChangeInfo", // 装备分解成功
[3206] = "Error", // 装备分解失败
[3207] = "EquipmentLockUnlockReq", // 装备加解锁
[3208] = "EquipmentInfo", // 装备加解锁成功
[3209] = "Error", // 装备加解锁失败
[3301] = "AgentApplyReq", // 请求派遣委托
[3302] = "AgentApplyResp", // 请求派遣委托成功
[3303] = "Error", // 请求派遣委托失败
[3304] = "AgentGiveUpReq", // 请求放弃派遣委托
[3305] = "AgentGiveUpResp", // 请求放弃派遣委托成功
[3306] = "Error", // 请求放弃派遣委托失败
[3307] = "AgentRewardReceiveReq", // 请求领取派遣委托奖励
[3308] = "AgentRewardReceiveResp", // 请求领取派遣委托奖励成功
[3309] = "Error", // 请求领取派遣委托奖励失败
[4201] = "UI32", // 领取手册任务奖励 value表示任务ID0表示一键领取
[4202] = "TourGuideQuestRewardResp", // 获取成功
[4203] = "Error", // 获取失败
[4204] = "UI32", // 领取日常任务奖励 value表示任务ID0表示一键领取
[4205] = "ChangeInfo", // 获取成功
[4206] = "Error", // 获取失败
[4207] = "DictRewardReq", // 领取词条奖励
[4208] = "ChangeInfo", // 获取成功
[4209] = "Error", // 获取失败
[4210] = "UI32", // 领取星塔任务奖励 value表示任务ID0表示一键领取
[4211] = "ChangeInfo", // 获取成功
[4212] = "Error", // 获取失败
[4213] = "Nil", // 领取日常任务活跃奖励
[4214] = "QuestDailyActiveRewardReceiveResp", // 领取日常任务活跃奖励成功
[4215] = "Error", // 领取日常任务活跃奖励失败
[4216] = "Nil", // 领取任务组奖励
[4217] = "TourGuideQuestGroupRewardResp", // 获取成功
[4218] = "Error", // 获取失败
[4401] = "AchievementRewardReq", // 领取成就奖励
[4402] = "ChangeInfo", // 获取成功
[4403] = "Error", // 获取失败
[4404] = "Nil", // 获取成就数据
[4405] = "Achievements", // 获取成功
[4406] = "Error", // 获取失败
[4501] = "Nil", // 申请无尽塔关卡数据
[4502] = "InfinityTowerInfoResp", // 申请无尽塔关卡数据成功
[4503] = "Error", // 申请无尽塔关卡数据失败
[4504] = "InfinityTowerApplyReq", // 申请进入无尽塔关卡
[4505] = "Nil", // 申请进入无尽塔关卡成功
[4506] = "Error", // 申请进入无尽塔关卡失败
[4507] = "UI32", // 申请结算无尽塔关卡 Value:1胜利,2失败,3退出(1,2重复挑战无需再发申请)
[4508] = "InfinityTowerSettleResp", // 申请结算无尽塔成功 非0表示可以继续挑战关卡ID(失败当前关卡ID成功下一个关卡ID),无需再发申请
[4509] = "Error", // 申请结算无尽塔失败
[4510] = "Nil", // 请求领取无尽塔每日奖励
[4511] = "InfinityTowerDailyRewardReceiveResp", // 请求领取无尽塔每日奖励成功
[4512] = "Error", // 请求领取无尽塔每日奖励失败
[4513] = "UI32", // 请求领取无尽塔剧情奖励
[4514] = "InfinityTowerPlotRewardReceiveResp", // 请求领取无尽塔剧情奖励成功
[4515] = "Error", // 请求领取无尽塔剧情奖励失败
[4601] = "StarTowerApplyReq", // 申请探索星塔
[4602] = "StarTowerApplyResp", // 申请成功返回
[4603] = "Error", // 申请失败
[4607] = "StarTowerInteractReq", // 星塔交互请求
[4608] = "StarTowerInteractResp", // 交互请求成功
[4609] = "Error", // 申请失败
[4610] = "Nil", // 获取星塔信息,用于重连
[4611] = "StarTowerInfo", // 获取星塔信息成功
[4612] = "Error", // 获取星塔信息失败
[4613] = "Nil", // 放弃星塔
[4614] = "StarTowerGiveUpResp", // 放弃星塔成功
[4615] = "Error", // 放弃星塔失败
[4701] = "StarTowerBuildWhetherSaveReq", // 请求是否保存星塔build
[4702] = "StarTowerBuildWhetherSaveResp", // 请求是否保存星塔build返回
[4703] = "Error", // 请求是否保存星塔build失败
[4704] = "Nil", // 请求星塔build简要信息列表
[4705] = "StarTowerBuildBriefListGetResp", // 请求星塔build简要信息列表返回
[4706] = "Error", // 请求星塔build简要信息列表失败
[4707] = "StarTowerBuildDetailGetReq", // 请求星塔build详细信息列表
[4708] = "StarTowerBuildDetailGetResp", // 请求星塔build详细信息列表返回
[4709] = "Error", // 请求遗迹build详细信息列表失败
[4710] = "StarTowerBuildDeleteReq", // 请求解散星塔build
[4711] = "ChangeInfo", // 请求解散星塔build返回
[4712] = "Error", // 请求解散星塔build失败
[4713] = "StarTowerBuildNameSetReq", // 请求设置星塔build名
[4714] = "Nil", // 请求设置星塔build名返回
[4715] = "Error", // 请求设置星塔build名失败
[4716] = "StarTowerBuildLockUnlockReq", // 请求星塔build加解锁
[4717] = "Nil", // 请求星塔build加解锁返回
[4718] = "Error", // 请求星塔build加解锁失败
[4719] = "StarTowerBuildPreferenceSetReq", // 请求设置星塔build偏好
[4720] = "Nil", // 请求设置星塔build偏好返回
[4721] = "Error", // 请求设置星塔build偏好失败
[4801] = "Nil", // 请求星塔排行榜信息
[4802] = "StarTowerRankInfo", // 请求星塔排行榜信息成功
[4803] = "Error", // 请求星塔排行信息失败
[4804] = "StarTowerRankApplyReq", // 申请探索星塔排行榜
[4805] = "StarTowerRankApplyResp", // 申请成功返回
[4806] = "Error", // 申请失败
[4901] = "Nil", // 请求星塔图鉴角色潜能简要信息
[4902] = "StarTowerBookPotentialBriefListResp", // 请求星塔图鉴角色潜能简要信息成功
[4903] = "Error", // 请求星塔图鉴角色潜能简要信息失败
[4904] = "UI32", // 请求星塔图鉴角色潜能信息
[4905] = "StarTowerBookPotentialGetResp", // 请求星塔图鉴角色潜能信息成功
[4906] = "Error", // 请求星塔图鉴角色潜能信息失败
[4907] = "UI32", // 请求领取星塔图鉴角色潜能奖励
[4908] = "StarTowerBookPotentialRewardReceiveResp", // 请求领取星塔图鉴角色潜能奖励成功
[4909] = "Error", // 请求领取星塔图鉴角色潜能奖励失败
[4910] = "Nil", // 请求星塔图鉴事件信息
[4911] = "StarTowerBookEventGetResp", // 请求星塔图鉴事件信息成功
[4912] = "Error", // 请求星塔图鉴事件信息失败
[4913] = "UI32", // 请求领取星塔图鉴角色潜能奖励
[4914] = "StarTowerBookEventRewardReceiveResp", // 请求领取星塔图鉴角色潜能奖励成功
[4915] = "Error", // 请求领取星塔图鉴角色潜能奖励失败
[5010] = "ResidentShopGetReq", // 请求常驻商店信息
[5011] = "ResidentShopGetResp", // 请求常驻商店信息成功
[5012] = "Error", // 请求常驻商店信息失败
[5013] = "ResidentShopPurchaseReq", // 请求常驻商店购买物品
[5014] = "ResidentShopPurchaseResp", // 请求常驻商店购买物品成功
[5015] = "Error", // 请求常驻商店购买物品失败
[5101] = "Nil", // 获取钻石商城产品列表
[5102] = "MallGemList", // 获取成功的列表
[5103] = "Error", // 获取失败
[5104] = "String", // 下单购买商品
[5105] = "OrderInfo", // 下单成功
[5106] = "Error", // 下单失败
[5107] = "UI64", // 取消某个尚未支付的订单
[5108] = "UI64", // 取消成功
[5109] = "Error", // 取消失败
[5110] = "UI64", // 领取某个支付成功的订单的奖励
[5111] = "CollectResp", // 返回成功,请根据具体状态处理
[5112] = "CollectResp", // 领取失败
[5113] = "Nil", // 获取月卡商城产品列表
[5114] = "MallMonthlyCardList", // 获取成功的列表
[5115] = "Error", // 获取失败
[5116] = "String", // 购买月卡商城商品
[5117] = "OrderInfo", // 下单成功
[5118] = "Error", // 获取失败
[5119] = "Nil", // 获取礼包商城商品列表
[5120] = "MallPackageList", // 商品列表
[5121] = "Error", // 获取失败
[5122] = "String", // 购买礼包商城产品
[5123] = "MallPackageOrder", // 购买成功结果
[5124] = "Error", // 购买失败
[5125] = "Nil", // 获取星尘兑换商城商品列表
[5126] = "MallShopProductList", // 商品列表
[5127] = "Error", // 获取失败
[5128] = "MallShopOrderReq", // 购买星尘兑换商店产品
[5129] = "ChangeInfo", // 购买成功结果
[5130] = "Error", // 购买失败
[6001] = "GachaSpinReq", // 根据卡池ID以及卡池模式在当前卡池抽卡
[6002] = "GachaSpinResp", // 成功返回掉落道具以及ChangeInfo
[6003] = "Error", // 错误,返回错误信息
[6004] = "Nil", // 获取所有卡池数据
[6005] = "GachaInformationResp", // 成功,返回所有的卡池数据
[6006] = "Error", // 错误,返回错误信息
[6007] = "UI32", // 根据存盘ID获取抽卡的历史记录
[6008] = "GachaHistories", // 成功,返回抽卡的历史数据
[6009] = "Error", // 错误,返回错误
[6101] = "Nil", // 获取已经拥有的命运卡图鉴
[6102] = "TowerBookFateCardDetailResp", // 成功,返回已经拥有的命运卡和已经领取的任务
[6103] = "Error", // 错误,返回错误信息
[6104] = "TowerBookFateCardRewardReq", // 领取命运卡任务奖励
[6105] = "ChangeInfo", // 成功,返回任务奖励
[6106] = "Error", // 错误,返回错误信息
[7013] = "UI32", // 传入剧情ID,领取剧情奖励
[7014] = "ChangeInfo", // 领取成功
[7015] = "Error", // 领取失败
[7016] = "DailyInstanceApplyReq", // 日常副本申请
[7017] = "Nil", // 日常副本申请成功
[7018] = "Error", // 错误,返回错误信息
[7019] = "DailyInstanceSettleReq", // 日常副本结算请求
[7020] = "DailyInstanceSettleResp", // 日常副本结算成功
[7021] = "Error", // 错误,返回错误信息
[7022] = "DailyInstanceRaidReq", // 日常副本扫荡请求
[7023] = "DailyInstanceRaidResp", // 日常副本扫荡成功
[7024] = "Error", // 错误,返回错误信息
[7028] = "DailyEquipmentApplyReq", // 日常装备副本申请
[7029] = "Nil", // 日常装备副本申请成功
[7030] = "Error", // 错误,返回错误信息
[7031] = "DailyEquipmentSettleReq", // 日常装备副本结算请求
[7032] = "DailyEquipmentSettleResp", // 日常装备副本结算成功
[7033] = "Error", // 错误,返回错误信息
[7034] = "DailyEquipmentSweepReq", // 日常装备副本扫荡请求
[7035] = "DailyEquipmentSweepResp", // 日常装备副本扫荡成功
[7036] = "Error", // 错误,返回错误信息
[7101] = "RegionBossLevelApplyReq", // 请求进入地区boss关卡
[7102] = "ChangeInfo", // 请求进入地区boss关成功
[7103] = "Error", // 请求进入地区boss关卡失败
[7104] = "RegionBossLevelSettleReq", // 请求结算地区boss关卡
[7105] = "RegionBossLevelSettleResp", // 请求结算地区boss关成功
[7106] = "Error", // 请求结算地区boss关卡失败
[7107] = "RegionBossLevelSweepReq", // 请求扫荡地区boss关卡
[7108] = "RegionBossLevelSweepResp", // 请求扫荡地区boss关成功
[7109] = "Error", // 请求结算地区boss关卡失败
[7201] = "TravelerDuelLevelApplyReq", // 请求进入旅人对决关卡
[7202] = "Nil", // 请求进入旅人对决关卡成功
[7203] = "Error", // 请求进入旅人对决关卡失败
[7204] = "TravelerDuelLevelSettleReq", // 请求结算旅人对决关卡
[7205] = "TravelerDuelLevelSettleResp", // 请求结算旅人对决关卡成功
[7206] = "Error", // 请求结算旅人对决关卡失败
[7207] = "Nil", // 请求旅人对决信息
[7208] = "TravelerDuelInfo", // 请求旅人对决信息成功
[7209] = "Error", // 请求旅人对决信息失败
[7210] = "TravelerDuelQuestRewardReceiveReq", // 领取旅人对决任务奖励
[7211] = "TravelerDuelQuestRewardReceiveResp", // 获取成功
[7212] = "Error", // 获取失败
[7213] = "Nil", // 请求旅人对决排行榜信息
[7214] = "TravelerDuelRankInfo", // 请求旅人对决排行榜信息成功
[7215] = "Error", // 请求旅人对决排行信息失败
[7216] = "TravelerDuelBattleSamples", // 请求旅人对决上传分数、附带本次战斗统计数据
[7217] = "RankChange", // 请求旅人对决上传分数成功
[7218] = "Error", // 请求旅人对决上传分数失败
[7301] = "StoryApplyReq", // 关卡申请
[7302] = "Nil", // 申请成功,返回 Nil
[7303] = "Error", // 错误,返回错误信息
[7304] = "StorySettleReq", // 关卡结算
[7305] = "ChangeInfo", // 结算成功,发放通关奖励
[7306] = "Error", // 错误,返回错误信息
[8001] = "Nil", // 购买体力请求
[8002] = "EnergyBuyResp", // 购买成功返回当日的购买次数以及ChangeInfo
[8003] = "Error", // 购买失败,返回错误信息
[8101] = "Events", // 客户端事件上报
[8102] = "Nil", // 客户端事件上报成功
[8103] = "Error", // 客户端事件上报失败
[8201] = "VampireSurvivorApplyReq", // 灾变防线副本申请
[8202] = "VampireSurvivorApplyResp", // 灾变防线副本申请成功
[8203] = "Error", // 灾变防线副本申请失败
[8204] = "VampireSurvivorAreaChangeReq", // 灾变防线阶段转化
[8205] = "Nil", // 灾变防线副本申请成功
[8206] = "Error", // 灾变防线副本申请失败
[8207] = "VampireSurvivorSettleReq", // 灾变防线副本结算申请 [失败也发这个]
[8208] = "VampireSurvivorSettleResp", // 灾变防线副本结算申请成功
[8209] = "Error", // 灾变防线副本结算申请失败
[8210] = "VampireSurvivorRewardSelectReq", // 灾变防线副本升级申请
[8211] = "VampireSurvivorRewardSelectResp", // 灾变防线副本结算申请成功
[8212] = "Error", // 灾变防线副本结算申请失败
[8213] = "VampireSurvivorRewardChestReq", // 灾变防线开宝箱申请
[8214] = "VampireSurvivorRewardChestResp", // 灾变防线开宝箱申请成功
[8215] = "Error", // 灾变防线开宝箱申请失败
[8216] = "VampireSurvivorQuestRewardReceiveReq", // 灾变防线领取任务奖励申请
[8217] = "ChangeInfo", // 灾变防线领取任务奖励成功
[8218] = "Error", // 灾变防线领取任务奖励失败
[8219] = "Nil", // 请求吸血鬼模式排行榜信息
[8220] = "VampireRankInfo", // 请求吸血鬼模式排行榜信息成功
[8221] = "Error", // 请求吸血鬼模式排行榜信息失败
[8301] = "Nil", // 获取节点信息
[8302] = "VampireTalentDetailResp", // 获取节点信息成功
[8303] = "Error", // 获取失败,返回错误信息
[8304] = "Nil", // 重置所有的天赋节点
[8305] = "UI32", // 重置成功,返回最终天赋点数
[8306] = "Error", // 重置失败,返回错误信息
[8307] = "UI32", // 传入节点ID,解锁对应的养成节点
[8308] = "Nil", // 解锁成功
[8309] = "Error", // 解锁失败,返回错误信息
[9001] = "Nil", // 获取邮件列表
[9002] = "Mails", // 邮件列表
[9003] = "Error", // 获取失败
[9004] = "MailRequest", // 标记邮件已读
[9005] = "UI32", // 返回已设置为已读的邮件ID
[9006] = "Error", // 设置失败
[9007] = "MailRequest", // 领取奖励 一键领取发送0单独领取发送对应邮件ID上来
[9008] = "MailRecvResp", // 领取成功
[9009] = "Error", // 领取失败
[9010] = "MailRequest", // 删除邮件 一键删除所有已读已领发送0单独删除发送对应邮件ID上来
[9011] = "MailRemoveResp", // 删除成功
[9012] = "Error", // 删除失败
[9101] = "Nil", // 获取所有的活动数据
[9102] = "ActivityResp", // 获取成功
[9103] = "Error", // 获取失败
[9104] = "ActivityPeriodicRewardReq", // 领取活动奖励
[9105] = "ChangeInfo", // 领取成功
[9106] = "Error", // 领取失败
[9107] = "UI32", // 仪式感,领取当前活动的最终奖励
[9108] = "ChangeInfo", // 领取成功
[9109] = "Error", // 领取失败
[9110] = "UI32", // 领取活动奖励
[9111] = "ChangeInfo", // 领取成功
[9112] = "Error", // 领取失败
[9201] = "Nil", // 获取手机所有联系人的数据
[9202] = "PhoneContactsInfoResp", // 获取联系人数据成功
[9203] = "Error", // 获取联系人数据
[9204] = "PhoneContactsReportReq", // 联系人聊天上报
[9205] = "ChangeInfo", // 上报成功
[9206] = "Error", // 上报失败
[9301] = "UI32", // 传入天赋ID,解锁相关天赋
[9302] = "TalentUnlockResp", // 解锁成功
[9303] = "Error", // 解锁失败,返回错误信息
[9304] = "TalentResetReq", // 传入角色ID,重置角色所有天赋
[9305] = "ChangeInfo", // 重置成功
[9306] = "Error", // 重置失败,返回错误信息
[9307] = "UI32", // 天赋普通节点ID
[9308] = "TalentNodeResetResp", // 重置成功
[9309] = "Error", // 重置失败,返回错误信息
[9801] = "Nil", // 获取当前战令信息
[9802] = "BattlePassInfo", // 战令信息
[9803] = "Error", // 获取失败
[9804] = "BattlePassRewardReceiveReq", // 领取战令奖励,传入战令等级和版本,全部领等级取传0
[9805] = "BattlePassRewardReceiveResp", // 领取战令奖励成功
[9806] = "Error", // 领取战令奖励失败
[9807] = "UI32V", // 战令等级购买,传入需要购买的级数和版本
[9808] = "BattlePassLevelBuyResp", // 购买成功
[9809] = "Error", // 购买成功
[9810] = "BattlePassOrderReq", // 战令进阶下单
[9811] = "OrderInfo", // 战令进阶下单成功
[9812] = "Error", // 领取失败
[9813] = "UI64", // 战令进阶订单收取
[9814] = "BattlePassOrderCollectResp", // 返回成功,请根据具体状态处理
[9815] = "CollectResp", // 战令进阶失败
[9816] = "UI32", // 战令任务一键领取 value表示任务ID0表示一键领取
[9817] = "BattlePassQuestRewardResp", // 获取成功
[9818] = "Error", // 获取失败
[9901] = "String", // 兑换码兑换
[9902] = "RedeemCodeResp", // 兑换成功返回兑换后德道具以及ChangeInfo
[9903] = "Error", // 兑换失败,返回错误信息
[10000] = "Error", // 系统级失败,主要用于http模式下强制失败返回
[10001] = "Nil", // 新用户
[10002] = "MailState", // 邮件状态变更
[10003] = "Error", // 在其他地方登录
[10004] = "Error", // token过期
[10005] = "Error", // 用户被ban
[10006] = "Quest", // 任务进度变更
[10007] = "ChangeInfo", // 星塔排行榜门票
[10008] = "NewAgent", // 每周刷新新委托ID列表
[10009] = "WorldClassUpdate", // 世界等级变化
[10010] = "FriendEnergyState", // 好友赠送体力状态变更
[10011] = "SigninRewardUpdate", // 登陆奖励更新
[10012] = "FriendState", // 好友状态变更
[10013] = "UI64", // 订单已完成支付通知,可以发起领取
[10014] = "ChangeInfo", // 订单道具被撤回,主要用于恶意退款
[10015] = "StarTowerBookPotentialChange", // 星塔潜能图鉴状态变更
[10016] = "StarTowerBookEventChange", // 星塔潜能图鉴状态变更
[10017] = "BattlePassState", // 战令状态变更
[10018] = "WorldClassRewardState", // 世界等级奖励状态变更
[10019] = "CharAdvanceRewardState", // 角色进阶奖励状态变更
[10020] = "Achievement", // 成就进度变更
[10021] = "AchievementState", // 成就待领取红点提示
[10022] = "Skin", // 角色获得新皮肤,如果是重复获取,将发送转换数据
[10023] = "SkinChange", // 角色装备的皮肤发生改变
[10024] = "HandbookInfo", // 图鉴数据发生变化
[10025] = "MonthlyCardRewards", // 月卡奖励通知
[10026] = "QuestState", // 任务红点奖励notify
[10027] = "MallPackageState", // 礼包商城免费商品红点notify
[10028] = "Dictionary", // 字典数据变更
[10029] = "Activity", // 活动数据变化
[10030] = "ActivityQuest", // 活动任务数据变化
[10031] = "CharAffinityRewardState", // 角色好感度奖励最终值
[10032] = "Nil", // 道具超发进邮件
[10033] = "InfinityTowerRewards", // 无尽塔是否有奖励可领最终值
[10034] = "UI32", // 手机新聊天变化
[10035] = "ChangeInfo", // 角色碎片溢出
[10036] = "ActivityLogin", // 七日登录活动通知
[10037] = "TowerBookFateCardCollectNotify", // 新获得的命运卡数据
[10038] = "TowerBookFateCardRewardChangeNotify", // 命运卡图鉴奖励变化
[10039] = "ChangeInfo", // 区域boss挑战模式门票变更通知最终值
[10040] = "HonorChangeNotify", // 荣誉称号变更通知(最终值,多个Notify以最后一个为准)
};
private readonly Dictionary<NetMsgId, MethodInfo> handlers = [];
private readonly Dictionary<Type, object?> handlerInstances = [];
public ProtocolHandlerFactory()
{
}
public void RegisterInstance(Type t, object? inst)
{
handlerInstances.Add(t, inst);
foreach (var method in t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where(x => x.GetCustomAttribute<ProtocolHandlerAttribute>() is not null))
{
var attr = method.GetCustomAttribute<ProtocolHandlerAttribute>()!;
if (handlers.ContainsKey(attr.NetMsgId))
{
continue;
}
handlers.Add(attr.NetMsgId, method);
Log.Information($"Loaded {method.Name} for {attr.NetMsgId}");
}
}
public object? Invoke(NetMsgId msgId, IMessage? req)
{
var handler = GetProtocolHandler(msgId);
if (handler is null)
return null;
handlerInstances.TryGetValue(handler.DeclaringType!, out var inst);
return handler.Invoke(inst, [req]);
}
public MethodInfo? GetProtocolHandler(NetMsgId msgId)
{
handlers.TryGetValue(msgId, out var handler);
return handler;
}
public static Type? GetRequestPacketTypeByProtocol(NetMsgId msgId)
{
if (!ProtocolHandlerFactory.NetMsgIdToNameMappings.ContainsKey((short)msgId))
{
return null;
}
string msgIdClassName = ProtocolHandlerFactory.NetMsgIdToNameMappings[(short)msgId];
Type packetClassType = Assembly.GetAssembly(typeof(LoginReq))!.GetTypes().Where(x => x.Name == msgIdClassName).SingleOrDefault();
return packetClassType;
}
}
public abstract class ProtocolHandlerBase : IHostedService
{
private IProtocolHandlerFactory protocolHandlerFactory;
public ProtocolHandlerBase(IProtocolHandlerFactory _protocolHandlerFactory)
{
protocolHandlerFactory = _protocolHandlerFactory;
}
public Task StartAsync(CancellationToken cancellationToken)
{
protocolHandlerFactory.RegisterInstance(GetType(), this);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
internal static class ServiceExtensions
{
public static void AddProtocolHandlerFactory(this IServiceCollection services)
{
services.AddSingleton<IProtocolHandlerFactory, ProtocolHandlerFactory>();
}
public static void AddProtocolHandlerGroup<T>(this IServiceCollection services) where T : ProtocolHandlerBase
{
services.AddHostedService<T>();
}
public static void AddProtocolHandlerGroupByType(this IServiceCollection services, Type type)
{
services.AddTransient(typeof(IHostedService), type);
}
}
}

View File

@@ -0,0 +1,26 @@
using Novaria.Common.Core;
using Proto;
using Serilog;
namespace Novaria.GameServer.Controllers.Api.ProtocolHandlers
{
public class ResidentShop : ProtocolHandlerBase
{
public ResidentShop(IProtocolHandlerFactory protocolHandlerFactory) : base(protocolHandlerFactory)
{
}
[ProtocolHandler(NetMsgId.resident_shop_get_req)] // req id goes here
public Packet ResidentShopGetHandler(ResidentShopGetReq req)
{
ResidentShopGetResp resp = new ResidentShopGetResp();
resp.Shops.Add(new Proto.ResidentShop() { Id = 1, RefreshTime = 1 });
resp.Shops.Add(new Proto.ResidentShop() { Id = 2, RefreshTime = 1 });
resp.Shops.Add(new Proto.ResidentShop() { Id = 3, RefreshTime = 1 });
return Packet.Create(NetMsgId.resident_shop_get_succeed_ack, resp);
}
}
}

View File

@@ -0,0 +1,63 @@
using Google.Protobuf;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Novaria.Common.Crypto;
using Novaria.Common.Util;
using Pb;
using Serilog;
using System.Text.Json;
namespace Novaria.GameServer.Controllers
{
[ApiController]
[Route("/meta")]
public class MetaController : ControllerBase
{
[Route("serverlist.html")]
public IActionResult GetServerlist()
{
ServerListMeta serverListMeta = new ServerListMeta()
{
Version = 37,
Status = 0,
Message = "测试尚未开始预计开服时间1月9日11:00",
ReportEndpoint = "https://nova.yostar.cn/report/",
};
serverListMeta.Agent.Add(new ServerAgent()
{
Name = "启明测试",
Addr = "https://nova.yostar.cn/agent-zone-1/",
Status = 0,
Zone = 1,
});
// seems like IV is sent as the first 16 bytes, and key is hardcoded in client
// response = [IV, protobuf_serialized_data]
byte[] encrypted_content = AeadTool.EncryptAesCBCInfo(AeadTool.DEFAULT_SERVERLIST_KEY, AeadTool.DEFAULT_SERVERLIST_IV, serverListMeta.ToByteArray());
byte[] response = Utils.CombineByteArrays(AeadTool.DEFAULT_SERVERLIST_IV, encrypted_content);
Log.Information("Response bytes:");
return File(response, "text/html");
}
[Route("and.html")]
public IActionResult GetAndroid()
{
string diffJson = System.IO.File.ReadAllText($"../../../../Novaria.GameServer/and.json"); // disgusting pathing, but "not hardcoded" now ig
ClientDiff clientDiff = JsonConvert.DeserializeObject<ClientDiff>(diffJson);
Console.WriteLine(clientDiff.Diff.Count);
Console.WriteLine(clientDiff.Diff[0].FileName);
byte[] encrypted_content = AeadTool.EncryptAesCBCInfo(AeadTool.DEFAULT_SERVERLIST_KEY, AeadTool.DEFAULT_AND_IV, clientDiff.ToByteArray());
byte[] response = Utils.CombineByteArrays(AeadTool.DEFAULT_AND_IV, encrypted_content);
return File(response, "text/html");
}
}
}

View File

@@ -0,0 +1,300 @@
using Microsoft.AspNetCore.Mvc;
using Serilog;
// rest of the random as packets during sdk
namespace Novaria.GameServer.Controllers
{
[ApiController]
[Route("/")]
public class SDKController : ControllerBase
{
[HttpPost("user/oauth2/v2/grant")]
public IResult OAuthGrant()
{
return Results.Content(
@"
{
""msg"": ""夏萝莉是小楠梁"",
""status"": 106,
""type"": ""A""
}",
"application/json"
);
}
[Route("")]
public IResult GetNothing()
{
return Results.Text(@"
{
""Code"": 200,
""Data"": ""Hello world"",
""Msg"": ""OK""
}
");
}
[Route("health-game/identity-auth")]
public IResult PostIdentityAuth()
{
Log.Information("post login received!");
string jsonResponse = @"
{
""Code"": 200,
""Data"": {
""Identity"": {
""BirthDate"": """",
""IDCard"": ""123*********34567*"",
""PI"": """",
""RealName"": ""**"",
""State"": 1,
""Type"": 0,
""Underage"": false
}
},
""Msg"": ""OK""
}
";
return Results.Text(jsonResponse, "application/json");
}
[Route("common/config")]
public IResult PostCommonConfig()
{
return Results.Text(@"
{
""Code"": 200,
""Data"": {
""AppConfig"": {
""AppropriateAge"": {
""Level"": ""18+"",
""Msg"": ""《星塔旅人》游戏适龄提示\n1、本游戏是一款玩法简单的角色扮演游戏适用于年满18周岁及以上的用户。\n2、本游戏基于架空的故事背景和幻想世界观剧情简单且积极向上没有基于真实历史和现实事件的改编内容。游戏玩法基于肢体操作鼓励玩家通过训练达成目标。游戏中有少量自定义文字系统该社交系统遵循相关法律法规进行管理。\n3、本游戏中有用户实名认证系统对年满18周岁及以上的用户开放使用18周岁以下的身份信息认证账号将无法进入游戏。""
},
""Captcha"": {
""AppID"": 191947906,
""Enable"": true
},
""DestroyUser"": {
""Days"": 15,
""Enable"": true
},
""DetectionAddress"": {
""Auto"": {
""DNS"": null,
""HTTP"": [
""https://static-stellasora.yostar.net"",
""https://nova.yostar.cn"",
""https://sdk-api.yostar.cn""
],
""MTR"": [
""https://static-stellasora.yostar.net"",
""https://nova.yostar.cn"",
""https://sdk-api.yostar.cn""
],
""PING"": null,
""TCP"": null
},
""Enable"": true,
""Internet"": ""https://www.baidu.com""
},
""EnableTextReview"": true,
""NicknameReg"": ""^[A-Za-z0-9一-龥]{2,16}$"",
""Passport"": {
""DestroyDays"": 0,
""ModifyEmailDays"": 30,
""ModifyMobileDays"": 30,
""Prefix"": ""YS""
},
""PassportPopup"": {
""Enable"": false,
""Text"": """"
},
""SLS"": {
""AccessKeyID"": ""7b5d0ffd0943f26704fc547a871c68b1b5d56b5c9caeb354205b81f445d7af59"",
""AccessKeySecret"": ""4a5e9cc8a50819290c9bfa1fedc79da7c50e85189a05eb462a3d28a7688eabb0"",
""ENABLE"": true
},
""Share"": {
""CaptureScreen"": {
""AutoCloseDelay"": 0,
""Enabled"": false
},
""PengYouQuan"": {
""AppID"": """",
""Enabled"": false,
""UniversalLink"": """"
},
""QQ"": {
""AppID"": """",
""Enabled"": false,
""UniversalLink"": """"
},
""Qzone"": {
""AppID"": """",
""Enabled"": false,
""UniversalLink"": """"
},
""Sort"": null,
""WeiXin"": {
""AppID"": """",
""Enabled"": false,
""UniversalLink"": """"
},
""Weibo"": {
""AppKey"": """",
""Enabled"": false,
""RedirectURL"": """",
""UniversalLink"": """"
}
},
""ThirdInfoShareList"": ""https://account.yostar.cn/cn-nova/shared_list"",
""Version"": {
""ChildPrivacyAgreement"": ""0.1"",
""ErrorCode"": ""4.3"",
""PrivacyAgreement"": ""0.1"",
""UserAgreement"": ""0.1"",
""UserDestroy"": ""0.1""
},
""WechatAppID"": """"
},
""ChannelConfig"": {
""Adjust"": {
""AppID"": ""tdzg1orirlkw"",
""Debug"": false,
""Enable"": true,
""EventTokens"": {
""#app_crash"": ""z9mk0z"",
""#app_second_retention"": ""joj939"",
""#app_seventh_retention"": ""cccp5a"",
""#asa_attribution"": ""c9w7x5"",
""#overwrite_install"": ""nnlh0v"",
""chapter0_1_complete"": ""i0b43c"",
""chapter0_2_complete"": ""iby4fp"",
""daily_mission_complete"": ""e57suj"",
""newbie_tutorial"": ""fx0u1h"",
""normal_tutorial"": ""1vebid"",
""role_create"": ""xiwizs"",
""role_login"": ""msxnvi"",
""setting_choice"": ""tdiya8"",
""ysdk_account_bind"": ""yimfis"",
""ysdk_account_create"": ""yimfis"",
""ysdk_clear_cache"": ""blu5v6"",
""ysdk_del_account"": ""dlbhch"",
""ysdk_del_account_intention"": ""zakuij"",
""ysdk_err"": ""qo4nog"",
""ysdk_init"": ""v32wb9"",
""ysdk_pay_illegal_currency"": ""jz0jhj"",
""ysdk_role_info_upload"": ""vr4gzs"",
""ysdk_user_login"": ""psim9y"",
""ysdk_user_open_review"": ""htiqns"",
""ysdk_user_open_survey"": ""wvbzk1"",
""ysdk_user_paid_level"": ""p52k29"",
""ysdk_user_pay_canceled"": ""qnta18"",
""ysdk_user_pay_checkout"": ""8x4g6e"",
""ysdk_user_pay_failed"": ""n99xq6"",
""ysdk_user_pay_intention"": ""ockxw2"",
""ysdk_user_share"": ""2tbkrf"",
""ysdk_user_share_intention"": ""gh5qc8"",
""ysdk_user_switch_account"": ""e23y7q""
}
},
""AiHelp"": {
""DisplayType"": ""Browser"",
""ServiceInterfaceURL"": """",
""ServiceURL"": ""https://account.yostar.cn/contact""
},
""Debug"": 0,
""JPush"": {
""Debug"": false,
""Key"": """"
},
""Login"": {
""Default"": ""onekey"",
""EnableList"": [
""mobile"",
""onekey""
]
},
""OneKeyLoginSecret"": ""2c6d738eaea2a59ac405bfbd484f7aeac770ff4ed89a5a03d6493d84b2dc265ec5364dbf270e11980252c80ed5de5dfd8fda1c0ad4fc1cb2c8c08b41083bea50033925af6c4e833eaa7ede488ae974ef829e9bd08d19b6f42399d597485d8b2fb7d7ebbd73c79045ff6f7bf141b7a3322944916fa00401a522862e554f414658189ed0e843e4d6a24b0a6384932d5058c8106f54599b2679674fd542aaba0f63b09710a5757d03d79bcbcc49ee523198ca6d136ea5129d98f9345039385b8744452a089b5341cb85a3dd21396e4f4ac5808ea75f07c6134c3632947adb1f643508090aa6ac418b4f714834685cda9f40718234272b6bd7d1617a304bb47601f82c690ea0dff12cf24cccab982a45557452b7096f422862a506cbfc6cb0024a1a0367e387ab58d27e87af47eb1c6d8a9bdf377a00afe40282ac58e4a4c4b90d489a5c4590fe6f78ed5ffbaa67efa00e98"",
""Udata"": {
""Enable"": true,
""MSUAKey"": ""-----BEGIN CERTIFICATE-----\nMIIFmzCCA4OgAwIBAgIDAP1zMA0GCSqGSIb3DQEBCwUAMIGAMQswCQYDVQQGEwJD\nTjEQMA4GA1UECAwHQmVpamluZzEMMAoGA1UECgwDTVNBMREwDwYDVQQLDAhPQUlE\nX1NESzEeMBwGA1UEAwwVY29tLmJ1bi5taWl0bWRpZC5zaWduMR4wHAYJKoZIhvcN\nAQkBFg9tc2FAY2FpY3QuYWMuY24wHhcNMjQxMTEyMTY1NDA2WhcNMjUxMTEzMTY1\nNDA2WjCBjDELMAkGA1UEBhMCQ04xEDAOBgNVBAgMB0JlaWppbmcxEDAOBgNVBAcM\nB0JlaWppbmcxDzANBgNVBAoMBnlvc3RhcjEjMCEGA1UEAwwaY29tLlJvYW1pbmdT\ndGFyLlN0ZWxsYVNvcmExIzAhBgkqhkiG9w0BCQEWFHNvbmcubGl1QHlvLXN0YXIu\nY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAshgvU9xluAUrL51T\nam/Ph5Z4yQEL0z3ex9d5tX/eOblLFGGDGmmbiAmI2+Knn2VMkIMsQVE2HwTk6RO3\nHHXDaGYAqdOUU54N9M75HOKY2sCSJDXiAtjRpTe0arjuCmv9Oi0rquTfm3y+/BND\nMGQIp6MUrMH/8jWOUdSydm9yOwZ1u61Um+ymwdEOM+SpridOIT1ZewbUnX1Z750V\nUwQ0Zv3DIMrqRJfgghlvB1Ho8zx3R3h0Sta8e37sECPuY4DJg2ETV2Q+AJidLwpb\nq0EeWrzdsTrsQfB27QfpUIrbDZ0Ar2GNV6eLOxbX5k+icwzUP/70YF8WKzmlRX6G\nP7oKzvkn2bano/pnSkdyoDmgAO+bfM3CXzOlhq2zyZd8R3Nm4Q+JBsEZEwHBye1k\nDiK1s/OQjwnyKGIfJ2ZQZbAjWpiSYej5dpwLZp3zLQ7mGSaP9kCuifyhbcsLGCHp\nFOn4kbCWmrpwX+bA/SMW7yXEUQfh0QrMkimBIPM+15YfdHx4pn1IE9BreOP6Pp8z\n4ewqvQMunVrTMWBce/GdXqWqTiXVDaWViBE/MEB64iZmydfE5YnRkQFvOZFtpGtn\nK+LuhHevCUGvpzfpG0tHQ3rrzgRjrIqqgsuWngNKM/PB0VliIGtNjy02WxLOD3WK\nzcx1giEo55N+CqcMMWCVJ+mactcCAwEAAaMQMA4wDAYDVR0TAQH/BAIwADANBgkq\nhkiG9w0BAQsFAAOCAgEACgZXL1JPsGlr1lVwbLTun3I8nZGAVXr3yhE3kcUYYX/F\n+dgyDlJmC8ZRNzhvAUPcUrsaic8s7JbMuTZzCLdw2fAkQWEs3nuN8zhZ3Lh1o2t9\n6t0V7ZnrahJn3OyY0XFfbLAuDtFWedxeNwSPL0hEVo13luCC/1N8dSZje/lEaTty\ngRTagbkMpHIDryOR5mPEob4UtSLdkcyWWNiuhvNFhY7TwXbPH6Mu/I9V79wwIkrw\nb45SdFgWYDPaNbBhWnvmKzXOZP1A+6x7wHonbvWf3oEPL+d+xyUXMpAEVnCpQS+A\nUgst9HuQcgEbyi6waQ2I21wkXJ4ajMM0TeHUfJDI/S/1mur8ijFQ6BToPUwPZ158\nxQNaeQalPNHiFuCuJvQlbkrlJVZPU0VKo+tTh2xxD0h7CyqwERV05h3RyS5auDf2\nQHvijp55H9mej8/K79rvwlMLvNI93FCjnWCgmy5/4AiFsm8R+gnEfVSpiSOzKYqS\ntLZ1HBaHFIgvAvW0dN1Qz8PXnYMRt7N6/fg0ZeXRiUbmGwuTxdfCBP5OyyWpIqtN\n91cpzIH8lYx19oEV5fNWMBbUjbZeQNmcSaz2NSKoM3PpFBkwW/bedqeUCuhj/uT+\nYNjvej3TMTXAi9Ln4eajGBIkpZlmtbnPAA4L4Kq2BedHbebKDVg+Ga5Eyy6Q/Ng=\n-----END CERTIFICATE-----""
}
}
},
""Msg"": ""OK""
}
");
}
[Route("heartbeat/pulse")]
public IResult PostsHeartbeatPulse()
{
return Results.Text(@"
{
""Code"": 200,
""Data"": {
""Delay"": 3600
},
""Msg"": ""OK""
}
");
}
[Route("common/geo-ip")]
public IResult PostGeoIP()
{
return Results.Text(@"
{
""Code"": 200,
""Data"": {
""Prov"": ""悠星总部""
},
""Msg"": ""OK""
}
");
}
[HttpPost("daq/track/v2")]
public IResult PostDaqTrackV2()
{
return Results.Text(@"
{
""code"": 200,
""data"": {
""ts"": 1736783123685
},
""msg"": ""OK""
}
");
}
[HttpGet("daq/init/v2")]
public IResult GetDaqInitV2([FromQuery] int os, [FromQuery] string? code = null)
{
Console.WriteLine("os", os);
return Results.Text(@"
{
""code"": 200,
""data"": {
""ts"": 1736783033783
},
""msg"": ""OK""
}
");
}
[Route("common/client-code")]
public IResult PostClientCode()
{
return Results.Text(@"
{
""Code"": 200,
""Msg"": ""OK""
}
");
}
}
}

View File

@@ -0,0 +1,137 @@
using Microsoft.AspNetCore.Mvc;
using Serilog;
namespace Novaria.GameServer.Controllers
{
[ApiController]
[Route("/user")]
public class UserController : ControllerBase
{
[Route("login")]
public IResult PostLogin()
{
Log.Information("post login received!");
string jsonResponse = @"
{
""Code"": 200,
""Data"": {
""IsNew"": true,
""IsTestAccount"": false,
""User"": {
""DestroyState"": 0,
""ID"": 1,
""PID"": ""CN-NOVA"",
""State"": 1,
""Token"": ""f94d936f7235f84493564ee0282e845cccd44828""
}
},
""Msg"": ""OK""
}
";
return Results.Text(jsonResponse, "application/json");
}
[Route("quick-login")]
public IResult PostQuickLogin()
{
Log.Information("post login received!");
string jsonResponse = @"
{
""Code"": 200,
""Data"": {
""Destroy"": {
""DestroyAt"": 0
},
""Identity"": {
""BirthDate"": """",
""IDCard"": ""123*********34567"",
""PI"": """",
""RealName"": ""**"",
""State"": 1,
""Type"": 0,
""Underage"": false
},
""IsTestAccount"": false,
""Keys"": [
{
""NickName"": ""123****4567"",
""Type"": ""mobile""
}
],
""User"": {
""DestroyState"": 0,
""ID"": 1,
""PID"": ""CN-NOVA"",
""State"": 1,
""Token"": ""f94d936f723asdasd5f84493564ee0282e845cccd44828""
},
""Yostar"": {
""CreatedAt"": 1735902322,
""DefaultNickName"": """",
""ID"": 1,
""NickName"": ""seggs"",
""Picture"": """",
""State"": 1
}
},
""Msg"": ""OK""
}
";
return Results.Text(jsonResponse, "application/json");
}
[Route("detail")]
public IResult PostDetail()
{
string jsonResponse = @"
{
""Code"": 200,
""Data"": {
""Destroy"": {
""DestroyAt"": 0
},
""Identity"": {
""BirthDate"": """",
""IDCard"": ""123*********34567*"",
""PI"": """",
""RealName"": ""**"",
""State"": 1,
""Type"": 0,
""Underage"": false
},
""IsTestAccount"": false,
""Keys"": [
{
""NickName"": ""123****4567"",
""Type"": ""mobile""
}
],
""User"": {
""DestroyState"": 0,
""ID"": 1,
""PID"": ""CN-NOVA"",
""State"": 1,
""Token"": ""f9s243e483e3e322138""
},
""Yostar"": {
""CreatedAt"": 1,
""DefaultNickName"": """",
""ID"": 1,
""NickName"": ""seggs"",
""Picture"": """",
""State"": 1
}
},
""Msg"": ""OK""
}
";
return Results.Text(jsonResponse, "application/json");
}
}
}