Merge branch 'develop' of https://github.com/sp-tarkov/server-csharp into develop

This commit is contained in:
Chomp
2025-06-10 10:18:41 +01:00
20 changed files with 125 additions and 80 deletions
@@ -54,15 +54,15 @@ public class GameCallbacks(
/// Save profiles on game close
/// </summary>
/// <returns></returns>
public ValueTask<string> GameLogout(string url, EmptyRequestData _, string sessionID)
public async ValueTask<string> GameLogout(string url, EmptyRequestData _, string sessionID)
{
_saveServer.SaveProfile(sessionID);
return new ValueTask<string>(_httpResponseUtil.GetBody(
await _saveServer.SaveProfileAsync(sessionID);
return _httpResponseUtil.GetBody(
new GameLogoutResponseData
{
Status = "ok"
}
));
);
}
/// <summary>
@@ -26,10 +26,10 @@ public class LauncherCallbacks(
return new ValueTask<string>(output ?? "FAILED");
}
public ValueTask<string> Register(string url, RegisterData info, string sessionID)
public async ValueTask<string> Register(string url, RegisterData info, string sessionID)
{
var output = _launcherController.Register(info);
return new ValueTask<string>(string.IsNullOrEmpty(output) ? "FAILED" : "OK");
var output = await _launcherController.Register(info);
return string.IsNullOrEmpty(output) ? "FAILED" : "OK";
}
public ValueTask<string> Get(string url, LoginRequestData info, string sessionID)
@@ -43,26 +43,26 @@ public class LauncherV2Callbacks(
));
}
public ValueTask<string> Register(RegisterData info)
public async ValueTask<string> Register(RegisterData info)
{
return new ValueTask<string>(_httpResponseUtil.NoBody(
return _httpResponseUtil.NoBody(
new LauncherV2RegisterResponse
{
Response = _launcherV2Controller.Register(info),
Response = await _launcherV2Controller.Register(info),
Profiles = _profileController.GetMiniProfiles()
}
));
);
}
public ValueTask<string> PasswordChange(ChangeRequestData info)
public async ValueTask<string> PasswordChange(ChangeRequestData info)
{
return new ValueTask<string>(_httpResponseUtil.NoBody(
return _httpResponseUtil.NoBody(
new LauncherV2PasswordChangeResponse
{
Response = _launcherV2Controller.PasswordChange(info),
Response = await _launcherV2Controller.PasswordChange(info),
Profiles = _profileController.GetMiniProfiles()
}
));
);
}
public ValueTask<string> Remove(LoginRequestData info)
@@ -31,10 +31,10 @@ public class PrestigeCallbacks(
/// <param name="info"></param>
/// <param name="sessionID">Session/player id</param>
/// <returns></returns>
public ValueTask<string> ObtainPrestige(string url, ObtainPrestigeRequestList info, string sessionID)
public async ValueTask<string> ObtainPrestige(string url, ObtainPrestigeRequestList info, string sessionID)
{
_prestigeController.ObtainPrestige(sessionID, info);
await _prestigeController.ObtainPrestige(sessionID, info);
return new ValueTask<string>(_httpResponseUtil.NullResponse());
return _httpResponseUtil.NullResponse();
}
}
@@ -22,15 +22,15 @@ public class ProfileCallbacks(
/// Handle client/game/profile/create
/// </summary>
/// <returns></returns>
public ValueTask<string> CreateProfile(string url, ProfileCreateRequestData info, string sessionID)
public async ValueTask<string> CreateProfile(string url, ProfileCreateRequestData info, string sessionID)
{
var id = _profileController.CreateProfile(info, sessionID);
return new ValueTask<string>(_httpResponse.GetBody(
var id = await _profileController.CreateProfile(info, sessionID);
return _httpResponse.GetBody(
new CreateProfileResponse
{
UserId = id
}
));
);
}
/// <summary>
@@ -20,19 +20,19 @@ public class SaveCallbacks(
public async Task OnLoad()
{
_backupService.StartBackupSystem();
_saveServer.Load();
await _saveServer.LoadAsync();
}
public Task<bool> OnUpdate(long secondsSinceLastRun)
public async Task<bool> OnUpdate(long secondsSinceLastRun)
{
if (secondsSinceLastRun < _coreConfig.ProfileSaveIntervalInSeconds)
{
// Not enough time has passed since last run, exit early
return Task.FromResult(false);
return false;
}
_saveServer.Save();
await _saveServer.SaveAsync();
return Task.FromResult(true);
return true;
}
}
@@ -97,7 +97,7 @@ public class LauncherController(
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
public string Register(RegisterData info)
public async Task<string> Register(RegisterData info)
{
foreach (var kvp in _saveServer.GetProfiles())
{
@@ -107,14 +107,14 @@ public class LauncherController(
}
}
return CreateAccount(info);
return await CreateAccount(info);
}
/// <summary>
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
protected string CreateAccount(RegisterData info)
protected async Task<string> CreateAccount(RegisterData info)
{
var profileId = GenerateProfileId();
var scavId = GenerateProfileId();
@@ -130,8 +130,8 @@ public class LauncherController(
};
_saveServer.CreateProfile(newProfileDetails);
_saveServer.LoadProfile(profileId);
_saveServer.SaveProfile(profileId);
await _saveServer.LoadProfileAsync(profileId);
await _saveServer.SaveProfileAsync(profileId);
return profileId;
}
@@ -71,7 +71,7 @@ public class LauncherV2Controller(
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
public bool Register(RegisterData info)
public async Task<bool> Register(RegisterData info)
{
foreach (var session in _saveServer.GetProfiles())
{
@@ -81,7 +81,7 @@ public class LauncherV2Controller(
}
}
CreateAccount(info);
await CreateAccount(info);
return true;
}
@@ -90,7 +90,7 @@ public class LauncherV2Controller(
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
public bool PasswordChange(ChangeRequestData info)
public async Task<bool> PasswordChange(ChangeRequestData info)
{
var sessionId = GetSessionId(info);
@@ -105,7 +105,7 @@ public class LauncherV2Controller(
}
_saveServer.GetProfile(sessionId).ProfileInfo!.Password = info.Change;
_saveServer.SaveProfile(sessionId);
await _saveServer.SaveProfileAsync(sessionId);
return true;
}
@@ -162,7 +162,7 @@ public class LauncherV2Controller(
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
protected string CreateAccount(RegisterData info)
protected async Task<string> CreateAccount(RegisterData info)
{
var profileId = GenerateProfileId();
var scavId = GenerateProfileId();
@@ -179,8 +179,8 @@ public class LauncherV2Controller(
_saveServer.CreateProfile(newProfileDetails);
_saveServer.LoadProfile(profileId);
_saveServer.SaveProfile(profileId);
await _saveServer.LoadProfileAsync(profileId);
await _saveServer.SaveProfileAsync(profileId);
return profileId;
}
@@ -59,7 +59,7 @@ public class PrestigeController(
/// </list>
/// </summary>
/// <returns></returns>
public void ObtainPrestige(
public async Task ObtainPrestige(
string sessionId,
ObtainPrestigeRequestList request)
{
@@ -75,7 +75,7 @@ public class PrestigeController(
profile.SptData.PendingPrestige = pendingPrestige;
profile.ProfileInfo.IsWiped = true;
_saveServer.SaveProfile(sessionId);
await _saveServer.SaveProfileAsync(sessionId);
}
}
}
@@ -118,9 +118,9 @@ public class ProfileController(
/// <param name="request">Create profile request</param>
/// <param name="sessionId">Player id</param>
/// <returns>Player id</returns>
public virtual string CreateProfile(ProfileCreateRequestData request, string sessionId)
public virtual async ValueTask<string> CreateProfile(ProfileCreateRequestData request, string sessionId)
{
return _createProfileService.CreateProfile(sessionId, request);
return await _createProfileService.CreateProfile(sessionId, request);
}
/// <summary>
@@ -56,7 +56,7 @@ public class SaveServer(
/// <summary>
/// Load all profiles in /user/profiles folder into memory (this.profiles)
/// </summary>
public void Load()
public async Task LoadAsync()
{
// get files to load
if (!_fileUtil.DirectoryExists(profileFilepath))
@@ -70,7 +70,7 @@ public class SaveServer(
var stopwatch = Stopwatch.StartNew();
foreach (var file in files)
{
LoadProfile(_fileUtil.StripExtension(file));
await LoadProfileAsync(_fileUtil.StripExtension(file));
}
stopwatch.Stop();
@@ -83,13 +83,13 @@ public class SaveServer(
/// <summary>
/// Save changes for each profile from memory into user/profiles json
/// </summary>
public void Save()
public async Task SaveAsync()
{
// Save every profile
var totalTime = 0L;
foreach (var sessionID in profiles)
{
totalTime += SaveProfile(sessionID.Key);
totalTime += await SaveProfileAsync(sessionID.Key);
}
if (_logger.IsLogEnabled(LogLevel.Debug))
@@ -196,14 +196,14 @@ public class SaveServer(
/// Execute saveLoadRouters callbacks after being loaded into memory.
/// </summary>
/// <param name="sessionID"> ID of profile to store in memory </param>
public void LoadProfile(string sessionID)
public async Task LoadProfileAsync(string sessionID)
{
var filename = $"{sessionID}.json";
var filePath = $"{profileFilepath}{filename}";
if (_fileUtil.FileExists(filePath))
// File found, store in profiles[]
{
profiles[sessionID] = _jsonUtil.DeserializeFromFile<SptProfile>(filePath);
profiles[sessionID] = await _jsonUtil.DeserializeFromFileAsync<SptProfile>(filePath);
}
// Run callbacks
@@ -220,7 +220,7 @@ public class SaveServer(
/// </summary>
/// <param name="sessionID"> Profile id (user/profiles/id.json) </param>
/// <returns> Time taken to save the profile in seconds </returns>
public long SaveProfile(string sessionID)
public async Task<long> SaveProfileAsync(string sessionID)
{
var filePath = $"{profileFilepath}{sessionID}.json";
@@ -250,12 +250,12 @@ public class SaveServer(
var start = Stopwatch.StartNew();
var jsonProfile = _jsonUtil.Serialize(profiles[sessionID], !_configServer.GetConfig<CoreConfig>().Features.CompressProfile);
var fmd5 = _hashUtil.GenerateMd5ForData(jsonProfile);
var fmd5 = await _hashUtil.GenerateHashForDataAsync(HashingAlgorithm.MD5, jsonProfile);
if (!saveMd5.TryGetValue(sessionID, out var currentMd5) || currentMd5 != fmd5)
{
saveMd5[sessionID] = fmd5;
// save profile to disk
_fileUtil.WriteFile(filePath, jsonProfile);
await _fileUtil.WriteFileAsync(filePath, jsonProfile);
}
start.Stop();
@@ -46,22 +46,22 @@ public class BackupService
/// <summary>
/// Start the backup interval if enabled in config.
/// </summary>
public void StartBackupSystem()
public async Task StartBackupSystem()
{
if (!_backupConfig.BackupInterval.Enabled)
{
// Not backing up at regular intervals, run once and exit
Init();
await Init();
return;
}
_backupIntervalTimer = new Timer(
_backupIntervalTimer = new Timer(async
_ =>
{
try
{
Init();
await Init();
}
catch (Exception ex)
{
@@ -79,7 +79,7 @@ public class BackupService
/// This method orchestrates the profile backup service. Handles copying profiles to a backup directory and cleaning
/// up old backups if the number exceeds the configured maximum.
/// </summary>
public void Init()
public async Task Init()
{
if (!IsEnabled())
{
@@ -129,7 +129,7 @@ public class BackupService
}
// Write a copy of active mods.
_fileUtil.WriteFile(Path.Combine(targetDir, "activeMods.json"), _jsonUtil.Serialize(_activeServerMods));
await _fileUtil.WriteFileAsync(Path.Combine(targetDir, "activeMods.json"), _jsonUtil.Serialize(_activeServerMods));
if (_logger.IsLogEnabled(LogLevel.Debug))
{
@@ -43,7 +43,7 @@ public class BundleHashCacheService(
public bool CalculateAndMatchHash(string bundlePath)
{
var fileContents = _fileUtil.ReadFile(bundlePath);
var generatedHash = _hashUtil.GenerateMd5ForData(fileContents);
var generatedHash = _hashUtil.GenerateHashForData(HashingAlgorithm.MD5, fileContents);
return MatchWithStoredHash(bundlePath, generatedHash);
}
@@ -51,7 +51,7 @@ public class BundleHashCacheService(
public void CalculateAndStoreHash(string bundlePath)
{
var fileContents = _fileUtil.ReadFile(bundlePath);
var generatedHash = _hashUtil.GenerateMd5ForData(fileContents);
var generatedHash = _hashUtil.GenerateHashForData(HashingAlgorithm.MD5, fileContents);
StoreValue(bundlePath, generatedHash);
}
@@ -42,14 +42,14 @@ public class ModHashCacheService(
public bool CalculateAndCompareHash(string modName, string modContent)
{
var generatedHash = _hashUtil.GenerateSha1ForData(modContent);
var generatedHash = _hashUtil.GenerateHashForData(HashingAlgorithm.SHA1, modContent);
return MatchWithStoredHash(modName, generatedHash);
}
public void CalculateAndStoreHash(string modName, string modContent)
{
var generatedHash = _hashUtil.GenerateSha1ForData(modContent);
var generatedHash = _hashUtil.GenerateHashForData(HashingAlgorithm.SHA1, modContent);
StoreValue(modName, generatedHash);
}
@@ -549,7 +549,7 @@ public class CircleOfCultistService(
// Key is sacrificed items separated by commas, a dash, then the rewards separated by commas
var key = $"{{{required}-{reward}}}";
return _hashUtil.GenerateMd5ForData(key);
return _hashUtil.GenerateHashForData(HashingAlgorithm.MD5, key);
}
/// <summary>
@@ -887,7 +887,7 @@ public class CircleOfCultistService(
protected string CreateSacrificeCacheKey(IEnumerable<string> requiredItems)
{
var concat = string.Join(",", requiredItems.OrderBy(item => item));
return _hashUtil.GenerateMd5ForData(concat);
return _hashUtil.GenerateHashForData(HashingAlgorithm.MD5, concat);
}
/// <summary>
@@ -40,7 +40,7 @@ public class CreateProfileService(
MailSendService _mailSendService
)
{
public string CreateProfile(string sessionId, ProfileCreateRequestData request)
public async ValueTask<string> CreateProfile(string sessionId, ProfileCreateRequestData request)
{
var account = _cloner.Clone(_saveServer.GetProfile(sessionId));
var profileTemplateClone = _cloner.Clone(_profileHelper.GetProfileTemplateForSide(account.ProfileInfo.Edition, request.Side));
@@ -207,12 +207,12 @@ public class CreateProfileService(
_saveServer.GetProfile(sessionId).CharacterData.ScavData = _playerScavGenerator.Generate(sessionId);
// Store minimal profile and reload it
_saveServer.SaveProfile(sessionId);
_saveServer.LoadProfile(sessionId);
await _saveServer.SaveProfileAsync(sessionId);
await _saveServer.LoadProfileAsync(sessionId);
// Completed account creation
_saveServer.GetProfile(sessionId).ProfileInfo.IsWiped = false;
_saveServer.SaveProfile(sessionId);
await _saveServer.SaveProfileAsync(sessionId);
return pmcData.Id;
}
@@ -722,7 +722,7 @@ public class LocationLifecycleService
pmcProfile.Info.LastTimePlayedAsSavage = _timeUtil.GetTimeStamp();
// Force a profile save
_saveServer.SaveProfile(sessionId);
_saveServer.SaveProfileAsync(sessionId);
}
/// <summary>
@@ -90,6 +90,31 @@ public class FileUtil()
File.WriteAllBytes(filePath, fileContent);
}
public async Task WriteFileAsync(string filePath, string fileContent)
{
if (!DirectoryExists(Path.GetDirectoryName(filePath)))
{
CreateDirectory(Path.GetDirectoryName(filePath));
}
if (!FileExists(filePath))
{
CreateFile(filePath);
}
await File.WriteAllTextAsync(filePath, fileContent);
}
public async Task WriteFileAsync(string filePath, byte[] fileContent)
{
if (!FileExists(filePath))
{
CreateFile(filePath);
}
await File.WriteAllBytesAsync(filePath, fileContent);
}
private void CreateFile(string filePath)
{
var stream = File.Create(filePath);
@@ -50,16 +50,6 @@ public partial class HashUtil(RandomUtil _randomUtil)
return MongoIdRegex().IsMatch(stringToCheck);
}
public string GenerateMd5ForData(string data)
{
return GenerateHashForData(HashingAlgorithm.MD5, data);
}
public string GenerateSha1ForData(string data)
{
return GenerateHashForData(HashingAlgorithm.SHA1, data);
}
public uint GenerateCrc32ForData(string data)
{
return Crc32.HashToUInt32(new ArraySegment<byte>(Encoding.UTF8.GetBytes(data)));
@@ -89,6 +79,36 @@ public partial class HashUtil(RandomUtil _randomUtil)
throw new NotImplementedException($"Provided hash algorithm: {algorithm} is not supported.");
}
/// <summary>
/// Create a hash for the data parameter asynchronously
/// </summary>
/// <param name="algorithm">algorithm to use to hash</param>
/// <param name="data">data to be hashed</param>
/// <returns>A task which contains the hash value</returns>
/// <exception cref="NotImplementedException">thrown if the provided algorithm is not implemented</exception>
/// >
public async Task<string> GenerateHashForDataAsync(HashingAlgorithm algorithm, string data)
{
switch (algorithm)
{
case HashingAlgorithm.MD5:
{
await using var ms = new MemoryStream(Encoding.UTF8.GetBytes(data));
var md5HashData = await MD5.HashDataAsync(ms);
return Convert.ToHexString(md5HashData).Replace("-", string.Empty);
}
case HashingAlgorithm.SHA1:
{
await using var ms = new MemoryStream(Encoding.UTF8.GetBytes(data));
var sha1HashData = await SHA1.HashDataAsync(ms);
return Convert.ToHexString(sha1HashData).Replace("-", string.Empty);
}
}
throw new NotImplementedException($"Provided hash algorithm: {algorithm} is not supported.");
}
/// <summary>
/// Generates an account ID for a profile
/// </summary>