server-master/srcs/Master/Services/Sessions/SessionService.cs
2026-02-10 18:21:30 +01:00

439 lines
No EOL
18 KiB
C#

using System;
using System.Threading;
using System.Threading.Tasks;
using PhoenixLib.Logging;
using WingsAPI.Communication;
using WingsAPI.Communication.Sessions;
using WingsAPI.Communication.Sessions.Model;
using WingsAPI.Communication.Sessions.Request;
using WingsAPI.Communication.Sessions.Response;
namespace WingsEmu.Master.Sessions
{
public class SessionService : ISessionService
{
private static readonly SemaphoreSlim _semaphoreSlim = new(1, 1);
private readonly EncryptionKeyFactory _encryptionKeyFactory;
private readonly ISessionManager _sessionManager;
public SessionService(ISessionManager sessionManager, EncryptionKeyFactory encryptionKeyFactory)
{
_sessionManager = sessionManager;
_encryptionKeyFactory = encryptionKeyFactory;
}
public async ValueTask<SessionResponse> CreateSession(CreateSessionRequest request)
{
await _semaphoreSlim.WaitAsync();
try
{
Session existingSession = await _sessionManager.GetSessionByAccountId(request.AccountId);
if (existingSession is not null && existingSession.State != SessionState.Disconnected && existingSession.State != SessionState.ServerSelection)
{
Log.Debug($"[SESSION_SERVICE][CREATE_SESSION] A Session for account {request.AccountId} already exists");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
var session = new Session
{
Id = Guid.NewGuid().ToString().Replace("-", ""),
IpAddress = request.IpAddress,
AccountId = request.AccountId,
AccountName = request.AccountName,
Authority = request.AuthorityType,
State = SessionState.Disconnected,
EncryptionKey = _encryptionKeyFactory.CreateEncryptionKey()
};
bool created = await _sessionManager.Create(session);
if (!created)
{
Log.Debug($"[SESSION_SERVICE][CREATE_SESSION] Failed to save session of account {session.AccountId} with session id {session.Id} into redis");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
Log.Debug($"[SESSION_SERVICE][CREATE_SESSION] Successfully created session for account {session.AccountId} with session id {session.Id}");
return new SessionResponse
{
ResponseType = RpcResponseType.SUCCESS,
Session = session
};
}
finally
{
_semaphoreSlim.Release();
}
}
public async ValueTask<SessionResponse> GetSessionByAccountName(GetSessionByAccountNameRequest request)
{
await _semaphoreSlim.WaitAsync();
try
{
Session session = await _sessionManager.GetSessionByAccountName(request.AccountName);
if (session is null)
{
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
return new SessionResponse
{
ResponseType = RpcResponseType.SUCCESS,
Session = session
};
}
finally
{
_semaphoreSlim.Release();
}
}
public async ValueTask<SessionResponse> GetSessionByAccountId(GetSessionByAccountIdRequest request)
{
await _semaphoreSlim.WaitAsync();
try
{
Session session = await _sessionManager.GetSessionByAccountId(request.AccountId);
if (session is null)
{
Log.Debug($"[SESSION_SERVICE][GET_SESSION_BY_ACCOUNT_ID] Couldn't find session with account id: {request.AccountId}");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
Log.Debug($"[SESSION_SERVICE][GET_SESSION_BY_ACCOUNT_ID] Successfully found a session with account id: {request.AccountId}");
return new SessionResponse
{
ResponseType = RpcResponseType.SUCCESS,
Session = session
};
}
finally
{
_semaphoreSlim.Release();
}
}
public async ValueTask<SessionResponse> ConnectToLoginServer(ConnectToLoginServerRequest request)
{
await _semaphoreSlim.WaitAsync();
try
{
Session session = await _sessionManager.GetSessionByAccountId(request.AccountId);
if (session is null)
{
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_LOGIN_SERVER] Can't connect account with ID {request.AccountId} to login server (No session found)");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
if (session.State != SessionState.Disconnected && session.State != SessionState.CharacterSelection)
{
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_LOGIN_SERVER] Can't session with ID {request.AccountId} to login server (Already connected)");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
session.State = SessionState.ServerSelection;
session.HardwareId = request.HardwareId;
session.ClientVersion = request.ClientVersion;
bool updated = await _sessionManager.Update(session);
if (!updated)
{
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_LOGIN_SERVER] Failed to update session of {session.AccountId} into redis");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_LOGIN_SERVER] Successfully connected session of {session.AccountId} to login server");
return new SessionResponse
{
ResponseType = RpcResponseType.SUCCESS,
Session = session
};
}
finally
{
_semaphoreSlim.Release();
}
}
public async ValueTask<SessionResponse> Disconnect(DisconnectSessionRequest request)
{
await _semaphoreSlim.WaitAsync();
try
{
Session session = await _sessionManager.GetSessionByAccountId(request.AccountId);
if (session is null)
{
Log.Debug($"[SESSION_SERVICE][DISCONNECT] Can't disconnect session of account {request.AccountId} (Couldn't find session)");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
if (session.State == SessionState.CrossChannelAuthentication)
{
Log.Debug($"[SESSION_SERVICE][DISCONNECT] Can't disconnect session of account {request.AccountId} (CrossChannel or CharacterSelection)");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
if (request.ForceDisconnect == false)
{
if (session.EncryptionKey != request.EncryptionKey)
{
Log.Debug($"[SESSION_SERVICE][DISCONNECT] Can't disconnect session of account {request.AccountId} (CrossChannel or CharacterSelection)");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
}
session.State = SessionState.Disconnected;
session.CharacterId = 0;
session.ChannelId = 0;
session.ServerGroup = null;
bool saved = await _sessionManager.Update(session);
if (!saved)
{
Log.Debug($"[SESSION_SERVICE][DISCONNECT] Failed to update session of account {request.AccountId}");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
Log.Debug($"[SESSION_SERVICE][DISCONNECT] Successfully updated session of account {request.AccountId}");
return new SessionResponse
{
ResponseType = RpcResponseType.SUCCESS
};
}
finally
{
_semaphoreSlim.Release();
}
}
public async ValueTask<SessionResponse> ConnectCharacter(ConnectCharacterRequest request)
{
await _semaphoreSlim.WaitAsync();
try
{
Session session = await _sessionManager.GetSessionByAccountId(request.AccountId);
if (session is null || session.State != SessionState.CharacterSelection || session.ChannelId != request.ChannelId)
{
Log.Debug($"[SESSION_SERVICE][CONNECT_CHARACTER] Can't connect character from session of account {request.AccountId} (Couldn't find session)");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
session.CharacterId = request.CharacterId;
session.State = SessionState.InGame;
bool updated = await _sessionManager.Update(session);
if (!updated)
{
Log.Debug($"[SESSION_SERVICE][CONNECT_CHARACTER] Failed to update session of account {request.AccountId}");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
Log.Debug($"[SESSION_SERVICE][CONNECT_CHARACTER] Successfully update session of account {request.AccountId}");
return new SessionResponse
{
ResponseType = RpcResponseType.SUCCESS,
Session = session
};
}
finally
{
_semaphoreSlim.Release();
}
}
public async ValueTask<SessionResponse> ActivateCrossChannelAuthentication(ActivateCrossChannelAuthenticationRequest request)
{
await _semaphoreSlim.WaitAsync();
try
{
Session session = await _sessionManager.GetSessionByAccountId(request.AccountId);
if (session is null || session.State != SessionState.InGame)
{
Log.Debug($"[SESSION_SERVICE][ACTIVE_CROSS_CHANNEL_AUTHENTICATION] Couldn't find session for account {request.AccountId} or account doesn't have correct state ({session?.State})");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
session.State = SessionState.CrossChannelAuthentication;
session.AllowedCrossChannelId = request.ChannelId;
bool updated = await _sessionManager.Update(session);
if (!updated)
{
Log.Debug($"[SESSION_SERVICE][ACTIVE_CROSS_CHANNEL_AUTHENTICATION] Failed to update session of account {request.AccountId}");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
Log.Debug($"[SESSION_SERVICE][ACTIVE_CROSS_CHANNEL_AUTHENTICATION] Successfully updated session of account {request.AccountId}");
return new SessionResponse
{
ResponseType = RpcResponseType.SUCCESS,
Session = session
};
}
finally
{
_semaphoreSlim.Release();
}
}
public async ValueTask<SessionResponse> Pulse(PulseRequest request)
{
await _semaphoreSlim.WaitAsync();
try
{
Session session = await _sessionManager.GetSessionByAccountId(request.AccountId);
if (session is null || session.State != SessionState.InGame)
{
Log.Debug($"[SESSION_SERVICE][PULSE] Couldn't find session of account or incorrect state {request.AccountId} ({session?.State})");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
bool pulsed = await _sessionManager.Pulse(session);
if (!pulsed)
{
Log.Debug($"[SESSION_SERVICE][PULSE] Failed to pulse session of account {request.AccountId}");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
session.LastPulse = DateTime.UtcNow;
bool updated = await _sessionManager.Update(session);
if (!updated)
{
Log.Debug($"[SESSION_SERVICE][PULSE] Failed to update session of account {request.AccountId} after pulse");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
Log.Debug($"[SESSION_SERVICE][PULSE] Successfully pulsed session of account {request.AccountId}");
return new SessionResponse
{
ResponseType = RpcResponseType.SUCCESS,
Session = session
};
}
finally
{
_semaphoreSlim.Release();
}
}
public async ValueTask<SessionResponse> ConnectToWorldServer(ConnectToWorldServerRequest request)
{
await _semaphoreSlim.WaitAsync();
try
{
Session session = await _sessionManager.GetSessionByAccountId(request.AccountId);
if (session is null)
{
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_WORLD_SERVER] Couldn't find session of account or incorrect state {request.AccountId}");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
// Can't connect to world if not in server selection screen or cross channel
if (session.State != SessionState.ServerSelection && session.State != SessionState.CrossChannelAuthentication)
{
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_WORLD_SERVER] Incorrect session state {session.State}");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
if (session.State == SessionState.CrossChannelAuthentication && session.AllowedCrossChannelId != request.ChannelId)
{
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_WORLD_SERVER] Incorrect cross channel ID {request.ChannelId} instead of {session.AllowedCrossChannelId}");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
session.LastChannelId = session.ChannelId;
session.ServerGroup = request.ServerGroup;
session.ChannelId = request.ChannelId;
session.State = SessionState.CharacterSelection;
session.AllowedCrossChannelId = 0;
bool updated = await _sessionManager.Update(session);
if (!updated)
{
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_WORLD_SERVER] Failed to update session of account {session.AccountId}");
return new SessionResponse
{
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
};
}
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_WORLD_SERVER] Successfully updated session of account {session.AccountId}");
return new SessionResponse
{
ResponseType = RpcResponseType.SUCCESS,
Session = session
};
}
finally
{
_semaphoreSlim.Release();
}
}
}
}