using BrewMonster; using BrewMonster.Managers; using BrewMonster.Scripts.Player; using BrewMonster.Scripts.Skills; using CSNetwork.C2SCommand; using CSNetwork.GPDataType; using CSNetwork.Protocols; using CSNetwork.Protocols.RPCData; using System; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO; using System.Numerics; using System.Runtime.InteropServices; using System.Text; using System.Text; using System.Threading.Tasks; using CommandID = CSNetwork.GPDataType.CommandID; namespace CSNetwork { public class GameSession : IDisposable { private static IPrefixedLogger _logger = LoggerFactory.GetLogger(nameof(GameSession)); // Get class-specific logger private NetworkManager _networkManager; private string _host; private int _port; private string _username; private string _password; private int _currentUserId = -1; // To store the UserID after successful login private int m_iCharID; private int m_idLastSelTarget = 0; // ID of selected item last time CECStringTab m_ErrorMsgs; // State management for async operations and callbacks private Action _loginCallback; private Action> _roleListCallback; private List _accumulatedRoles; private Action _selectRoleCallback; private RoleInfo _selectedRole; public bool IsConnected => _networkManager?.IsConnected ?? false; #if UNITY_EDITOR public bool isDebug; public bool IsDebug { get => isDebug; set => isDebug = value; } #endif public GameSession() { _networkManager = new NetworkManager(); _networkManager.ProtocolReceived += OnProtocolReceived; _networkManager.ErrorOccurred += OnErrorOccurred; _networkManager.Disconnected += OnDisconnected; } public void SetLogPath(string path) { LoggerFactory.SetFileLoggerImplementation(new FileLogger()); _logger = LoggerFactory.GetCustomLogger(path, nameof(GameSession) + GetHashCode(), LoggerType.File); _networkManager.SetLogPath(path); } /// /// Connects to the game server asynchronously. /// /// Server hostname or IP address. /// Server port. /// Task representing the asynchronous connect operation. Check IsConnected property or handle Disconnected event for status. public async Task ConnectAsync(string host, int port) { if (IsConnected) { _logger.Log(LogType.Warning, "ConnectAsync called but already connected."); return; } _host = host; _port = port; _logger.Log(LogType.Info, $"Attempting to connect to {_host}:{_port}..."); try { await _networkManager.ConnectAsync(_host, _port); if (IsConnected) { _logger.Log(LogType.Info, "Connection established."); } else { _logger.Log(LogType.Warning, "Connection failed after ConnectAsync completed (check NetworkManager logs/events)."); } } catch (Exception ex) { _logger.Log(LogType.Error, $"Connection exception: {ex.Message}"); _logger.LogException(ex); OnDisconnected(); } } public void Disconnect() { _networkManager.Disconnect(); } /// /// Initiates the login process asynchronously. /// /// Account username. /// Account password. /// Action invoked with true on successful login (OnlineAnnounce received), false otherwise. public void LoginAsync(string username, string password, Action callback) { if (!IsConnected) { _logger.Log(LogType.Warning, "LoginAsync called but not connected."); callback?.Invoke(false); return; } if (_loginCallback != null) { _logger.Log(LogType.Warning, "LoginAsync called while another login is already in progress."); callback?.Invoke(false); return; } _username = username; _password = password; _loginCallback = callback; _currentUserId = -1; // Reset user ID _logger.Log(LogType.Info, $"Initiating login for user '{_username}'..."); } /// /// Initiates fetching the role list asynchronously. Requires successful login. /// /// Action invoked with the complete list of roles, or null/empty list on failure. public void GetRoleListAsync(Action> callback) { if (!IsConnected) { _logger.Log(LogType.Warning, "GetRoleListAsync called but not connected."); callback?.Invoke(null); return; } if (_currentUserId == -1) { _logger.Log(LogType.Warning, "GetRoleListAsync called but not logged in."); callback?.Invoke(null); return; } if (_roleListCallback != null) { _logger.Log(LogType.Warning, "GetRoleListAsync called while another role list retrieval is already in progress."); callback?.Invoke(null); return; } _roleListCallback = callback; _accumulatedRoles = new List(); _logger.Log(LogType.Info, "Requesting role list..."); RequestRoleListInternal(); } public RoleInfo GetRoleInfo() { return _selectedRole; } public void SelectRoleAsync(RoleInfo role, Action callback) { _selectedRole = role; _selectRoleCallback = callback; SetCharacterID(role.roleid); SendProtocol(new selectrole() { Roleid = role.roleid, Flag = 0 }); } public void EnterWorldAsync(RoleInfo role, Action callback) { SendProtocol(new enterworld() { Roleid = _selectedRole.roleid, Provider_link_id = 0, }, callback); } public void RequestDropIvtrItem(byte index, int amount) { gamedatasend gamedatasendRequest = new gamedatasend(); gamedatasendRequest.Data = C2SCommandFactory.CreateDropIvtrItem(index, amount); SendProtocol(gamedatasendRequest); } public void RequestDropEquipItem(byte index) { gamedatasend gamedatasendRequest = new gamedatasend(); gamedatasendRequest.Data = C2SCommandFactory.CreateDropEquipItem(index); SendProtocol(gamedatasendRequest); } public void RequestPickupItem(int idItem, int tid) { gamedatasend gamedatasendRequest = new gamedatasend(); gamedatasendRequest.Data = C2SCommandFactory.CreatePickupItem(idItem, tid); SendProtocol(gamedatasendRequest); } public void RequestInventoryAsync(byte byPackage, Action callback) { gamedatasend gamedatasendRequest = new gamedatasend(); gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.CreateGetInventoryDetail(byPackage); SendProtocol(gamedatasendRequest, callback); } public void RequestQueryPlayerCash() { gamedatasend gamedatasendRequest = new gamedatasend(); gamedatasendRequest.Data = C2SCommandFactory.CreateQueryPlayerCash(); SendProtocol(gamedatasendRequest); } public void RequestCheckSecurityPassWd(string password) { gamedatasend gamedatasendRequest = new gamedatasend(); gamedatasendRequest.Data = C2SCommandFactory.CreateCheckSecurityPassWd(password); SendProtocol(gamedatasendRequest); } public void RequestEquipItem(byte iIvtrIdx, byte iEquipIdx, Action callback) { gamedatasend gamedatasendRequest = new gamedatasend(); gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.CreateEquipItem(iIvtrIdx, iEquipIdx); SendProtocol(gamedatasendRequest, callback); } public void RequestReviveBase(int param = 0) { gamedatasend gamedatasendRequest = new gamedatasend(); gamedatasendRequest.Data = C2SCommandFactory.CreateReviveBase(param); SendProtocol(gamedatasendRequest); } public void RequestReviveItem(int param = 0) { gamedatasend gamedatasendRequest = new gamedatasend(); gamedatasendRequest.Data = C2SCommandFactory.CreateReviveBase(param); SendProtocol(gamedatasendRequest); } public void RequestReviveByPlayer(int param = 0) { gamedatasend gamedatasendRequest = new gamedatasend(); gamedatasendRequest.Data = C2SCommandFactory.CreateReviveBase(param); SendProtocol(gamedatasendRequest); } public void RequestMallShopping(uint count, CMD_MallShopping.goods[] goodsArray) { gamedatasend gamedatasendRequest = new gamedatasend(); gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.CreateGetMallShopping(count, goodsArray); SendProtocol(gamedatasendRequest); } public void RequestOwnItemInfoAsync( byte byPackage, byte bySlot, int type, int expire_date, int state, uint count, ushort crc, ushort content_length, byte[] content, Action callback) { gamedatasend gamedatasendRequest = new gamedatasend(); gamedatasendRequest.Data = C2SCommandFactory.CreateOwnItemInfo(byPackage, bySlot, type, expire_date, state, count, crc, content_length, content); SendProtocol(gamedatasendRequest, callback); } // --- Protocol Sending --- public void SendProtocol(Protocol protocol, Action complete = null) { if (IsConnected) { _logger.Log(LogType.Debug, $"Sending protocol: {protocol.GetType().Name} (Detail: {protocol.ToString})"); _networkManager.Send(protocol); complete?.Invoke(); } else { _logger.Log(LogType.Warning, $"Cannot send protocol ({protocol.GetType().Name}), not connected."); } } // --- Event Handlers (from NetworkManager) --- private void OnProtocolReceived(Protocol protocol) { _logger.Log(LogType.Debug, $"Received protocol: {protocol.GetType().Name} (Type: {protocol.Type})"); if (protocol is null) return; // Route protocol to appropriate handler switch (protocol.GetPType()) { case ProtocolType.PROTOCOL_CHALLENGE: HandleChallenge((challenge)protocol); break; case ProtocolType.PROTOCOL_KEYEXCHANGE: HandleKeyExchange((KeyExchange)protocol); break; case ProtocolType.PROTOCOL_ONLINEANNOUNCE: HandleOnlineAnnounce((onlineannounce)protocol); break; case ProtocolType.PROTOCOL_ROLELIST_RE: HandleRoleListResponse((RoleListResponse)protocol); break; // Add cases for other protocols GameSession might need to handle case ProtocolType.PROTOCOL_SELECTROLE_RE: HandleSelectRoleResponse((SelectRole_Re)protocol); //_networkManager.IgnoreBytes = 2; break; case ProtocolType.PROTOCOL_S2CGAMEDATASEND: case ProtocolType.PROTOCOL_GAMEDATASEND: HandleServerDataSend((gamedatasend)protocol); break; case ProtocolType.PROTOCOL_CHATMESSAGE: _logger.Log(LogType.Warning, $"HoangDev :ProtocolType.PROTOCOL_CHATMESSAGE {protocol.GetPType()}"); OnPrtcChatMessage(protocol, false); break; default: _logger.Log(LogType.Warning, $"Received unhandled protocol type: {protocol.GetPType()}"); break; } } private void HandleServerDataSend(gamedatasend protocol) { int lenghtHeader = Marshal.SizeOf(); var pDataBuf = new byte[protocol.Data.ByteArray.Length - lenghtHeader]; var byteArrHeader = new byte[lenghtHeader]; long dwDataSize = protocol.Data.Size; if (dwDataSize < Marshal.SizeOf()) { _logger.Error($"### GameDataSend: size invalid {dwDataSize}"); return; } dwDataSize -= Marshal.SizeOf(); // subtract the header size (ushort) for (int i = 0; i < protocol.Data.ByteArray.Length; i++) { if (i < lenghtHeader) { byteArrHeader[i] = protocol.Data.ByteArray[i]; } else { pDataBuf[i - lenghtHeader] = protocol.Data.ByteArray[i]; } } var pCmdHeader = BitConverter.ToUInt16(byteArrHeader); //sss #if UNITY_EDITOR if (isDebug) { BMLogger.LogError($"### GameDataSend: CMDID {pCmdHeader}"); } #endif int iHostID = _selectedRole.roleid; switch (pCmdHeader) { case CommandID.PLAYER_INFO_2: case CommandID.PLAYER_INFO_3: case CommandID.PLAYER_INFO_4: case CommandID.PLAYER_INFO_2_LIST: case CommandID.PLAYER_INFO_3_LIST: case CommandID.PLAYER_INFO_23_LIST: break; case CommandID.PLAYER_INFO_1: case CommandID.PLAYER_ENTER_WORLD: case CommandID.PLAYER_ENTER_SLICE: case CommandID.PLAYER_INFO_1_LIST: case CommandID.PLAYER_INFO_00: case CommandID.SELF_INFO_1: EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERINFO, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader, iHostID, _selectedRole); break; case CommandID.OBJECT_MOVE: int lenghtDataType = Marshal.SizeOf(); byte[] arrByteData = GetBytes(pDataBuf, lenghtDataType, 0); int idObjMove = BitConverter.ToInt32(arrByteData); if (ISPLAYERID(idObjMove)) { EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERMOVE, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader, iHostID); } else if (ISNPCID(idObjMove)) { EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCMOVE, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); } break; case CommandID.OBJECT_STOP_MOVE: { int id1 = GPDataTypeHelper.FromBytes(pDataBuf); if (ISPLAYERID(id1)) { EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERSTOPMOVE, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); } else if (ISNPCID(id1)) { EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCSTOPMOVE, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); } break; } case CommandID.OBJECT_LEAVE_SLICE: { int id = GPDataTypeHelper.FromBytes(pDataBuf); if (ISPLAYERID(id)) { EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERRUNOUT, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); } else if (ISNPCID(id)) { EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCRUNOUT, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); } break; } case CommandID.OWN_IVTR_DATA: case CommandID.OWN_IVTR_DETAIL_DATA: case CommandID.GET_OWN_MONEY: case CommandID.CHANGE_IVTR_SIZE: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_IVTRINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, iHostID); break; case CommandID.EXG_IVTR_ITEM: case CommandID.MOVE_IVTR_ITEM: case CommandID.PLAYER_DROP_ITEM: case CommandID.EXG_EQUIP_ITEM: case CommandID.EQUIP_ITEM: case CommandID.MOVE_EQUIP_ITEM: case CommandID.UNFREEZE_IVTR_SLOT: case CommandID.PLAYER_EQUIP_TRASHBOX_ITEM: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ITEMOPERATION, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); break; case CommandID.PLAYER_CASH: { EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_IVTRINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, iHostID); break; } case CommandID.MATTER_INFO_LIST: EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTERINFO, (int)MANAGER_INDEX.MAN_MATTER, 0, pDataBuf, pCmdHeader); break; case CommandID.MATTER_ENTER_WORLD: EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTERENTWORLD, (int)MANAGER_INDEX.MAN_MATTER, 0, pDataBuf, pCmdHeader); break; case CommandID.PICKUP_ITEM: case CommandID.HOST_OBTAIN_ITEM: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PICKUPITEM, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); break; case CommandID.MATTER_PICKUP: EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PICKUPMATTER, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); break; case CommandID.PICKUP_MONEY: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PICKUPMONEY, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); break; case CommandID.HOST_CORRECT_POS: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_CORRECTPOS, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, iHostID); break; case CommandID.OWN_ITEM_INFO: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_OWNITEMINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, iHostID); break; case CommandID.PLAYER_DIED: EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERDIED, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); break; case CommandID.HOST_DIED: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_DIED, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); break; case CommandID.PLAYER_REVIVE: EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERREVIVE, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); break; case CommandID.NOTIFY_HOSTPOS: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_GOTO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); break; case CommandID.NPC_ENTER_SLICE: case CommandID.NPC_INFO_LIST: case CommandID.NPC_INFO_00: case CommandID.NPC_ENTER_WORLD: case CommandID.NPC_VISIBLE_TID_NOTIFY: EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCINFO, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader, dwDataSize); break; case CommandID.TASK_DATA: case CommandID.TASK_VAR_DATA: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_TASKDATA, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize); break; case CommandID.BE_HURT: case CommandID.HURT_RESULT: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_HURTRESULT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); break; case CommandID.OBJECT_ATTACK_RESULT: //int id = GPDataTypeHelper.FromBytes(pDataBuf); cmd_object_atk_result pCmdAtk = GPDataTypeHelper.FromBytes(pDataBuf); //BMLogger.LogError($"OBJECT_ATTACK_RESULT: npc ? " + ISNPCID(id)); if (ISPLAYERID(pCmdAtk.attacker_id)) EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERATKRESULT, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); else if (ISNPCID(pCmdAtk.attacker_id)) EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCATKRESULT, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); break; case CommandID.HOST_ATTACKRESULT: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ATKRESULT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); break; case CommandID.HOST_ATTACKED: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ATTACKED, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); break; case CommandID.ERROR_MESSAGE: _logger.Info($"### GameDataSend: ERROR_MESSAGE: {BitConverter.ToInt32(pDataBuf, 0)}"); cmd_error_msg pCmd = GPDataTypeHelper.FromBytes(pDataBuf); BMLogger.LogError("hOANGdEV : ERROR_MESSAGE pCmd.iMessage!=0 " + pCmd.iMessage); if (pCmd.iMessage != 0) { string szMsg = m_ErrorMsgs.GetWideString(pCmd.iMessage); if (string.IsNullOrEmpty(szMsg)) BMLogger.LogError("SERVER - unknown error !"); //else if (pCmd.iMessage != 2) //g_pGame->GetGameRun()->AddChatMessage(szMsg, GP_CHAT_MISC); } if (pCmd.iMessage == 2) { // Attack target is too far EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_TARGETISFAR, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); } else if (pCmd.iMessage == 20) { // Failed to cast skill //pGameRun->PostMessage(MSG_PM_CASTSKILL, MAN_PLAYER, 0, (DWORD)pDataBuf, pCmdHeader->cmd); } else if (pCmd.iMessage == 133 || pCmd.iMessage == 134) { // deal failed //pGameRun->PostMessage(MSG_HST_BUY_SELL_FAIL, MAN_PLAYER, 0, (DWORD)pDataBuf, pCmdHeader->cmd); } else if (pCmd.iMessage == 158) { // µ±Ç°»ãÂʲ»¶Ô£¬ÖØÐÂÈ¡»ãÂÊ //c2s_CmdGetCashMoneyRate(); } else if (pCmd.iMessage == 108 /*&& pGameRun->GetHostPlayer()->IsInKingService()*/) { /* CECGameUIMan* pGameUI = pGameRun->GetUIManager()->GetInGameUIMan(); if (pGameUI) pGameUI->EndNPCService();*/ } else if (pCmd.iMessage == 108 /*&& pGameRun->GetHostPlayer()->GetOfflineShopCtrl()->GetNPCSevFlag() != COfflineShopCtrl::NPCSEV_NULL*/ ) { /* CECGameUIMan* pGameUI = pGameRun->GetUIManager()->GetInGameUIMan(); if (pGameUI) pGameUI->EndNPCService();*/ } else if (pCmd.iMessage == 175) { //c2s_CmdQueryParallelWorld(); } else if (pCmd.iMessage == 6) { //AP_ActionEvent(AP_EVENT_CANNOTPICKUP); } break; case CommandID.SELECT_TARGET: case CommandID.UNSELECT: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SELTARGET, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); break; case CommandID.NPC_DIED: case CommandID.NPC_DIED2: EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCDIED, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); break; case CommandID.OBJECT_DISAPPEAR: { int lenghtDataType1 = Marshal.SizeOf(); byte[] arrByteData1 = GetBytes(pDataBuf, lenghtDataType1, 0); int idObjMove1 = BitConverter.ToInt32(arrByteData1); if (ISPLAYERID(idObjMove1)) EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERDISAPPEAR, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); else if (ISNPCID(idObjMove1)) EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCDISAPPEAR, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); break; } case CommandID.SELF_INFO_00: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_INFO00, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); break; case CommandID.SCENE_SERVICE_NPC_LIST: { CECHostSkillModel.Instance.RecvNPCServiceList(protocol.Data); break; } case CommandID.SKILL_DATA: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SKILLDATA, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); break; } } private void HandleSelectRoleResponse(SelectRole_Re protocol) { _logger.Info($"Select role response {protocol.result}"); _selectRoleCallback?.Invoke(_selectedRole); } private void OnErrorOccurred(string errorMessage) { _logger.Log(LogType.Error, $"Network Error: {errorMessage}"); FailLoginInProgress(errorMessage); FailRoleListInProgress(errorMessage); } private void OnDisconnected() { _logger.Log(LogType.Info, "Disconnected from server."); _currentUserId = -1; FailLoginInProgress("Disconnected"); FailRoleListInProgress("Disconnected"); } // --- Protocol Handling Logic --- private void HandleChallenge(challenge challenge) { if (_loginCallback == null || string.IsNullOrEmpty(_username)) { _logger.Log(LogType.Warning, "Received Challenge but not expecting it or username not set."); return; } _logger.Log(LogType.Info, "Handling Challenge..."); response response = new response(); byte[] usernameBytes = Encoding.ASCII.GetBytes(_username); byte[] passwordBytes = Encoding.ASCII.GetBytes(_password); response.identity.Replace(usernameBytes); response.Setup(new Octets(usernameBytes), new Octets(passwordBytes), challenge.nonce); uint clientId = 0xffffffff; byte[] clientIdBytes = BitConverter.GetBytes(clientId); response.cli_fingerprint.Replace(clientIdBytes); response.use_token = 0; _networkManager.SetNonce(response.response_data); SendProtocol(response); _logger.Log(LogType.Info, "Sent Response."); } private void HandleKeyExchange(KeyExchange keyExchange) { if (_loginCallback == null || string.IsNullOrEmpty(_username)) { _logger.Log(LogType.Warning, "Received KeyExchange but not expecting it."); return; } _logger.Log(LogType.Info, "Handling KeyExchange..."); keyExchange.Setup(_networkManager, _username); keyExchange.Blkickuser = 1; SendProtocol(keyExchange); _logger.Log(LogType.Info, "Sent KeyExchange acknowledgment/response."); } private void HandleOnlineAnnounce(onlineannounce announce) { if (_loginCallback == null) { _logger.Log(LogType.Warning, "Received OnlineAnnounce but not expecting it."); return; } _logger.Log(LogType.Info, $"Login successful! UserID: {announce.Userid}, LocalSID: {announce.Localsid}"); _currentUserId = announce.Userid; var callback = _loginCallback; _loginCallback = null; callback?.Invoke(true); } private void RequestRoleListInternal(int lastHandle = -1) { rolelist rolelistRequest = new rolelist(); rolelistRequest.Userid = _currentUserId; rolelistRequest.Localsid = 0; rolelistRequest.Handle = lastHandle; SendProtocol(rolelistRequest); //gamedatasend gamedatasendRequest = new gamedatasend(); //gamedatasendRequest.Data = C2SCommandFactory.CreatePlayerMove(); //SendProtocol(gamedatasendRequest); } private void HandleRoleListResponse(RoleListResponse response) { if (_roleListCallback == null || _accumulatedRoles == null) { _logger.Log(LogType.Warning, "Received RoleListResponse but not expecting it."); return; } _logger.Log(LogType.Debug, $"Received RoleListResponse. Handle: {response.handle}, Result: {response.result}, Count: {response.rolelist.Count}"); if (response.result == 0) { _accumulatedRoles.AddRange(response.rolelist); foreach (var role in response.rolelist) { try { string roleName = Encoding.UTF8.GetString(role.name.ByteArray, 0, role.name.Length); _logger.Log(LogType.Info, $" - Role ID: {role.roleid}, Name: {roleName}, Level: {role.level}"); } catch (Exception ex) { _logger.Log(LogType.Error, $" - Error decoding role name: {ex.Message}"); _logger.LogException(ex); } } if (response.handle != -1) { _logger.Log(LogType.Debug, $"Requesting next batch of roles (handle: {response.handle})..."); RequestRoleListInternal(response.handle); } else { _logger.Log(LogType.Info, $"Finished fetching roles. Total count: {_accumulatedRoles.Count}"); var callback = _roleListCallback; var result = _accumulatedRoles; _roleListCallback = null; _accumulatedRoles = null; callback?.Invoke(result); } } else { _logger.Log(LogType.Error, $"Role list retrieval failed. Result code: {response.result}"); FailRoleListInProgress($"Role list retrieval failed (Result: {response.result})"); } } // --- Helper methods for failure handling --- private void FailLoginInProgress(string reason) { if (_loginCallback != null) { _logger.Log(LogType.Error, $"Login failed: {reason}"); var callback = _loginCallback; _loginCallback = null; callback?.Invoke(false); } } private void FailRoleListInProgress(string reason) { if (_roleListCallback != null) { _logger.Log(LogType.Error, $"Role list retrieval failed: {reason}"); var callback = _roleListCallback; _roleListCallback = null; _accumulatedRoles = null; callback?.Invoke(null); } } // --- IDisposable Implementation --- private bool disposedValue = false; protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { if (_networkManager != null) { _logger.Log(LogType.Info, "[DUCK] Disposing GameSession and disconnecting..."); _networkManager.ProtocolReceived -= OnProtocolReceived; _networkManager.ErrorOccurred -= OnErrorOccurred; _networkManager.Disconnected -= OnDisconnected; _networkManager.Disconnect(); _networkManager.Dispose(); _networkManager = null; } _loginCallback = null; _roleListCallback = null; _accumulatedRoles = null; } disposedValue = true; } } public void Dispose() { Dispose(true); // GC.SuppressFinalize(this); } public bool ISPLAYERID(int id) { return id != 0 && (id & 0x80000000) == 0; } public bool ISNPCID(int id) => ((id & 0x80000000) != 0) && ((id & 0x40000000) == 0); private byte[] GetBytes(byte[] bytes, int length, int index) { byte[] arrByteData = new byte[length]; for (int i = 0; i < length; i++) { arrByteData[i] = bytes[i + index]; } return arrByteData; } public void c2s_CmdPlayerMove(in Vector3 vCurPos, in Vector3 vDest, int iTime, float fSpeed, int iMoveMode, ushort wStamp) { gamedatasend gamedatasend = new gamedatasend(); gamedatasend.Data = C2SCommandFactory.CreatePlayerMove(vCurPos, vDest, (ushort)iTime, fSpeed, (byte)iMoveMode, wStamp); SendProtocol(gamedatasend); } public void c2s_CmdCastSkill(int idSkill, byte byPVPMask, int iNumTarget, int aTargets) { gamedatasend gamedatasend = new gamedatasend(); gamedatasend.Data = C2SCommandFactory.CreatePlayerCastSkill(idSkill, byPVPMask, iNumTarget, aTargets); SendProtocol(gamedatasend); } public void c2s_SendCmdStopMove(in Vector3 vDest, float fSpeed, int iMoveMode, byte byDir, ushort wStamp, int iTime) { gamedatasend gamedatasend = new gamedatasend(); gamedatasend.Data = C2SCommandFactory.CreatePlayerStop(vDest, fSpeed, (byte)iMoveMode, byDir, wStamp, (ushort)iTime); SendProtocol(gamedatasend); } public void c2s_CmdSendEnterPKPrecinctint() { gamedatasend gamedatasend = new gamedatasend(); gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.ENTER_PK_PROTECTED); SendProtocol(gamedatasend); } public void SendChatData(byte cChannel, in string szMsg, int iPack, int iSlot) { publicchat publicChat = new publicchat(); publicChat.Channel = cChannel; publicChat.Roleid = m_iCharID; byte[] unicodeBytes = Encoding.Unicode.GetBytes(szMsg); publicChat.Msg.Replace(unicodeBytes); _logger.Log(LogType.Warning, $"HoangDev : publicChat {publicChat}"); SendProtocol(publicChat); } private void SetCharacterID(int iCharID) { m_iCharID = iCharID; } private void OnPrtcChatMessage(Protocol pProtocol, bool bCalledagain) { chatmessage p = (chatmessage)pProtocol; string strTemp = System.Text.Encoding.Unicode.GetString(p.Msg.ToArray(), 0, p.Msg.Length); _logger.Log(LogType.Warning, $"HoangDev : OnPrtcChatMessage :{strTemp}"); EventBus.Publish(new ChatMessageEvent(strTemp)); } public struct ChatMessageEvent { public string context; public ChatMessageEvent(string context) { this.context = context; } } public void c2s_SendCmdGetAllData(bool byPack, bool byEquip, bool byTask) { gamedatasend gamedatasend = new gamedatasend(); gamedatasend.Data = C2SCommandFactory.CreateGetAllDataCommand(byPack, byEquip, byTask); _logger.Log(LogType.Warning, $"[Dat]- SendCmdGetAllData {byPack},{byEquip},{byTask}"); SendProtocol(gamedatasend); } public void c2s_SendCmdNPCSevHello(int nid) { gamedatasend gamedatasend = new gamedatasend(); gamedatasend.Data = C2SCommandFactory.CreateNPCSevHelloDataCommand(nid); SendProtocol(gamedatasend); } public void c2s_CmdNormalAttack(byte byPVPMask) { gamedatasend gamedatasend = new gamedatasend(); gamedatasend.Data = C2SCommandFactory.CreateNormalAttackDataCmd(byPVPMask); SendProtocol(gamedatasend); } public void c2s_CmdCancelAction() { gamedatasend gamedatasend = new gamedatasend(); gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(CSNetwork.C2SCommand.CommandID.CANCEL_ACTION); SendProtocol(gamedatasend); } public void c2s_CmdUnselect() { gamedatasend gamedatasend = new gamedatasend(); gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(CSNetwork.C2SCommand.CommandID.UNSELECT); SendProtocol(gamedatasend); } public void c2s_CmdSelectTarget(int idTarget) { // Set selection first before server returns, so as to reduce the player waiting time. CECHostPlayer pHost = EC_ManMessageMono.Instance.GetECManPlayer.GetHostPlayer(); pHost.SetSelectedTarget(idTarget); if (m_idLastSelTarget != idTarget) { gamedatasend gamedatasend = new gamedatasend(); gamedatasend.Data = C2SCommandFactory.CreateSelectTarget(idTarget); SendProtocol(gamedatasend); m_idLastSelTarget = idTarget; } } } }