From bb8d56c8b0663d5dd9839c10e9d9516c7ee1c519 Mon Sep 17 00:00:00 2001 From: CWX Date: Sun, 19 Jan 2025 14:29:59 +0000 Subject: [PATCH] V2 launcher API --- Core/Callbacks/LauncherV2Callbacks.cs | 112 ++++++++++ Core/Controllers/LauncherV2Controller.cs | 208 ++++++++++++++++++ .../Launcher/LauncherV2CompatibleVersion.cs | 9 + .../Spt/Launcher/LauncherV2LoginResponse.cs | 8 + .../Spt/Launcher/LauncherV2ModsResponse.cs | 9 + .../LauncherV2PasswordChangeResponse.cs | 10 + .../Spt/Launcher/LauncherV2PingResponse.cs | 8 + .../Launcher/LauncherV2ProfilesResponse.cs | 9 + .../Launcher/LauncherV2RegisterResponse.cs | 10 + .../Spt/Launcher/LauncherV2RemoveResponse.cs | 10 + .../Spt/Launcher/LauncherV2TypesResponse.cs | 8 + .../Spt/Launcher/LauncherV2VersionResponse.cs | 8 + Core/Routers/Static/LauncherV2StaticRouter.cs | 67 ++++++ 13 files changed, 476 insertions(+) create mode 100644 Core/Callbacks/LauncherV2Callbacks.cs create mode 100644 Core/Controllers/LauncherV2Controller.cs create mode 100644 Core/Models/Spt/Launcher/LauncherV2CompatibleVersion.cs create mode 100644 Core/Models/Spt/Launcher/LauncherV2LoginResponse.cs create mode 100644 Core/Models/Spt/Launcher/LauncherV2ModsResponse.cs create mode 100644 Core/Models/Spt/Launcher/LauncherV2PasswordChangeResponse.cs create mode 100644 Core/Models/Spt/Launcher/LauncherV2PingResponse.cs create mode 100644 Core/Models/Spt/Launcher/LauncherV2ProfilesResponse.cs create mode 100644 Core/Models/Spt/Launcher/LauncherV2RegisterResponse.cs create mode 100644 Core/Models/Spt/Launcher/LauncherV2RemoveResponse.cs create mode 100644 Core/Models/Spt/Launcher/LauncherV2TypesResponse.cs create mode 100644 Core/Models/Spt/Launcher/LauncherV2VersionResponse.cs create mode 100644 Core/Routers/Static/LauncherV2StaticRouter.cs diff --git a/Core/Callbacks/LauncherV2Callbacks.cs b/Core/Callbacks/LauncherV2Callbacks.cs new file mode 100644 index 00000000..9411cb14 --- /dev/null +++ b/Core/Callbacks/LauncherV2Callbacks.cs @@ -0,0 +1,112 @@ +using Core.Annotations; +using Core.Controllers; +using Core.Models.Eft.Launcher; +using Core.Models.Spt.Launcher; +using Core.Utils; + +namespace Core.Callbacks; + +[Injectable] +public class LauncherV2Callbacks( + HttpResponseUtil _httpResponseUtil, + LauncherV2Controller _launcherV2Controller, + ProfileController _profileController +) +{ + public string Ping() + { + return _httpResponseUtil.NoBody(new LauncherV2PingResponse + { + Response = _launcherV2Controller.Ping() + } + ); + } + + public string Types() + { + return _httpResponseUtil.NoBody(new LauncherV2TypesResponse + { + Response = _launcherV2Controller.Types() + }); + } + + public string Login(LoginRequestData info) + { + return _httpResponseUtil.NoBody(new LauncherV2LoginResponse + { + Response = _launcherV2Controller.Login(info) + } + ); + } + + public string Register(RegisterData info) + { + return _httpResponseUtil.NoBody(new LauncherV2RegisterResponse + { + Response = _launcherV2Controller.Register(info), + Profiles = _profileController.GetMiniProfiles() + } + ); + } + + public string PasswordChange(ChangeRequestData info) + { + return _httpResponseUtil.NoBody(new LauncherV2PasswordChangeResponse + { + Response = _launcherV2Controller.PasswordChange(info), + Profiles = _profileController.GetMiniProfiles() + } + ); + } + + public string Remove(LoginRequestData info) + { + return _httpResponseUtil.NoBody(new LauncherV2RemoveResponse + { + Response = _launcherV2Controller.Remove(info), + Profiles = _profileController.GetMiniProfiles() + } + ); + } + + public string CompatibleVersion() + { + return _httpResponseUtil.NoBody(new LauncherV2VersionResponse + { + Response = new LauncherV2CompatibleVersion + { + SptVersion = _launcherV2Controller.SptVersion(), + EftVersion = _launcherV2Controller.EftVersion() + } + } + ); + } + + public string Mods() + { + return _httpResponseUtil.NoBody(new LauncherV2ModsResponse + { + Response = _launcherV2Controller.LoadedMods() + } + ); + } + + public string Profiles() + { + return _httpResponseUtil.NoBody(new LauncherV2ProfilesResponse + { + Response = _profileController.GetMiniProfiles() + } + ); + } + + public string Profile() + { + throw new NotImplementedException(); + } + + public string ProfileMods() + { + throw new NotImplementedException(); + } +} diff --git a/Core/Controllers/LauncherV2Controller.cs b/Core/Controllers/LauncherV2Controller.cs new file mode 100644 index 00000000..8a0059ba --- /dev/null +++ b/Core/Controllers/LauncherV2Controller.cs @@ -0,0 +1,208 @@ +using Core.Annotations; +using Core.Helpers; +using Core.Models.Eft.Common.Tables; +using Core.Models.Eft.Launcher; +using Core.Models.Spt.Config; +using Core.Models.Spt.Mod; +using Core.Models.Utils; +using Core.Servers; +using Core.Services; +using Core.Utils; +using Core.Utils.Extensions; +using Info = Core.Models.Eft.Profile.Info; + +namespace Core.Controllers; + +[Injectable] +public class LauncherV2Controller( + ISptLogger _logger, + HashUtil _hashUtil, + TimeUtil _timeUtil, + RandomUtil _randomUtil, + SaveServer _saveServer, + DatabaseService _databaseService, + LocalisationService _localisationService, + ConfigServer _configServer, + Watermark _watermark +) +{ + protected CoreConfig _coreConfig = _configServer.GetConfig(); + + /// + /// Returns a simple string of Pong! + /// + /// + public string Ping() + { + return "Pong!"; + } + + /// + /// Returns all available profile types and descriptions for creation. + /// - This is also localised. + /// + /// + public Dictionary Types() + { + var result = new Dictionary(); + var dbProfiles = _databaseService.GetProfiles(); + + foreach (var templatesProperty in typeof(ProfileTemplates).GetProperties().Where(p => p.CanWrite)) + { + 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; + } + + /// + /// Checks if login details were correct. + /// + /// + /// + public bool Login(LoginRequestData info) + { + var sessionId = GetSessionId(info); + + return sessionId is not null; + } + + /// + /// Register a new profile. + /// + /// + /// + public bool Register(RegisterData info) + { + foreach (var session in _saveServer.GetProfiles()) + { + if (info.Username == _saveServer.GetProfile(session.Key).ProfileInfo!.Username) + { + return false; + } + } + + CreateAccount(info); + return true; + } + + /// + /// Make a password change. + /// + /// + /// + public bool PasswordChange(ChangeRequestData info) + { + var sessionId = GetSessionId(info); + + if (sessionId is null) + return false; + + _saveServer.GetProfile(sessionId).ProfileInfo!.Password = info.Password; + return true; + } + + /// + /// Remove profile from server. + /// + /// + /// + public bool Remove(LoginRequestData info) + { + var sessionId = GetSessionId(info); + + return sessionId is not null && _saveServer.RemoveProfile(sessionId); + } + + /// + /// Gets the Servers SPT Version. + /// - "4.0.0" + /// + /// + public string SptVersion() + { + return _watermark.GetVersionTag(); + } + + /// + /// Gets the compatible EFT Version. + /// - "0.14.9.31124" + /// + /// + public string EftVersion() + { + return _coreConfig.CompatibleTarkovVersion; + } + + /// + /// Gets the Servers loaded mods. + /// + /// + public Dictionary LoadedMods() + { + return new Dictionary(); + } + + /// + /// Creates the account from provided details. + /// + /// + /// + protected string CreateAccount(RegisterData info) + { + 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; + } + + protected string GenerateProfileId() + { + var timestamp = _timeUtil.GetTimeStamp(); + + return FormatID(timestamp, timestamp * _randomUtil.GetInt(1, 1000000)); + } + + protected string FormatID(long timeStamp, long counter) + { + var timeStampStr = Convert.ToString(timeStamp, 16).PadLeft(8, '0'); + var counterStr = Convert.ToString(counter, 16).PadLeft(16, '0'); + + return timeStampStr.ToLower() + counterStr.ToLower(); + } + + protected string? GetSessionId(LoginRequestData info) + { + foreach (var profile in _saveServer.GetProfiles()) + { + if (info.Username == profile.Value.ProfileInfo!.Username + && info.Password == profile.Value.ProfileInfo.Password) + return profile.Key; + } + + return null; + } +} diff --git a/Core/Models/Spt/Launcher/LauncherV2CompatibleVersion.cs b/Core/Models/Spt/Launcher/LauncherV2CompatibleVersion.cs new file mode 100644 index 00000000..fd742cff --- /dev/null +++ b/Core/Models/Spt/Launcher/LauncherV2CompatibleVersion.cs @@ -0,0 +1,9 @@ +using Core.Models.Utils; + +namespace Core.Models.Spt.Launcher; + +public class LauncherV2CompatibleVersion : IRequestData +{ + public required string SptVersion { get; set; } + public required string EftVersion { get; set; } +} diff --git a/Core/Models/Spt/Launcher/LauncherV2LoginResponse.cs b/Core/Models/Spt/Launcher/LauncherV2LoginResponse.cs new file mode 100644 index 00000000..3d982c1f --- /dev/null +++ b/Core/Models/Spt/Launcher/LauncherV2LoginResponse.cs @@ -0,0 +1,8 @@ +using Core.Models.Utils; + +namespace Core.Models.Spt.Launcher; + +public class LauncherV2LoginResponse : IRequestData +{ + public required bool Response { get; set; } +} diff --git a/Core/Models/Spt/Launcher/LauncherV2ModsResponse.cs b/Core/Models/Spt/Launcher/LauncherV2ModsResponse.cs new file mode 100644 index 00000000..f0f42f60 --- /dev/null +++ b/Core/Models/Spt/Launcher/LauncherV2ModsResponse.cs @@ -0,0 +1,9 @@ +using Core.Models.Spt.Mod; +using Core.Models.Utils; + +namespace Core.Models.Spt.Launcher; + +public class LauncherV2ModsResponse : IRequestData +{ + public required Dictionary Response { get; set; } +} diff --git a/Core/Models/Spt/Launcher/LauncherV2PasswordChangeResponse.cs b/Core/Models/Spt/Launcher/LauncherV2PasswordChangeResponse.cs new file mode 100644 index 00000000..d31d742a --- /dev/null +++ b/Core/Models/Spt/Launcher/LauncherV2PasswordChangeResponse.cs @@ -0,0 +1,10 @@ +using Core.Models.Eft.Launcher; +using Core.Models.Utils; + +namespace Core.Models.Spt.Launcher; + +public class LauncherV2PasswordChangeResponse : IRequestData +{ + public required bool Response { get; set; } + public required List Profiles { get; set; } +} diff --git a/Core/Models/Spt/Launcher/LauncherV2PingResponse.cs b/Core/Models/Spt/Launcher/LauncherV2PingResponse.cs new file mode 100644 index 00000000..59571607 --- /dev/null +++ b/Core/Models/Spt/Launcher/LauncherV2PingResponse.cs @@ -0,0 +1,8 @@ +using Core.Models.Utils; + +namespace Core.Models.Spt.Launcher; + +public class LauncherV2PingResponse : IRequestData +{ + public required string Response { get; set; } +} diff --git a/Core/Models/Spt/Launcher/LauncherV2ProfilesResponse.cs b/Core/Models/Spt/Launcher/LauncherV2ProfilesResponse.cs new file mode 100644 index 00000000..3b5f3572 --- /dev/null +++ b/Core/Models/Spt/Launcher/LauncherV2ProfilesResponse.cs @@ -0,0 +1,9 @@ +using Core.Models.Eft.Launcher; +using Core.Models.Utils; + +namespace Core.Models.Spt.Launcher; + +public class LauncherV2ProfilesResponse : IRequestData +{ + public required List Response { get; set; } +} diff --git a/Core/Models/Spt/Launcher/LauncherV2RegisterResponse.cs b/Core/Models/Spt/Launcher/LauncherV2RegisterResponse.cs new file mode 100644 index 00000000..ff97326f --- /dev/null +++ b/Core/Models/Spt/Launcher/LauncherV2RegisterResponse.cs @@ -0,0 +1,10 @@ +using Core.Models.Eft.Launcher; +using Core.Models.Utils; + +namespace Core.Models.Spt.Launcher; + +public class LauncherV2RegisterResponse : IRequestData +{ + public required bool Response { get; set; } + public required List Profiles { get; set; } +} diff --git a/Core/Models/Spt/Launcher/LauncherV2RemoveResponse.cs b/Core/Models/Spt/Launcher/LauncherV2RemoveResponse.cs new file mode 100644 index 00000000..b19b3ac3 --- /dev/null +++ b/Core/Models/Spt/Launcher/LauncherV2RemoveResponse.cs @@ -0,0 +1,10 @@ +using Core.Models.Eft.Launcher; +using Core.Models.Utils; + +namespace Core.Models.Spt.Launcher; + +public class LauncherV2RemoveResponse : IRequestData +{ + public required bool Response { get; set; } + public required List Profiles { get; set; } +} diff --git a/Core/Models/Spt/Launcher/LauncherV2TypesResponse.cs b/Core/Models/Spt/Launcher/LauncherV2TypesResponse.cs new file mode 100644 index 00000000..0cadc001 --- /dev/null +++ b/Core/Models/Spt/Launcher/LauncherV2TypesResponse.cs @@ -0,0 +1,8 @@ +using Core.Models.Utils; + +namespace Core.Models.Spt.Launcher; + +public class LauncherV2TypesResponse : IRequestData +{ + public required Dictionary Response { get; set; } +} diff --git a/Core/Models/Spt/Launcher/LauncherV2VersionResponse.cs b/Core/Models/Spt/Launcher/LauncherV2VersionResponse.cs new file mode 100644 index 00000000..dcb65f38 --- /dev/null +++ b/Core/Models/Spt/Launcher/LauncherV2VersionResponse.cs @@ -0,0 +1,8 @@ +using Core.Models.Utils; + +namespace Core.Models.Spt.Launcher; + +public record LauncherV2VersionResponse : IRequestData +{ + public required LauncherV2CompatibleVersion Response { get; set; } +} diff --git a/Core/Routers/Static/LauncherV2StaticRouter.cs b/Core/Routers/Static/LauncherV2StaticRouter.cs new file mode 100644 index 00000000..d85dc3cc --- /dev/null +++ b/Core/Routers/Static/LauncherV2StaticRouter.cs @@ -0,0 +1,67 @@ +using Core.Annotations; +using Core.Callbacks; +using Core.DI; +using Core.Models.Eft.Launcher; +using Core.Utils; + +namespace Core.Routers.Static; + +[Injectable(InjectableTypeOverride = typeof(StaticRouter))] +public class LauncherV2StaticRouter : StaticRouter +{ + public LauncherV2StaticRouter(LauncherV2Callbacks launcherV2Callbacks, JsonUtil jsonUtil) : base( + jsonUtil, + [ + new RouteAction( + "/launcher/v2/ping", + (url, _, sessionID, _) => launcherV2Callbacks.Ping() + ), + new RouteAction( + "/launcher/v2/types", + (url, _, sessionID, _) => launcherV2Callbacks.Types() + ), + new RouteAction( + "/launcher/v2/Login", + (url, info, sessionID, _) => launcherV2Callbacks.Login(info as LoginRequestData), + typeof(LoginRequestData) + ), + new RouteAction( + "/launcher/v2/Register", + (url, info, sessionID, _) => launcherV2Callbacks.Register(info as RegisterData), + typeof(RegisterData) + ), + new RouteAction( + "/launcher/v2/passwordChange", + (url, info, sessionID, _) => launcherV2Callbacks.PasswordChange(info as ChangeRequestData), + typeof(ChangeRequestData) + ), + new RouteAction( + "/launcher/v2/Remove", + (url, info, sessionID, _) => launcherV2Callbacks.Remove(info as LoginRequestData), + typeof(LoginRequestData) + ), + new RouteAction( + "/launcher/v2/version", + (url, _, sessionID, _) => launcherV2Callbacks.CompatibleVersion() + ), + new RouteAction( + "/launcher/v2/mods", + (url, _, sessionID, _) => launcherV2Callbacks.Mods() + ), + new RouteAction( + "/launcher/v2/profiles", + (url, _, sessionID, _) => launcherV2Callbacks.Profiles() + ), + new RouteAction( + "/launcher/v2/profile", + (url, _, sessionID, _) => launcherV2Callbacks.Profile() + ), + new RouteAction( + "/launcher/v2/profileMods", + (url, _, sessionID, _) => launcherV2Callbacks.ProfileMods() + ), + ] + ) + { + } +}