diff --git a/Core/Callbacks/LauncherCallbacks.cs b/Core/Callbacks/LauncherCallbacks.cs index a616dd84..c1a07552 100644 --- a/Core/Callbacks/LauncherCallbacks.cs +++ b/Core/Callbacks/LauncherCallbacks.cs @@ -20,71 +20,80 @@ public class LauncherCallbacks SaveServer saveServer, Watermark watermark) { - + _httpResponseUtil = httpResponse; + _launcherController = launcherController; + _saveServer = saveServer; + _watermark = watermark; } public string Connect() { - throw new NotImplementedException(); + return _httpResponseUtil.NoBody(_launcherController.Connect()); } public string Login(string url, LoginRequestData info, string sessionID) { - throw new NotImplementedException(); + var output = _launcherController.Login(info); + return output ?? "FAILED"; } public string Register(string url, RegisterData info, string sessionID) { - throw new NotImplementedException(); + var output = _launcherController.Register(info); + return string.IsNullOrEmpty(output) ? "FAILED" : "OK"; } public string Get(string url, LoginRequestData info, string sessionID) { - throw new NotImplementedException(); + var output = _launcherController.Find(_launcherController.Login(info)); + return _httpResponseUtil.NoBody(output); } public string ChangeUsername(string url, ChangeRequestData info, string sessionID) { - throw new NotImplementedException(); + var output = _launcherController.ChangeUsername(info); + return string.IsNullOrEmpty(output) ? "FAILED" : "OK"; } public string ChangePassword(string url, ChangeRequestData info, string sessionID) { - throw new NotImplementedException(); + var output = _launcherController.ChangePassword(info); + return string.IsNullOrEmpty(output) ? "FAILED" : "OK"; } public string Wipe(string url, RegisterData info, string sessionID) { - throw new NotImplementedException(); + var output = _launcherController.Wipe(info); + return string.IsNullOrEmpty(output) ? "FAILED" : "OK"; } public string GetServerVersion() { - throw new NotImplementedException(); + return _httpResponseUtil.NoBody(_watermark.GetVersionTag()); } public string Ping(string url, EmptyRequestData info, string sessionID) { - throw new NotImplementedException(); + return _httpResponseUtil.NoBody("pong!"); } public string RemoveProfile(string url, RemoveProfileData info, string sessionID) { - throw new NotImplementedException(); + return _httpResponseUtil.NoBody(_saveServer.RemoveProfile(sessionID)); } public string GetCompatibleTarkovVersion() { - throw new NotImplementedException(); + return _httpResponseUtil.NoBody(_launcherController.GetCompatibleTarkovVersion()); } public string GetLoadedServerMods() { - throw new NotImplementedException(); + return _httpResponseUtil.NoBody(_launcherController.GetLoadedServerMods()); } public string GetServerModsProfileUsed(string url, EmptyRequestData info, string sessionID) { - throw new NotImplementedException(); + return _httpResponseUtil.NoBody(_launcherController.GetServerModsProfileUsed(sessionID)); } } diff --git a/Core/Controllers/LauncherController.cs b/Core/Controllers/LauncherController.cs index 787783cc..684d6860 100644 --- a/Core/Controllers/LauncherController.cs +++ b/Core/Controllers/LauncherController.cs @@ -1,149 +1,241 @@ +using System.Text.Json.Serialization; using Core.Annotations; +using Core.Helpers; +using Core.Models.Eft.Common.Tables; using Core.Models.Eft.Launcher; using Core.Models.Eft.Profile; +using Core.Models.Enums; +using Core.Models.Spt.Config; using Core.Models.Spt.Mod; +using Core.Servers; +using Core.Services; +using Core.Utils; +using Core.Utils.Extensions; +using ILogger = Core.Models.Utils.ILogger; +using Info = Core.Models.Eft.Profile.Info; namespace Core.Controllers; [Injectable] public class LauncherController { - /// - /// - /// - /// + protected CoreConfig _coreConfig; + + protected ILogger _logger; + protected HashUtil _hashUtil; + protected TimeUtil _timeUtil; + protected RandomUtil _randomUtil; + protected SaveServer _saveServer; + protected HttpServerHelper _httpServerHelper; + protected ProfileHelper _profileHelper; + protected DatabaseService _databaseService; + protected LocalisationService _localisationService; + + + public LauncherController( + Models.Utils.ILogger logger, + HashUtil hashUtil, + TimeUtil timeUtil, + RandomUtil randomUtil, + SaveServer saveServer, + HttpServerHelper httpServerHelper, + ProfileHelper profileHelper, + DatabaseService databaseService, + LocalisationService localisationService, + // TODO => @inject("PreSptModLoader") protected preSptModLoader: PreSptModLoader, + ConfigServer configServer + ) { + _logger = logger; + _hashUtil = hashUtil; + _timeUtil = timeUtil; + _randomUtil = randomUtil; + _saveServer = saveServer; + _httpServerHelper = httpServerHelper; + _profileHelper = profileHelper; + _databaseService = databaseService; + _localisationService = localisationService; + _coreConfig = configServer.GetConfig(ConfigTypes.CORE); + } + public ConnectResponse Connect() { - throw new NotImplementedException(); + // Get all possible profile types + filter out any that are blacklisted + + var profiles = typeof(ProfileTemplates).GetProperties() + .Select(p => p.GetJsonName()) + .Where(profileName => !_coreConfig.Features.CreateNewProfileTypesBlacklist.Contains(profileName)) + .ToList(); + + return new ConnectResponse(){ + BackendUrl = _httpServerHelper.GetBackendUrl(), + Name = _coreConfig.ServerName, + Editions = profiles, + ProfileDescriptions = GetProfileDescriptions(), + }; } - /// - /// Get descriptive text for each of the profile edtions a player can choose, keyed by profile.json profile type - /// e.g. "Edge Of Darkness" - /// - /// Dictionary of profile types with related descriptive text - private Dictionary GetProfileDescriptions() + /** + * Get descriptive text for each of the profile edtions a player can choose, keyed by profile.json profile type e.g. "Edge Of Darkness" + * @returns Dictionary of profile types with related descriptive text + */ + protected Dictionary GetProfileDescriptions() { - throw new NotImplementedException(); + var result = new Dictionary(); + var dbProfiles = _databaseService.GetProfiles(); + foreach (var templatesProperty in typeof(ProfileTemplates).GetProperties()) + { + var propertyValue = templatesProperty.GetValue(dbProfiles); + if (propertyValue == null) { + _logger.Warning(_localisationService.GetText("launcher-missing_property", templatesProperty)); + continue; + } + + var casterPropertyValue = propertyValue as ProfileSides; + result[templatesProperty.GetJsonName()] = _localisationService.GetText(casterPropertyValue.DescriptionLocaleKey); + } + + return result; } - /// - /// - /// - /// - /// - public Info Find(string sessionId) + public Info? Find(string sessionId) { - throw new NotImplementedException(); + return _saveServer.GetProfiles().TryGetValue(sessionId, out var profile) ? profile.ProfileInfo : null; } - /// - /// - /// - /// - /// public string Login(LoginRequestData info) { - throw new NotImplementedException(); + foreach (var sessionID in _saveServer.GetProfiles()) { + var account = _saveServer.GetProfile(sessionID.Key).ProfileInfo; + if (info.Username == account.UserName) { + return sessionID.Key; + } + } + + return ""; } - /// - /// - /// - /// - /// public string Register(RegisterData info) { - throw new NotImplementedException(); + foreach (var sessionID in _saveServer.GetProfiles()) { + if (info.Username == _saveServer.GetProfile(sessionID.Key).ProfileInfo.UserName) { + return ""; + } + } + + return CreateAccount(info); } - /// - /// - /// - /// - /// - private string CreateAccount(RegisterData info) + protected string CreateAccount(RegisterData info) { - throw new NotImplementedException(); + var profileId = GenerateProfileId(); + var scavId = GenerateProfileId(); + var newProfileDetails = new Info(){ + ProfileId = profileId, + ScavengerId = scavId, + Aid = _hashUtil.GenerateAccountId(), + UserName = info.Username, + Password = info.Password, + IsWiped = true, + Edition = info.Edition, + }; + _saveServer.CreateProfile(newProfileDetails); + + _saveServer.LoadProfile(profileId); + _saveServer.SaveProfile(profileId); + + return profileId; } - /// - /// - /// - /// - private string GenerateProfileId() + protected string GenerateProfileId() { - throw new NotImplementedException(); + var timestamp = _timeUtil.GetTimeStamp(); + + return FormatID(timestamp, timestamp * _randomUtil.GetInt(1, 1000000)); } - /// - /// - /// - /// - /// - /// - private string FormatId( - long timeStamp, - int counter) + protected string FormatID(long timeStamp, long counter) { - throw new NotImplementedException(); + + var timeStampStr = Convert.ToString(timeStamp, 16).PadLeft(8, '0'); + var counterStr = Convert.ToString(counter, 16).PadLeft(16, '0'); + + return timeStampStr.ToLower() + counterStr.ToLower(); } - /// - /// - /// - /// - /// public string ChangeUsername(ChangeRequestData info) { - throw new NotImplementedException(); + var sessionID = Login(info); + + if (!string.IsNullOrEmpty(sessionID)) { + _saveServer.GetProfile(sessionID).ProfileInfo.UserName = info.Change; + } + + return sessionID; } - /// - /// - /// - /// - /// public string ChangePassword(ChangeRequestData info) { - throw new NotImplementedException(); + var sessionID = Login(info); + + if (!string.IsNullOrEmpty(sessionID)) { + _saveServer.GetProfile(sessionID).ProfileInfo.Password = info.Change; + } + + return sessionID; } - /// - /// Handle launcher requesting profile be wiped - /// - /// RegisterData - /// Session id - public string Wipe(RegisterData info) + /** + * Handle launcher requesting profile be wiped + * @param info IRegisterData + * @returns Session id + */ + public string? Wipe(RegisterData info) { - throw new NotImplementedException(); + if (!_coreConfig.AllowProfileWipe) { + return null; + } + + var sessionID = Login(info); + + if (!string.IsNullOrEmpty(sessionID)) { + var profileInfo = _saveServer.GetProfile(sessionID).ProfileInfo; + profileInfo.Edition = info.Edition; + profileInfo.IsWiped = true; + } + + return sessionID; } - /// - /// - /// - /// public string GetCompatibleTarkovVersion() { - throw new NotImplementedException(); + return _coreConfig.CompatibleTarkovVersion; } - /// - /// Get the mods the server has currently loaded - /// - /// Dictionary of mod name and mod details - public Dictionary GetLoadedServerMods() // TODO: We no longer use a package.json + /** + * Get the mods the server has currently loaded + * @returns Dictionary of mod name and mod details + */ + public Dictionary GetLoadedServerMods() { - throw new NotImplementedException(); + return new Dictionary(); + // TODO => return this.preSptModLoader.getImportedModDetails(); } - /// - /// Get the mods a profile has ever loaded into game with - /// - /// Player id - /// List of mod details + /** + * Get the mods a profile has ever loaded into game with + * @param sessionId Player id + * @returns Array of mod details + */ public List GetServerModsProfileUsed(string sessionId) { - throw new NotImplementedException(); + var profile = _profileHelper.GetFullProfile(sessionId); + + /* TODO => modding + if (profile?.spt?.mods) { + return this.preSptModLoader.GetProfileModsGroupedByModName(profile?.spt?.mods); + } + */ + + return []; } } diff --git a/Core/Helpers/ProfileHelper.cs b/Core/Helpers/ProfileHelper.cs index 42e4c8de..409d330a 100644 --- a/Core/Helpers/ProfileHelper.cs +++ b/Core/Helpers/ProfileHelper.cs @@ -1,10 +1,12 @@ -using Core.Models.Eft.Common; +using Core.Annotations; +using Core.Models.Eft.Common; using Core.Models.Eft.Common.Tables; using Core.Models.Eft.Profile; using Core.Models.Enums; namespace Core.Helpers; +[Injectable] public class ProfileHelper { /// diff --git a/Core/Servers/Http/SptHttpListener.cs b/Core/Servers/Http/SptHttpListener.cs index 434cfa1d..5c7cbb73 100644 --- a/Core/Servers/Http/SptHttpListener.cs +++ b/Core/Servers/Http/SptHttpListener.cs @@ -106,6 +106,7 @@ public class SptHttpListener : IHttpListener if (IsDebugRequest(req)) { // Send only raw response without transformation SendJson(resp, output, sessionID); + Console.WriteLine($"Response: {output}"); // TODO: this.logRequest(req, output); return; } @@ -118,7 +119,7 @@ public class SptHttpListener : IHttpListener // No serializer can handle the request (majority of requests dont), zlib the output and send response back SendZlibJson(resp, output, sessionID); } - + Console.WriteLine($"Response: {output}"); // TODO: this.LogRequest(req, output); } @@ -181,8 +182,15 @@ public class SptHttpListener : IHttpListener public void SendZlibJson(HttpResponse resp, string? output, string sessionID) { - if (!string.IsNullOrEmpty(output)) - new ZLibStream(resp.Body, CompressionLevel.SmallestSize, false).WriteAsync(Encoding.UTF8.GetBytes(output)).AsTask().Wait(); + using (var ms = new MemoryStream()) + { + using (var deflateStream = new ZLibStream(ms, CompressionLevel.SmallestSize)) + { + deflateStream.WriteAsync(Encoding.UTF8.GetBytes(output)).AsTask().Wait(); + } + var bytes = ms.ToArray(); + resp.Body.WriteAsync(bytes, 0, bytes.Length).Wait(); + } resp.StartAsync().Wait(); resp.CompleteAsync().Wait(); } diff --git a/Core/Utils/Extensions/MemberInfoExtensions.cs b/Core/Utils/Extensions/MemberInfoExtensions.cs new file mode 100644 index 00000000..0d90d062 --- /dev/null +++ b/Core/Utils/Extensions/MemberInfoExtensions.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Text.Json.Serialization; + +namespace Core.Utils.Extensions; + +public static class MemberInfoExtensions +{ + public static string GetJsonName(this MemberInfo memberInfo) + { + return Attribute.IsDefined(memberInfo, typeof(JsonPropertyNameAttribute)) + ? (Attribute.GetCustomAttribute(memberInfo, typeof(JsonPropertyNameAttribute)) as JsonPropertyNameAttribute).Name + : memberInfo.Name; + } + +} diff --git a/Core/Utils/JsonUtil.cs b/Core/Utils/JsonUtil.cs index a2627e3b..368771eb 100644 --- a/Core/Utils/JsonUtil.cs +++ b/Core/Utils/JsonUtil.cs @@ -10,8 +10,9 @@ namespace Core.Utils; [Injectable(InjectionType.Singleton)] public class JsonUtil { - private readonly JsonSerializerOptions jsonSerializerOptions = new() + private static readonly JsonSerializerOptions jsonSerializerOptionsNoIndent = new() { + WriteIndented = false, UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow, Converters = { @@ -23,27 +24,30 @@ public class JsonUtil new EftEnumConverter() } }; + private static readonly JsonSerializerOptions jsonSerializerOptionsIndented = new(jsonSerializerOptionsNoIndent) + { + WriteIndented = true + }; + public T? Deserialize(string? json) { - return string.IsNullOrEmpty(json) ? default : JsonSerializer.Deserialize(json, jsonSerializerOptions); + return string.IsNullOrEmpty(json) ? default : JsonSerializer.Deserialize(json, jsonSerializerOptionsNoIndent); } public object? Deserialize(string? json, Type type) { - return string.IsNullOrEmpty(json) ? null : JsonSerializer.Deserialize(json, type, jsonSerializerOptions); + return string.IsNullOrEmpty(json) ? null : JsonSerializer.Deserialize(json, type, jsonSerializerOptionsNoIndent); } public string? Serialize(T? obj, bool indented = false) { - jsonSerializerOptions.WriteIndented = indented; - return obj == null ? null : JsonSerializer.Serialize(obj, jsonSerializerOptions); + return obj == null ? null : JsonSerializer.Serialize(obj, indented ? jsonSerializerOptionsIndented : jsonSerializerOptionsNoIndent); } public string? Serialize(object? obj, Type type, bool indented = false) { - jsonSerializerOptions.WriteIndented = indented; - return obj == null ? null : JsonSerializer.Serialize(obj, type, jsonSerializerOptions); + return obj == null ? null : JsonSerializer.Serialize(obj, type, indented ? jsonSerializerOptionsIndented : jsonSerializerOptionsNoIndent); } }