From 8e808408eb32a36a52295c9f76303790e618bc65 Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Fri, 29 May 2026 13:28:25 +0200 Subject: [PATCH 1/6] Add AssetFlags.Imagery --- Refresh.Database/Models/Assets/AssetFlags.cs | 36 +++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/Refresh.Database/Models/Assets/AssetFlags.cs b/Refresh.Database/Models/Assets/AssetFlags.cs index fa4b1a9b..107f92a2 100644 --- a/Refresh.Database/Models/Assets/AssetFlags.cs +++ b/Refresh.Database/Models/Assets/AssetFlags.cs @@ -20,6 +20,10 @@ public enum AssetFlags /// A planet is considered modded if it depends on this asset, or if the asset already has the Modded flag above. /// ModdedOnPlanets = 1 << 3, + /// + /// This asset is a texture or contains imagery, for now only given to asset types handled by image conversion. + /// + Imagery = 1 << 4, } public static class AssetSafetyLevelExtensions @@ -30,33 +34,33 @@ public static AssetFlags FromAssetType(GameAssetType type, GameAssetFormat? meth { // Common asset types created by the game GameAssetType.Level => AssetFlags.None, - GameAssetType.StreamingLevelChunk => AssetFlags.None | AssetFlags.ModdedOnPlanets, + GameAssetType.StreamingLevelChunk => AssetFlags.ModdedOnPlanets, GameAssetType.Plan => AssetFlags.None, - GameAssetType.ThingRecording => AssetFlags.None | AssetFlags.ModdedOnPlanets, - GameAssetType.SyncedProfile => AssetFlags.None | AssetFlags.ModdedOnPlanets, - GameAssetType.GriefSongState => AssetFlags.None | AssetFlags.ModdedOnPlanets, - GameAssetType.Quest => AssetFlags.None | AssetFlags.ModdedOnPlanets, - GameAssetType.AdventureSharedData => AssetFlags.None | AssetFlags.ModdedOnPlanets, - GameAssetType.AdventureCreateProfile => AssetFlags.None | AssetFlags.ModdedOnPlanets, - GameAssetType.ChallengeGhost => AssetFlags.None | AssetFlags.ModdedOnPlanets, + GameAssetType.ThingRecording => AssetFlags.ModdedOnPlanets, + GameAssetType.SyncedProfile => AssetFlags.ModdedOnPlanets, + GameAssetType.GriefSongState => AssetFlags.ModdedOnPlanets, + GameAssetType.Quest => AssetFlags.ModdedOnPlanets, + GameAssetType.AdventureSharedData => AssetFlags.ModdedOnPlanets, + GameAssetType.AdventureCreateProfile => AssetFlags.ModdedOnPlanets, + GameAssetType.ChallengeGhost => AssetFlags.ModdedOnPlanets, // Common media types created by the game GameAssetType.VoiceRecording => AssetFlags.Media | AssetFlags.ModdedOnPlanets, GameAssetType.Painting => AssetFlags.Media, - GameAssetType.Texture => AssetFlags.Media, - GameAssetType.Jpeg => AssetFlags.Media, - GameAssetType.Png => AssetFlags.Media, - GameAssetType.Tga => AssetFlags.Media | AssetFlags.ModdedOnPlanets, - GameAssetType.Mip => AssetFlags.Media | AssetFlags.ModdedOnPlanets, + GameAssetType.Texture => AssetFlags.Media | AssetFlags.Imagery, + GameAssetType.Jpeg => AssetFlags.Media | AssetFlags.Imagery, + GameAssetType.Png => AssetFlags.Media | AssetFlags.Imagery, + GameAssetType.Tga => AssetFlags.Media | AssetFlags.Imagery | AssetFlags.ModdedOnPlanets, + GameAssetType.Mip => AssetFlags.Media | AssetFlags.Imagery | AssetFlags.ModdedOnPlanets, // Uncommon, but still vanilla assets created by the game in niche scenarios. // While not image/audio data like the other media types, GfxMaterial is marked as media because this file can contain full PS3 shaders. GameAssetType.GfxMaterial => AssetFlags.Media | AssetFlags.ModdedOnPlanets, - GameAssetType.Material => AssetFlags.None | AssetFlags.ModdedOnPlanets, - GameAssetType.Bevel => AssetFlags.None | AssetFlags.ModdedOnPlanets, + GameAssetType.Material => AssetFlags.ModdedOnPlanets, + GameAssetType.Bevel => AssetFlags.ModdedOnPlanets, // Modded media types - GameAssetType.GameDataTexture => AssetFlags.Media | AssetFlags.Modded, + GameAssetType.GameDataTexture => AssetFlags.Media | AssetFlags.Modded | AssetFlags.Imagery, GameAssetType.AnimatedTexture => AssetFlags.Media | AssetFlags.Modded, // Normal modded assets From 1381db92113658616e95235d2810612a012937b8 Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Fri, 29 May 2026 14:50:19 +0200 Subject: [PATCH 2/6] Catch IsPspTga() exceptions --- Refresh.Core/Importing/Importer.cs | 60 +++++++++++++++++------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/Refresh.Core/Importing/Importer.cs b/Refresh.Core/Importing/Importer.cs index d38a968f..a105a269 100644 --- a/Refresh.Core/Importing/Importer.cs +++ b/Refresh.Core/Importing/Importer.cs @@ -107,32 +107,40 @@ private static bool MatchesMagic(ReadOnlySpan data, ulong magic) /// Whether the file is likely of TGA format private static bool IsPspTga(ReadOnlySpan data) { - byte imageIdLength = data[0]; - byte colorMapType = data[1]; - byte imageType = data[2]; - ReadOnlySpan colorMapSpecification = data[3..8]; - ReadOnlySpan imageSpecification = data[8..18]; - short xOrigin = BinaryPrimitives.ReadInt16LittleEndian(imageSpecification[..2]); - short yOrigin = BinaryPrimitives.ReadInt16LittleEndian(imageSpecification[2..4]); - ushort width = BinaryPrimitives.ReadUInt16LittleEndian(imageSpecification[4..6]); - ushort height = BinaryPrimitives.ReadUInt16LittleEndian(imageSpecification[6..8]); - byte depth = imageSpecification[8]; - byte descriptor = imageSpecification[9]; - - //PSP does not seem to fill out this information - if (imageIdLength != 0) return false; - if (xOrigin != 0) return false; - if (yOrigin != 0) return false; - //These are the fields set by PSP, that shouldn't change from image to image - if (colorMapType != 1) return false; - if (descriptor != 0) return false; - if (imageType != 1) return false; - if (depth != 8) return false; - //Reasonable validation checks (PSP seems to only send images of max size 480x272) - if (width > 500) return false; - if (height > 300) return false; - - return true; + try + { + byte imageIdLength = data[0]; + byte colorMapType = data[1]; + byte imageType = data[2]; + ReadOnlySpan colorMapSpecification = data[3..8]; + ReadOnlySpan imageSpecification = data[8..18]; + short xOrigin = BinaryPrimitives.ReadInt16LittleEndian(imageSpecification[..2]); + short yOrigin = BinaryPrimitives.ReadInt16LittleEndian(imageSpecification[2..4]); + ushort width = BinaryPrimitives.ReadUInt16LittleEndian(imageSpecification[4..6]); + ushort height = BinaryPrimitives.ReadUInt16LittleEndian(imageSpecification[6..8]); + byte depth = imageSpecification[8]; + byte descriptor = imageSpecification[9]; + + //PSP does not seem to fill out this information + if (imageIdLength != 0) return false; + if (xOrigin != 0) return false; + if (yOrigin != 0) return false; + //These are the fields set by PSP, that shouldn't change from image to image + if (colorMapType != 1) return false; + if (descriptor != 0) return false; + if (imageType != 1) return false; + if (depth != 8) return false; + //Reasonable validation checks (PSP seems to only send images of max size 480x272) + if (width > 500) return false; + if (height > 300) return false; + + return true; + } + catch + { + //If the data couldn't be read, it's not a TGA + return false; + } } private bool IsMip(Span rawData) From da88500881189ba50d249f359b96cf01d07431e4 Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Fri, 29 May 2026 17:57:48 +0200 Subject: [PATCH 3/6] Implement ResourceValidationHelper --- .../Helpers/ResourceValidationHelper.cs | 116 ++++++++++++++++++ .../Validation/AssetValidationParameters.cs | 65 ++++++++++ .../Assets/Validation/ValidatedAssetResult.cs | 43 +++++++ 3 files changed, 224 insertions(+) create mode 100644 Refresh.Core/Helpers/ResourceValidationHelper.cs create mode 100644 Refresh.Core/Types/Assets/Validation/AssetValidationParameters.cs create mode 100644 Refresh.Core/Types/Assets/Validation/ValidatedAssetResult.cs diff --git a/Refresh.Core/Helpers/ResourceValidationHelper.cs b/Refresh.Core/Helpers/ResourceValidationHelper.cs new file mode 100644 index 00000000..ba7be246 --- /dev/null +++ b/Refresh.Core/Helpers/ResourceValidationHelper.cs @@ -0,0 +1,116 @@ +using System.Diagnostics; +using Bunkum.Core; +using NotEnoughLogs; +using Refresh.Common.Verification; +using Refresh.Core.Types.Assets.Validation; +using Refresh.Database.Models.Assets; +using Refresh.Database.Models.Authentication; + +namespace Refresh.Core.Helpers; + +public abstract class ResourceValidationHelper +{ + public static ValidatedAssetResult Validate(AssetValidationParameters parameters, Logger logger) + { + string assetTypeStr = parameters.AssetContextTypeStr ?? (parameters.MustBeTexture ? "image asset" : "asset"); + GameAsset? asset = null; + bool existsInDataStore = false; + bool isPSP = parameters.GameToUseIn == TokenGame.LittleBigPlanetPSP; + Action? onNewAssetKeyCallback = parameters.OnNewAssetKeyCallback; + + if (parameters.AssetKey.IsBlankHash()) + { + if (!parameters.MayBeBlank) return new(BadRequest, "0", $"The {assetTypeStr} must be set.", onNewAssetKeyCallback); + else return new(OK, "0", null, onNewAssetKeyCallback); + } + + else if (parameters.AssetKey.StartsWith('g')) + { + if (!parameters.MayBeGuid) return new(BadRequest, null, $"The {assetTypeStr} may not be an in-game asset.", onNewAssetKeyCallback); + if (parameters.AssetKey.Length < 2) return new(BadRequest, null, $"The used in-game {assetTypeStr} is invalid (empty GUID).", onNewAssetKeyCallback); + + // This should only happen if the user is messing with mods/the API/beta builds, so give them a more detailed response + bool canParseGuid = long.TryParse(parameters.AssetKey[1..], out long guid); + if (!canParseGuid) + return new(BadRequest, null, $"The used in-game {assetTypeStr} is invalid (badly formatted GUID).", onNewAssetKeyCallback); + + if (parameters.MustBeTexture && !parameters.GuidChecker.IsTextureGuid(parameters.GameToUseIn, guid)) + return new(BadRequest, null, $"The used in-game {assetTypeStr} was not a valid image (unknown GUID).", onNewAssetKeyCallback); + } + + // At this point the reference is a hash + else if (!parameters.MayBeHash) + { + return new(BadRequest, null, $"The {assetTypeStr} may not be a custom asset.", onNewAssetKeyCallback); + } + + else if (!CommonPatterns.Sha1Regex().IsMatch(parameters.AssetKey)) + { + // This should only happen if a player is messing with mods/the API, so give them a more detailed response + return new(BadRequest, null, $"The used {assetTypeStr} had an invalid hash.", onNewAssetKeyCallback); + } + + else + { + DisallowedAsset? disallowed = parameters.Database.GetDisallowedAssetInfo(parameters.AssetKey); + if (disallowed != null) + { + logger.LogWarning(BunkumCategory.UserContent, $"{parameters.User} tried to use a manually disallowed {assetTypeStr}."); + return new(Unauthorized, disallowanceInfo: disallowed, onNewAssetKeyCallback: onNewAssetKeyCallback); + } + + string filename = isPSP ? $"psp/{parameters.AssetKey}" : parameters.AssetKey; + existsInDataStore = parameters.DataStore.ExistsInStore(filename); + + if (!existsInDataStore) + { + logger.LogDebug(BunkumCategory.UserContent, $"Referenced asset '{filename}' could not be found in data store."); + + if (parameters.MustBeInDataStoreIfHash) + return new(NotFound, null, $"The used {assetTypeStr} did not exist on the server.", onNewAssetKeyCallback); + } + + asset = parameters.Cache.GetAssetInfo(parameters.AssetKey, parameters.Database); + + // Only try to import if the asset exists in the data store + if (existsInDataStore && asset == null) + { + logger.LogInfo(BunkumCategory.UserContent, $"Referenced asset '{filename}' exists in data store but not in database, attempting to import automatically..."); + Stopwatch sw = new(); + sw.Start(); + + if (!parameters.DataStore.TryGetDataFromStore(filename, out byte[]? assetData) || assetData == null) + { + sw.Stop(); + logger.LogError(BunkumCategory.UserContent, $"Failed to read '{filename}' from data store!"); + logger.LogDebug(BunkumCategory.UserContent, $"Failed to get '{filename}' after {sw.ElapsedMilliseconds}ms."); + return new(InternalServerError, null, $"Failed to read {assetTypeStr} internally. Please report this to the server owner.", onNewAssetKeyCallback, existsInDataStore: existsInDataStore); + } + + asset = parameters.AssetImporter.ReadAndVerifyAsset(parameters.AssetKey, assetData, parameters.PlatformToUseIn, parameters.Database); + if (asset == null) + { + sw.Stop(); + logger.LogDebug(BunkumCategory.UserContent, $"Failed to get '{filename}' after {sw.ElapsedMilliseconds}ms."); + return new(BadRequest, null, $"The used {assetTypeStr} was invalid or corrupt.", onNewAssetKeyCallback, existsInDataStore: existsInDataStore); + } + + sw.Stop(); + logger.LogInfo(BunkumCategory.UserContent, $"Successfully imported '{filename}' in {sw.ElapsedMilliseconds}ms."); + } + + // FIXME: for some reason, PSP texture detection/conversion broke so we can no longer tell if a PSP texture is actually a texture, so skip this for PSP + if (asset != null && !isPSP) + { + bool isHashedTexture = (asset.AssetFlags & AssetFlags.Imagery) != 0; + + if (parameters.MustBeTexture && !isHashedTexture) + return new(BadRequest, null, $"The used {assetTypeStr} was not a valid custom image.", onNewAssetKeyCallback, assetInfo: asset, existsInDataStore: existsInDataStore); + + // TODO: actually use AIPI to scan image if not null + } + } + + return new(OK, parameters.AssetKey, null, onNewAssetKeyCallback, assetInfo: asset, existsInDataStore: existsInDataStore); + } +} \ No newline at end of file diff --git a/Refresh.Core/Types/Assets/Validation/AssetValidationParameters.cs b/Refresh.Core/Types/Assets/Validation/AssetValidationParameters.cs new file mode 100644 index 00000000..e61398d1 --- /dev/null +++ b/Refresh.Core/Types/Assets/Validation/AssetValidationParameters.cs @@ -0,0 +1,65 @@ +using Bunkum.Core.Storage; +using Refresh.Core.Importing; +using Refresh.Core.Services; +using Refresh.Core.Types.Data; +using Refresh.Database; +using Refresh.Database.Models.Authentication; +using Refresh.Database.Models.Users; + +namespace Refresh.Core.Types.Assets.Validation; + +public struct AssetValidationParameters +{ + /// + /// The hash/guid/blank to validate + /// + public string AssetKey { get; set; } = "0"; + public GameUser? User { get; set; } + public TokenGame GameToUseIn { get; set; } + public TokenPlatform PlatformToUseIn { get; set; } + public GameDatabaseContext Database { get; set; } = null!; + public IDataStore DataStore { get; set; } = null!; + public CacheService Cache { get; set; } = null!; + public GuidCheckerService GuidChecker { get; set; } = null!; + public AssetImporter AssetImporter { get; set; } = null!; + public AipiService? Aipi { get; set; } + + public bool MayBeBlank { get; set; } = true; + public bool MayBeGuid { get; set; } = true; + public bool MayBeHash { get; set; } = true; + public bool MustBeInDataStoreIfHash { get; set; } = true; + public bool MustBeTexture { get; set; } = false; + + /// + /// What the asset should be referred as in user-faced error messages and in logs, e.g. "planet asset" or "icon". + /// If null, we will default to calling it "asset" or "image" depending on MustBeTexture. + /// + public string? AssetContextTypeStr { get; set; } + + /// + /// Callback which is called with the new asset reference/key as parameter when constructing ; + /// useful to update asset references of entities during validation without requiring the caller to manually reassign them after validation; + /// this way similar attributes like photo images or face icons can simply be iterated. + /// If null, this will be skipped. + /// + public Action? OnNewAssetKeyCallback { get; set; } + + public AssetValidationParameters(string assetKey, DataContext dataContext, AssetImporter assetImporter, AipiService? aipi = null) + { + this.AssetKey = assetKey; + this.User = dataContext.User; + this.GameToUseIn = dataContext.Game; + this.PlatformToUseIn = dataContext.Platform; + this.Database = dataContext.Database; + this.DataStore = dataContext.DataStore; + this.Cache = dataContext.Cache; + this.GuidChecker = dataContext.GuidChecker; + this.AssetImporter = assetImporter; + this.Aipi = aipi; + } + + public AssetValidationParameters() + { + + } +} \ No newline at end of file diff --git a/Refresh.Core/Types/Assets/Validation/ValidatedAssetResult.cs b/Refresh.Core/Types/Assets/Validation/ValidatedAssetResult.cs new file mode 100644 index 00000000..32a9ffe5 --- /dev/null +++ b/Refresh.Core/Types/Assets/Validation/ValidatedAssetResult.cs @@ -0,0 +1,43 @@ +using System.Net; +using Refresh.Database.Models.Assets; + +namespace Refresh.Core.Types.Assets.Validation; + +// key = blank/guid/hash, whatever is used to identify the asset +public struct ValidatedAssetResult +{ + /// + /// HTTP code to return if validation failed. + /// OK: don't cancel request and proceed. + /// + public HttpStatusCode Status { get; set; } + + /// + /// new hash/guid/blank to use + /// + public string NewKey { get; set; } + + /// + /// message to show to the user. + /// null: don't show anything. + /// + public string? ErrorMessage { get; set; } + + public GameAsset? AssetInfo { get; set; } + public DisallowedAsset? DisallowanceInfo { get; set; } + public bool ExistsInDataStore { get; set; } + + public ValidatedAssetResult(HttpStatusCode status, string? newKey = null, string? errorMessage = null, Action? onNewAssetKeyCallback = null, + GameAsset? assetInfo = null, DisallowedAsset? disallowanceInfo = null, bool existsInDataStore = false) + { + this.Status = status; + this.NewKey = newKey ?? "0"; + this.ErrorMessage = errorMessage; + this.AssetInfo = assetInfo; + this.DisallowanceInfo = disallowanceInfo; + this.ExistsInDataStore = existsInDataStore; + + if (onNewAssetKeyCallback != null) + onNewAssetKeyCallback(this.NewKey); + } +} \ No newline at end of file From c424722db55cb50274065265537226d153d6b3bf Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Fri, 29 May 2026 18:01:08 +0200 Subject: [PATCH 4/6] TestContext: Allow outputting Token when authenticating, allow specifying DataStore when generating DataContext --- RefreshTests.GameServer/TestContext.cs | 34 +++++++++++++++++++------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/RefreshTests.GameServer/TestContext.cs b/RefreshTests.GameServer/TestContext.cs index fc126a12..aa6a8ea3 100644 --- a/RefreshTests.GameServer/TestContext.cs +++ b/RefreshTests.GameServer/TestContext.cs @@ -56,7 +56,7 @@ public HttpClient GetAuthenticatedClient(TokenType type, TokenGame game, TokenPl int tokenExpirySeconds = GameDatabaseContext.DefaultTokenExpirySeconds, string? ipAddress = null) { - return this.GetAuthenticatedClient(type, game, platform, out _, user, tokenExpirySeconds, ipAddress); + return this.GetAuthenticatedClient(type, game, platform, out string _, user, tokenExpirySeconds, ipAddress); } public HttpClient GetAuthenticatedClient(TokenType type, out string tokenData, @@ -86,25 +86,41 @@ public HttpClient GetAuthenticatedClient(TokenType type, TokenGame game, TokenPl int tokenExpirySeconds = GameDatabaseContext.DefaultTokenExpirySeconds, string? ipAddress = null) { - user ??= this.CreateUser(); - - Token token = this.Database.GenerateTokenForUser(user, type, game, platform, ipAddress ?? "0.0.0.0", tokenExpirySeconds); + Token token = this.GenerateToken(user, type, game, platform, ipAddress, tokenExpirySeconds); tokenData = token.TokenData; - + return this.GetAuthenticatedClient(token.TokenData, type); + } + + public HttpClient GetAuthenticatedClient(TokenType type, TokenGame game, TokenPlatform platform, out Token token, + GameUser? user = null, + int tokenExpirySeconds = GameDatabaseContext.DefaultTokenExpirySeconds, + string? ipAddress = null) + { + token = this.GenerateToken(user, type, game, platform, ipAddress, tokenExpirySeconds); + return this.GetAuthenticatedClient(token.TokenData, type); + } + + public HttpClient GetAuthenticatedClient(string tokenData, TokenType type) + { HttpClient client = this.Listener.GetClient(); if (type == TokenType.Game) { - client.DefaultRequestHeaders.Add("Cookie", "MM_AUTH=" + token.TokenData); + client.DefaultRequestHeaders.Add("Cookie", "MM_AUTH=" + tokenData); } else { - client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", token.TokenData); + client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", tokenData); } return client; } + public Token GenerateToken(GameUser? user, TokenType type, TokenGame game, TokenPlatform platform, string? ipAddress, int tokenExpirySeconds) + { + return this.Database.GenerateTokenForUser(user ?? this.CreateUser(), type, game, platform, ipAddress ?? "0.0.0.0", tokenExpirySeconds); + } + public GameUser CreateUser(string? username = null, GameUserRole role = GameUserRole.User, bool verifyEmail = true) { username ??= this.UserIncrement.ToString(); @@ -218,13 +234,13 @@ public GamePlaylist CreatePlaylist(GameUser author, string? title = null) [Pure] public TService GetService() where TService : Service => this.Server.Value.GetService(); - public DataContext GetDataContext(Token? token = null) + public DataContext GetDataContext(Token? token = null, IDataStore? dataStore = null) { return new DataContext { Database = this.Database, Logger = this.Server.Value.Logger, - DataStore = (IDataStore)this.GetService() + DataStore = dataStore ?? (IDataStore)this.GetService() .AddParameterToEndpoint(null!, new BunkumParameterInfo(typeof(IDataStore), ""), null!)!, Match = this.GetService(), Token = token, From b4ccec262b6a4c09d8023a9e8a491f536ced52b6 Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Fri, 29 May 2026 18:01:52 +0200 Subject: [PATCH 5/6] Test ResourceValidationHelper --- .../Assets/AssetReferenceValidationTests.cs | 527 ++++++++++++++++++ 1 file changed, 527 insertions(+) create mode 100644 RefreshTests.GameServer/Tests/Assets/AssetReferenceValidationTests.cs diff --git a/RefreshTests.GameServer/Tests/Assets/AssetReferenceValidationTests.cs b/RefreshTests.GameServer/Tests/Assets/AssetReferenceValidationTests.cs new file mode 100644 index 00000000..6c41beeb --- /dev/null +++ b/RefreshTests.GameServer/Tests/Assets/AssetReferenceValidationTests.cs @@ -0,0 +1,527 @@ +using System.Security.Cryptography; +using Refresh.Core.Helpers; +using Refresh.Core.Importing; +using Refresh.Core.Types.Assets.Validation; +using Refresh.Core.Types.Data; +using Refresh.Database.Models.Assets; +using Refresh.Database.Models.Authentication; +using Refresh.Database.Models.Users; + +namespace RefreshTests.GameServer.Tests.Assets; + +public class AssetReferenceValidationTests : GameServerTest +{ + private const string ValidImageGuid = "g18451"; // star sticker texture + private const string InvalidImageGuid = "g1087"; // sackboy model + + [Test] + [TestCase("")] + [TestCase(" ")] + [TestCase(" ")] + [TestCase("0")] + public void AcceptBlankHash(string blankHashVariation) + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, out Token token, user); + DataContext dataContext = context.GetDataContext(token); + AssetImporter importer = new(dataContext.Logger, context.Time); + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new(blankHashVariation, dataContext, importer) + { + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + Assert.That(result.Status, Is.EqualTo(OK)); + Assert.That(result.NewKey, Is.EqualTo("0")); + Assert.That(newKeySetByCallback, Is.EqualTo("0")); + Assert.That(result.AssetInfo, Is.Null); + } + + [Test] + [TestCase("")] + [TestCase(" ")] + [TestCase(" ")] + [TestCase("0")] + public void RejectBlankHashIfDisallowed(string blankHashVariation) + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, out Token token, user); + DataContext dataContext = context.GetDataContext(token); + AssetImporter importer = new(dataContext.Logger, context.Time); + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new(blankHashVariation, dataContext, importer) + { + MayBeBlank = false, + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + Assert.That(result.Status, Is.EqualTo(BadRequest)); + Assert.That(result.ErrorMessage, Is.Not.Null); + Assert.That(result.NewKey, Is.EqualTo("0")); + Assert.That(newKeySetByCallback, Is.EqualTo("0")); + } + + [Test] + [TestCase(ValidImageGuid)] + [TestCase(InvalidImageGuid)] + public void AcceptGuidsIfNotRestricted(string guid) + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, out Token token, user); + DataContext dataContext = context.GetDataContext(token); + AssetImporter importer = new(dataContext.Logger, context.Time); + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new(guid, dataContext, importer) + { + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + Assert.That(result.Status, Is.EqualTo(OK)); + Assert.That(result.NewKey, Is.EqualTo(guid)); + Assert.That(newKeySetByCallback, Is.EqualTo(guid)); + Assert.That(result.ErrorMessage, Is.Null); + } + + [Test] + [TestCase(InvalidImageGuid)] + [TestCase(ValidImageGuid)] + public void RejectGuidIfDisallowed(string guid) + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, out Token token, user); + DataContext dataContext = context.GetDataContext(token); + AssetImporter importer = new(dataContext.Logger, context.Time); + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new(guid, dataContext, importer) + { + MayBeGuid = false, + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + Assert.That(result.Status, Is.EqualTo(BadRequest)); + Assert.That(result.ErrorMessage, Is.Not.Null); + Assert.That(result.NewKey, Is.EqualTo("0")); + Assert.That(newKeySetByCallback, Is.EqualTo("0")); + } + + [Test] + [TestCase("g")] + [TestCase("greg")] + [TestCase("g67676767676767676767676767676776767676767")] + public void RejectGuidIfBadlyFormatted(string guid) + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, out Token token, user); + DataContext dataContext = context.GetDataContext(token); + AssetImporter importer = new(dataContext.Logger, context.Time); + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new(guid, dataContext, importer) + { + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + Assert.That(result.Status, Is.EqualTo(BadRequest)); + Assert.That(result.ErrorMessage, Is.Not.Null); + Assert.That(result.NewKey, Is.EqualTo("0")); + Assert.That(newKeySetByCallback, Is.EqualTo("0")); + } + + [Test] + public void RejectNonTextureGuidIfOnlyTexturesAllowed() + { + string guid = InvalidImageGuid; + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, out Token token, user); + DataContext dataContext = context.GetDataContext(token); + AssetImporter importer = new(dataContext.Logger, context.Time); + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new(guid, dataContext, importer) + { + MustBeTexture = true, + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + Assert.That(result.Status, Is.EqualTo(BadRequest)); + Assert.That(result.ErrorMessage, Is.Not.Null); + Assert.That(result.NewKey, Is.EqualTo("0")); + Assert.That(newKeySetByCallback, Is.EqualTo("0")); + } + + [Test] + public void AcceptTextureGuidIfOnlyTexturesAllowed() + { + string guid = ValidImageGuid; + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, out Token token, user); + DataContext dataContext = context.GetDataContext(token); + AssetImporter importer = new(dataContext.Logger, context.Time); + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new(guid, dataContext, importer) + { + MustBeTexture = true, + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + Assert.That(result.Status, Is.EqualTo(OK)); + Assert.That(result.AssetInfo, Is.Null); + Assert.That(result.NewKey, Is.EqualTo(guid)); + Assert.That(newKeySetByCallback, Is.EqualTo(guid)); + } + + [Test] + [TestCase(true, false)] + [TestCase(false, false)] + [TestCase(true, true)] + [TestCase(false, true)] + public void AcceptOrRejectHashDependingOnExistenceInDataStore(bool addToDataStore, bool mustBeInDataStore) + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, out Token token, user); + DataContext dataContext = context.GetDataContext(token); + AssetImporter importer = new(dataContext.Logger, context.Time); + bool expectDataStoreFailure = !addToDataStore && mustBeInDataStore; + + ReadOnlySpan data = "LVLb"u8; + string hash = BitConverter.ToString(SHA1.HashData(data)).Replace("-", "").ToLower(); + + // add to store but not database, this way we can also test auto-import + if (addToDataStore) + { + dataContext.DataStore.WriteToStore(hash, data); + } + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new(hash, dataContext, importer) + { + MustBeInDataStoreIfHash = mustBeInDataStore, + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + + if (expectDataStoreFailure) + { + Assert.That(result.Status, Is.EqualTo(NotFound)); + Assert.That(result.AssetInfo, Is.Null); + Assert.That(result.NewKey, Is.EqualTo("0")); + Assert.That(newKeySetByCallback, Is.EqualTo("0")); + } + else if (addToDataStore) + { + Assert.That(result.Status, Is.EqualTo(OK)); + Assert.That(result.AssetInfo, Is.Not.Null); // ensure it was auto-imported + Assert.That(result.AssetInfo!.AssetHash, Is.EqualTo(hash)); + Assert.That(result.AssetInfo!.AssetType, Is.EqualTo(GameAssetType.Level)); + Assert.That(result.NewKey, Is.EqualTo(hash)); + Assert.That(newKeySetByCallback, Is.EqualTo(hash)); + } + else + { + Assert.That(result.Status, Is.EqualTo(OK)); + Assert.That(result.AssetInfo, Is.Null); // not auto-imported and also not in DB before + Assert.That(result.NewKey, Is.EqualTo(hash)); + Assert.That(newKeySetByCallback, Is.EqualTo(hash)); + } + + Assert.That(result.DisallowanceInfo, Is.Null); + Assert.That(result.ExistsInDataStore, Is.EqualTo(addToDataStore)); + } + + [Test] + public void RejectHashIfReadingFromDataStoreFails() + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, out Token token, user); + + // read-failing will unconditionally return true on key lookup, but will fail trying to read (null bytes/false return), which is what we want + DataContext dataContext = context.GetDataContext(token, new ReadFailingDataStore()); + AssetImporter importer = new(dataContext.Logger, context.Time); + + ReadOnlySpan data = "LVLb"u8; + string hash = BitConverter.ToString(SHA1.HashData(data)).Replace("-", "").ToLower(); + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new(hash, dataContext, importer) + { + MustBeInDataStoreIfHash = true, + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + + Assert.That(result.Status, Is.EqualTo(InternalServerError)); + Assert.That(result.AssetInfo, Is.Null); + Assert.That(result.DisallowanceInfo, Is.Null); + Assert.That(result.ExistsInDataStore, Is.True); + Assert.That(result.NewKey, Is.EqualTo("0")); + Assert.That(newKeySetByCallback, Is.EqualTo("0")); + } + + [Test] + public void RejectHashIfImportingFromDataStoreFails() + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, out Token token, user); + DataContext dataContext = context.GetDataContext(token); + AssetImporter importer = new(dataContext.Logger, context.Time); + + ReadOnlySpan data = "totallyalevel"u8; + // importing, for now, only really fails if the given hash doesn't match the actual hash + string fakeHash = BitConverter.ToString(SHA1.HashData("veryreallevel"u8)).Replace("-", "").ToLower(); + dataContext.DataStore.WriteToStore(fakeHash, data); + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new(fakeHash, dataContext, importer) + { + MustBeInDataStoreIfHash = true, + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + + Assert.That(result.Status, Is.EqualTo(BadRequest)); + Assert.That(result.AssetInfo, Is.Null); + Assert.That(result.DisallowanceInfo, Is.Null); + Assert.That(result.ExistsInDataStore, Is.True); + Assert.That(result.ErrorMessage, Is.Not.Null); + Assert.That(result.NewKey, Is.EqualTo("0")); + Assert.That(newKeySetByCallback, Is.EqualTo("0")); + } + + [Test] + public void RejectHashIfInvalidHash() + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, out Token token, user); + DataContext dataContext = context.GetDataContext(token); + AssetImporter importer = new(dataContext.Logger, context.Time); + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new("lololol", dataContext, importer) + { + MustBeInDataStoreIfHash = true, + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + + Assert.That(result.Status, Is.EqualTo(BadRequest)); + Assert.That(result.AssetInfo, Is.Null); + Assert.That(result.DisallowanceInfo, Is.Null); + Assert.That(result.ExistsInDataStore, Is.False); // Cancelled before actually wasting time looking it up + Assert.That(result.NewKey, Is.EqualTo("0")); + Assert.That(newKeySetByCallback, Is.EqualTo("0")); + } + + [Test] + public void RejectHashIfAssetDisallowed() + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, out Token token, user); + DataContext dataContext = context.GetDataContext(token); + AssetImporter importer = new(dataContext.Logger, context.Time); + + ReadOnlySpan data = "LVLb"u8; + string hash = BitConverter.ToString(SHA1.HashData(data)).Replace("-", "").ToLower(); + context.Database.DisallowAsset(hash, GameAssetType.Level, "youre evel was so shit that we had to ban it"); + + context.Database.Refresh(); + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new(hash, dataContext, importer) + { + MustBeInDataStoreIfHash = true, // Ensure disallowance is checked before the data store check + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + + Assert.That(result.Status, Is.EqualTo(Unauthorized)); + Assert.That(result.ErrorMessage, Is.Null); + Assert.That(result.AssetInfo, Is.Null); + Assert.That(result.DisallowanceInfo, Is.Not.Null); + Assert.That(result.NewKey, Is.EqualTo("0")); + Assert.That(newKeySetByCallback, Is.EqualTo("0")); + } + + [Test] + public void RejectHashIfHashesDisallowed() + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, out Token token, user); + DataContext dataContext = context.GetDataContext(token); + AssetImporter importer = new(dataContext.Logger, context.Time); + + ReadOnlySpan data = "LVLb"u8; + string hash = BitConverter.ToString(SHA1.HashData(data)).Replace("-", "").ToLower(); + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new(hash, dataContext, importer) + { + MayBeHash = false, + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + + Assert.That(result.Status, Is.EqualTo(BadRequest)); + Assert.That(result.ErrorMessage, Is.Not.Null); + Assert.That(result.AssetInfo, Is.Null); + Assert.That(result.DisallowanceInfo, Is.Null); + Assert.That(result.NewKey, Is.EqualTo("0")); + Assert.That(newKeySetByCallback, Is.EqualTo("0")); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void AcceptTextureHashIfOnlyTexturesAllowed(bool addToDataStore) + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, out Token token, user); + DataContext dataContext = context.GetDataContext(token); + AssetImporter importer = new(dataContext.Logger, context.Time); + + ReadOnlySpan data = "TEX "u8; + string hash = BitConverter.ToString(SHA1.HashData(data)).Replace("-", "").ToLower(); + + if (addToDataStore) dataContext.DataStore.WriteToStore(hash, data); + else context.Database.AddAssetToDatabase(new() // not adding to store: test getting from just DB instead + { + AssetHash = hash, + AssetType = GameAssetType.Texture, + }); + + context.Database.Refresh(); + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new(hash, dataContext, importer) + { + MustBeTexture = true, + MustBeInDataStoreIfHash = false, // not tested in this one + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + + Assert.That(result.Status, Is.EqualTo(OK)); + Assert.That(result.NewKey, Is.EqualTo(hash)); + Assert.That(newKeySetByCallback, Is.EqualTo(hash)); + Assert.That(result.AssetInfo, Is.Not.Null); + Assert.That(result.AssetInfo!.AssetHash, Is.EqualTo(hash)); + Assert.That(result.AssetInfo!.AssetType, Is.EqualTo(GameAssetType.Texture)); + Assert.That(result.DisallowanceInfo, Is.Null); + Assert.That(result.ExistsInDataStore, Is.EqualTo(addToDataStore)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void RejectNonTextureHashIfOnlyTexturesAllowed(bool addToDataStore) + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, out Token token, user); + DataContext dataContext = context.GetDataContext(token); + AssetImporter importer = new(dataContext.Logger, context.Time); + + ReadOnlySpan data = "LVLb"u8; + string hash = BitConverter.ToString(SHA1.HashData(data)).Replace("-", "").ToLower(); + + if (addToDataStore) dataContext.DataStore.WriteToStore(hash, data); + else context.Database.AddAssetToDatabase(new() // not adding to store: test getting from just DB instead + { + AssetHash = hash, + AssetType = GameAssetType.Level, + }); + + context.Database.Refresh(); + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new(hash, dataContext, importer) + { + MustBeTexture = true, + MustBeInDataStoreIfHash = false, // not tested in this one + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + + Assert.That(result.Status, Is.EqualTo(BadRequest)); + Assert.That(result.NewKey, Is.EqualTo("0")); + Assert.That(newKeySetByCallback, Is.EqualTo("0")); + Assert.That(result.AssetInfo, Is.Not.Null); + Assert.That(result.AssetInfo!.AssetHash, Is.EqualTo(hash)); + Assert.That(result.AssetInfo!.AssetType, Is.EqualTo(GameAssetType.Level)); + Assert.That(result.DisallowanceInfo, Is.Null); + Assert.That(result.ExistsInDataStore, Is.EqualTo(addToDataStore)); + } + + // TODO: test AIPI using a fake test server + + [Test] + public void AcceptsIfMustBeTextureButUnreadablePSPAsset() + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanetPSP, TokenPlatform.PSP, out Token token, user); + DataContext dataContext = context.GetDataContext(token); + AssetImporter importer = new(dataContext.Logger, context.Time); + + ReadOnlySpan data = "bbbbbbbbbbb"u8; + string hash = BitConverter.ToString(SHA1.HashData(data)).Replace("-", "").ToLower(); + dataContext.DataStore.WriteToStore($"psp/{hash}", data); + + context.Database.Refresh(); + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new(hash, dataContext, importer) + { + MustBeTexture = true, + MustBeInDataStoreIfHash = true, + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + + Assert.That(result.Status, Is.EqualTo(OK)); + Assert.That(result.NewKey, Is.EqualTo(hash)); + Assert.That(newKeySetByCallback, Is.EqualTo(hash)); + Assert.That(result.AssetInfo, Is.Not.Null); + Assert.That(result.AssetInfo!.AssetHash, Is.EqualTo(hash)); + Assert.That(result.AssetInfo!.AssetType, Is.EqualTo(GameAssetType.Unknown)); + Assert.That(result.AssetInfo!.IsPSP, Is.True); + Assert.That(result.DisallowanceInfo, Is.Null); + Assert.That(result.ExistsInDataStore, Is.True); + } + + [Test] + public void RejectsIfMustBeTextureAndUnreadableNonPSPAsset() + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, out Token token, user); + DataContext dataContext = context.GetDataContext(token); + AssetImporter importer = new(dataContext.Logger, context.Time); + + ReadOnlySpan data = "bbbbbbbbbbb"u8; + string hash = BitConverter.ToString(SHA1.HashData(data)).Replace("-", "").ToLower(); + dataContext.DataStore.WriteToStore(hash, data); + + context.Database.Refresh(); + + string newKeySetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.Validate(new(hash, dataContext, importer) + { + MustBeTexture = true, + MustBeInDataStoreIfHash = true, + OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + }, dataContext.Logger); + + Assert.That(result.Status, Is.EqualTo(BadRequest)); + Assert.That(result.NewKey, Is.EqualTo("0")); + Assert.That(newKeySetByCallback, Is.EqualTo("0")); + Assert.That(result.AssetInfo, Is.Not.Null); + Assert.That(result.AssetInfo!.AssetHash, Is.EqualTo(hash)); + Assert.That(result.AssetInfo!.AssetType, Is.EqualTo(GameAssetType.Unknown)); + Assert.That(result.AssetInfo!.IsPSP, Is.False); + Assert.That(result.DisallowanceInfo, Is.Null); + Assert.That(result.ExistsInDataStore, Is.True); + } +} \ No newline at end of file From 146ca073c3f3280b4297c8364603d1ab7ccac93c Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Fri, 29 May 2026 18:15:47 +0200 Subject: [PATCH 6/6] Improve comments and naming --- .../Helpers/ResourceValidationHelper.cs | 52 ++--- .../Validation/AssetValidationParameters.cs | 10 +- .../Assets/Validation/ValidatedAssetResult.cs | 12 +- .../Assets/AssetReferenceValidationTests.cs | 178 +++++++++--------- 4 files changed, 128 insertions(+), 124 deletions(-) diff --git a/Refresh.Core/Helpers/ResourceValidationHelper.cs b/Refresh.Core/Helpers/ResourceValidationHelper.cs index ba7be246..2efb5b6e 100644 --- a/Refresh.Core/Helpers/ResourceValidationHelper.cs +++ b/Refresh.Core/Helpers/ResourceValidationHelper.cs @@ -10,56 +10,60 @@ namespace Refresh.Core.Helpers; public abstract class ResourceValidationHelper { - public static ValidatedAssetResult Validate(AssetValidationParameters parameters, Logger logger) + /// + /// Validates the given asset reference (hash/guid/blank) using the given parameters, if necessary also by reading the referenced asset + /// or getting data about it from database (see and ). + /// + public static ValidatedAssetResult ValidateReference(AssetValidationParameters parameters, Logger logger) { string assetTypeStr = parameters.AssetContextTypeStr ?? (parameters.MustBeTexture ? "image asset" : "asset"); GameAsset? asset = null; bool existsInDataStore = false; bool isPSP = parameters.GameToUseIn == TokenGame.LittleBigPlanetPSP; - Action? onNewAssetKeyCallback = parameters.OnNewAssetKeyCallback; + Action? onNewAssetRefCallback = parameters.OnNewAssetRefCallback; - if (parameters.AssetKey.IsBlankHash()) + if (parameters.AssetRef.IsBlankHash()) { - if (!parameters.MayBeBlank) return new(BadRequest, "0", $"The {assetTypeStr} must be set.", onNewAssetKeyCallback); - else return new(OK, "0", null, onNewAssetKeyCallback); + if (!parameters.MayBeBlank) return new(BadRequest, "0", $"The {assetTypeStr} must be set.", onNewAssetRefCallback); + else return new(OK, "0", null, onNewAssetRefCallback); } - else if (parameters.AssetKey.StartsWith('g')) + else if (parameters.AssetRef.StartsWith('g')) { - if (!parameters.MayBeGuid) return new(BadRequest, null, $"The {assetTypeStr} may not be an in-game asset.", onNewAssetKeyCallback); - if (parameters.AssetKey.Length < 2) return new(BadRequest, null, $"The used in-game {assetTypeStr} is invalid (empty GUID).", onNewAssetKeyCallback); + if (!parameters.MayBeGuid) return new(BadRequest, null, $"The {assetTypeStr} may not be an in-game asset.", onNewAssetRefCallback); + if (parameters.AssetRef.Length < 2) return new(BadRequest, null, $"The used in-game {assetTypeStr} is invalid (empty GUID).", onNewAssetRefCallback); // This should only happen if the user is messing with mods/the API/beta builds, so give them a more detailed response - bool canParseGuid = long.TryParse(parameters.AssetKey[1..], out long guid); + bool canParseGuid = long.TryParse(parameters.AssetRef[1..], out long guid); if (!canParseGuid) - return new(BadRequest, null, $"The used in-game {assetTypeStr} is invalid (badly formatted GUID).", onNewAssetKeyCallback); + return new(BadRequest, null, $"The used in-game {assetTypeStr} is invalid (badly formatted GUID).", onNewAssetRefCallback); if (parameters.MustBeTexture && !parameters.GuidChecker.IsTextureGuid(parameters.GameToUseIn, guid)) - return new(BadRequest, null, $"The used in-game {assetTypeStr} was not a valid image (unknown GUID).", onNewAssetKeyCallback); + return new(BadRequest, null, $"The used in-game {assetTypeStr} was not a valid image (unknown GUID).", onNewAssetRefCallback); } // At this point the reference is a hash else if (!parameters.MayBeHash) { - return new(BadRequest, null, $"The {assetTypeStr} may not be a custom asset.", onNewAssetKeyCallback); + return new(BadRequest, null, $"The {assetTypeStr} may not be a custom asset.", onNewAssetRefCallback); } - else if (!CommonPatterns.Sha1Regex().IsMatch(parameters.AssetKey)) + else if (!CommonPatterns.Sha1Regex().IsMatch(parameters.AssetRef)) { // This should only happen if a player is messing with mods/the API, so give them a more detailed response - return new(BadRequest, null, $"The used {assetTypeStr} had an invalid hash.", onNewAssetKeyCallback); + return new(BadRequest, null, $"The used {assetTypeStr} had an invalid hash.", onNewAssetRefCallback); } else { - DisallowedAsset? disallowed = parameters.Database.GetDisallowedAssetInfo(parameters.AssetKey); + DisallowedAsset? disallowed = parameters.Database.GetDisallowedAssetInfo(parameters.AssetRef); if (disallowed != null) { logger.LogWarning(BunkumCategory.UserContent, $"{parameters.User} tried to use a manually disallowed {assetTypeStr}."); - return new(Unauthorized, disallowanceInfo: disallowed, onNewAssetKeyCallback: onNewAssetKeyCallback); + return new(Unauthorized, disallowanceInfo: disallowed, onNewAssetRefCallback: onNewAssetRefCallback); } - string filename = isPSP ? $"psp/{parameters.AssetKey}" : parameters.AssetKey; + string filename = isPSP ? $"psp/{parameters.AssetRef}" : parameters.AssetRef; existsInDataStore = parameters.DataStore.ExistsInStore(filename); if (!existsInDataStore) @@ -67,10 +71,10 @@ public static ValidatedAssetResult Validate(AssetValidationParameters parameters logger.LogDebug(BunkumCategory.UserContent, $"Referenced asset '{filename}' could not be found in data store."); if (parameters.MustBeInDataStoreIfHash) - return new(NotFound, null, $"The used {assetTypeStr} did not exist on the server.", onNewAssetKeyCallback); + return new(NotFound, null, $"The used {assetTypeStr} did not exist on the server.", onNewAssetRefCallback); } - asset = parameters.Cache.GetAssetInfo(parameters.AssetKey, parameters.Database); + asset = parameters.Cache.GetAssetInfo(parameters.AssetRef, parameters.Database); // Only try to import if the asset exists in the data store if (existsInDataStore && asset == null) @@ -84,15 +88,15 @@ public static ValidatedAssetResult Validate(AssetValidationParameters parameters sw.Stop(); logger.LogError(BunkumCategory.UserContent, $"Failed to read '{filename}' from data store!"); logger.LogDebug(BunkumCategory.UserContent, $"Failed to get '{filename}' after {sw.ElapsedMilliseconds}ms."); - return new(InternalServerError, null, $"Failed to read {assetTypeStr} internally. Please report this to the server owner.", onNewAssetKeyCallback, existsInDataStore: existsInDataStore); + return new(InternalServerError, null, $"Failed to read {assetTypeStr} internally. Please report this to the server owner.", onNewAssetRefCallback, existsInDataStore: existsInDataStore); } - asset = parameters.AssetImporter.ReadAndVerifyAsset(parameters.AssetKey, assetData, parameters.PlatformToUseIn, parameters.Database); + asset = parameters.AssetImporter.ReadAndVerifyAsset(parameters.AssetRef, assetData, parameters.PlatformToUseIn, parameters.Database); if (asset == null) { sw.Stop(); logger.LogDebug(BunkumCategory.UserContent, $"Failed to get '{filename}' after {sw.ElapsedMilliseconds}ms."); - return new(BadRequest, null, $"The used {assetTypeStr} was invalid or corrupt.", onNewAssetKeyCallback, existsInDataStore: existsInDataStore); + return new(BadRequest, null, $"The used {assetTypeStr} was invalid or corrupt.", onNewAssetRefCallback, existsInDataStore: existsInDataStore); } sw.Stop(); @@ -105,12 +109,12 @@ public static ValidatedAssetResult Validate(AssetValidationParameters parameters bool isHashedTexture = (asset.AssetFlags & AssetFlags.Imagery) != 0; if (parameters.MustBeTexture && !isHashedTexture) - return new(BadRequest, null, $"The used {assetTypeStr} was not a valid custom image.", onNewAssetKeyCallback, assetInfo: asset, existsInDataStore: existsInDataStore); + return new(BadRequest, null, $"The used {assetTypeStr} was not a valid custom image.", onNewAssetRefCallback, assetInfo: asset, existsInDataStore: existsInDataStore); // TODO: actually use AIPI to scan image if not null } } - return new(OK, parameters.AssetKey, null, onNewAssetKeyCallback, assetInfo: asset, existsInDataStore: existsInDataStore); + return new(OK, parameters.AssetRef, null, onNewAssetRefCallback, assetInfo: asset, existsInDataStore: existsInDataStore); } } \ No newline at end of file diff --git a/Refresh.Core/Types/Assets/Validation/AssetValidationParameters.cs b/Refresh.Core/Types/Assets/Validation/AssetValidationParameters.cs index e61398d1..b83f4cae 100644 --- a/Refresh.Core/Types/Assets/Validation/AssetValidationParameters.cs +++ b/Refresh.Core/Types/Assets/Validation/AssetValidationParameters.cs @@ -11,9 +11,9 @@ namespace Refresh.Core.Types.Assets.Validation; public struct AssetValidationParameters { /// - /// The hash/guid/blank to validate + /// The reference (hash/guid/blank) to validate /// - public string AssetKey { get; set; } = "0"; + public string AssetRef { get; set; } = "0"; public GameUser? User { get; set; } public TokenGame GameToUseIn { get; set; } public TokenPlatform PlatformToUseIn { get; set; } @@ -37,16 +37,16 @@ public struct AssetValidationParameters public string? AssetContextTypeStr { get; set; } /// - /// Callback which is called with the new asset reference/key as parameter when constructing ; + /// Callback which is called with the new asset reference as parameter when constructing ; /// useful to update asset references of entities during validation without requiring the caller to manually reassign them after validation; /// this way similar attributes like photo images or face icons can simply be iterated. /// If null, this will be skipped. /// - public Action? OnNewAssetKeyCallback { get; set; } + public Action? OnNewAssetRefCallback { get; set; } public AssetValidationParameters(string assetKey, DataContext dataContext, AssetImporter assetImporter, AipiService? aipi = null) { - this.AssetKey = assetKey; + this.AssetRef = assetKey; this.User = dataContext.User; this.GameToUseIn = dataContext.Game; this.PlatformToUseIn = dataContext.Platform; diff --git a/Refresh.Core/Types/Assets/Validation/ValidatedAssetResult.cs b/Refresh.Core/Types/Assets/Validation/ValidatedAssetResult.cs index 32a9ffe5..a44eb9c6 100644 --- a/Refresh.Core/Types/Assets/Validation/ValidatedAssetResult.cs +++ b/Refresh.Core/Types/Assets/Validation/ValidatedAssetResult.cs @@ -13,9 +13,9 @@ public struct ValidatedAssetResult public HttpStatusCode Status { get; set; } /// - /// new hash/guid/blank to use + /// new reference (hash/guid/blank) to use /// - public string NewKey { get; set; } + public string NewAssetRef { get; set; } /// /// message to show to the user. @@ -27,17 +27,17 @@ public struct ValidatedAssetResult public DisallowedAsset? DisallowanceInfo { get; set; } public bool ExistsInDataStore { get; set; } - public ValidatedAssetResult(HttpStatusCode status, string? newKey = null, string? errorMessage = null, Action? onNewAssetKeyCallback = null, + public ValidatedAssetResult(HttpStatusCode status, string? newAssetRef = null, string? errorMessage = null, Action? onNewAssetRefCallback = null, GameAsset? assetInfo = null, DisallowedAsset? disallowanceInfo = null, bool existsInDataStore = false) { this.Status = status; - this.NewKey = newKey ?? "0"; + this.NewAssetRef = newAssetRef ?? "0"; this.ErrorMessage = errorMessage; this.AssetInfo = assetInfo; this.DisallowanceInfo = disallowanceInfo; this.ExistsInDataStore = existsInDataStore; - if (onNewAssetKeyCallback != null) - onNewAssetKeyCallback(this.NewKey); + if (onNewAssetRefCallback != null) + onNewAssetRefCallback(this.NewAssetRef); } } \ No newline at end of file diff --git a/RefreshTests.GameServer/Tests/Assets/AssetReferenceValidationTests.cs b/RefreshTests.GameServer/Tests/Assets/AssetReferenceValidationTests.cs index 6c41beeb..9d986af3 100644 --- a/RefreshTests.GameServer/Tests/Assets/AssetReferenceValidationTests.cs +++ b/RefreshTests.GameServer/Tests/Assets/AssetReferenceValidationTests.cs @@ -27,14 +27,14 @@ public void AcceptBlankHash(string blankHashVariation) DataContext dataContext = context.GetDataContext(token); AssetImporter importer = new(dataContext.Logger, context.Time); - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new(blankHashVariation, dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(blankHashVariation, dataContext, importer) { - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); Assert.That(result.Status, Is.EqualTo(OK)); - Assert.That(result.NewKey, Is.EqualTo("0")); - Assert.That(newKeySetByCallback, Is.EqualTo("0")); + Assert.That(result.NewAssetRef, Is.EqualTo("0")); + Assert.That(newRefSetByCallback, Is.EqualTo("0")); Assert.That(result.AssetInfo, Is.Null); } @@ -51,16 +51,16 @@ public void RejectBlankHashIfDisallowed(string blankHashVariation) DataContext dataContext = context.GetDataContext(token); AssetImporter importer = new(dataContext.Logger, context.Time); - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new(blankHashVariation, dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(blankHashVariation, dataContext, importer) { MayBeBlank = false, - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); Assert.That(result.Status, Is.EqualTo(BadRequest)); Assert.That(result.ErrorMessage, Is.Not.Null); - Assert.That(result.NewKey, Is.EqualTo("0")); - Assert.That(newKeySetByCallback, Is.EqualTo("0")); + Assert.That(result.NewAssetRef, Is.EqualTo("0")); + Assert.That(newRefSetByCallback, Is.EqualTo("0")); } [Test] @@ -74,14 +74,14 @@ public void AcceptGuidsIfNotRestricted(string guid) DataContext dataContext = context.GetDataContext(token); AssetImporter importer = new(dataContext.Logger, context.Time); - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new(guid, dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(guid, dataContext, importer) { - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); Assert.That(result.Status, Is.EqualTo(OK)); - Assert.That(result.NewKey, Is.EqualTo(guid)); - Assert.That(newKeySetByCallback, Is.EqualTo(guid)); + Assert.That(result.NewAssetRef, Is.EqualTo(guid)); + Assert.That(newRefSetByCallback, Is.EqualTo(guid)); Assert.That(result.ErrorMessage, Is.Null); } @@ -96,16 +96,16 @@ public void RejectGuidIfDisallowed(string guid) DataContext dataContext = context.GetDataContext(token); AssetImporter importer = new(dataContext.Logger, context.Time); - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new(guid, dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(guid, dataContext, importer) { MayBeGuid = false, - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); Assert.That(result.Status, Is.EqualTo(BadRequest)); Assert.That(result.ErrorMessage, Is.Not.Null); - Assert.That(result.NewKey, Is.EqualTo("0")); - Assert.That(newKeySetByCallback, Is.EqualTo("0")); + Assert.That(result.NewAssetRef, Is.EqualTo("0")); + Assert.That(newRefSetByCallback, Is.EqualTo("0")); } [Test] @@ -120,15 +120,15 @@ public void RejectGuidIfBadlyFormatted(string guid) DataContext dataContext = context.GetDataContext(token); AssetImporter importer = new(dataContext.Logger, context.Time); - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new(guid, dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(guid, dataContext, importer) { - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); Assert.That(result.Status, Is.EqualTo(BadRequest)); Assert.That(result.ErrorMessage, Is.Not.Null); - Assert.That(result.NewKey, Is.EqualTo("0")); - Assert.That(newKeySetByCallback, Is.EqualTo("0")); + Assert.That(result.NewAssetRef, Is.EqualTo("0")); + Assert.That(newRefSetByCallback, Is.EqualTo("0")); } [Test] @@ -141,16 +141,16 @@ public void RejectNonTextureGuidIfOnlyTexturesAllowed() DataContext dataContext = context.GetDataContext(token); AssetImporter importer = new(dataContext.Logger, context.Time); - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new(guid, dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(guid, dataContext, importer) { MustBeTexture = true, - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); Assert.That(result.Status, Is.EqualTo(BadRequest)); Assert.That(result.ErrorMessage, Is.Not.Null); - Assert.That(result.NewKey, Is.EqualTo("0")); - Assert.That(newKeySetByCallback, Is.EqualTo("0")); + Assert.That(result.NewAssetRef, Is.EqualTo("0")); + Assert.That(newRefSetByCallback, Is.EqualTo("0")); } [Test] @@ -163,16 +163,16 @@ public void AcceptTextureGuidIfOnlyTexturesAllowed() DataContext dataContext = context.GetDataContext(token); AssetImporter importer = new(dataContext.Logger, context.Time); - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new(guid, dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(guid, dataContext, importer) { MustBeTexture = true, - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); Assert.That(result.Status, Is.EqualTo(OK)); Assert.That(result.AssetInfo, Is.Null); - Assert.That(result.NewKey, Is.EqualTo(guid)); - Assert.That(newKeySetByCallback, Is.EqualTo(guid)); + Assert.That(result.NewAssetRef, Is.EqualTo(guid)); + Assert.That(newRefSetByCallback, Is.EqualTo(guid)); } [Test] @@ -198,19 +198,19 @@ public void AcceptOrRejectHashDependingOnExistenceInDataStore(bool addToDataStor dataContext.DataStore.WriteToStore(hash, data); } - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new(hash, dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(hash, dataContext, importer) { MustBeInDataStoreIfHash = mustBeInDataStore, - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); if (expectDataStoreFailure) { Assert.That(result.Status, Is.EqualTo(NotFound)); Assert.That(result.AssetInfo, Is.Null); - Assert.That(result.NewKey, Is.EqualTo("0")); - Assert.That(newKeySetByCallback, Is.EqualTo("0")); + Assert.That(result.NewAssetRef, Is.EqualTo("0")); + Assert.That(newRefSetByCallback, Is.EqualTo("0")); } else if (addToDataStore) { @@ -218,15 +218,15 @@ public void AcceptOrRejectHashDependingOnExistenceInDataStore(bool addToDataStor Assert.That(result.AssetInfo, Is.Not.Null); // ensure it was auto-imported Assert.That(result.AssetInfo!.AssetHash, Is.EqualTo(hash)); Assert.That(result.AssetInfo!.AssetType, Is.EqualTo(GameAssetType.Level)); - Assert.That(result.NewKey, Is.EqualTo(hash)); - Assert.That(newKeySetByCallback, Is.EqualTo(hash)); + Assert.That(result.NewAssetRef, Is.EqualTo(hash)); + Assert.That(newRefSetByCallback, Is.EqualTo(hash)); } else { Assert.That(result.Status, Is.EqualTo(OK)); Assert.That(result.AssetInfo, Is.Null); // not auto-imported and also not in DB before - Assert.That(result.NewKey, Is.EqualTo(hash)); - Assert.That(newKeySetByCallback, Is.EqualTo(hash)); + Assert.That(result.NewAssetRef, Is.EqualTo(hash)); + Assert.That(newRefSetByCallback, Is.EqualTo(hash)); } Assert.That(result.DisallowanceInfo, Is.Null); @@ -247,19 +247,19 @@ public void RejectHashIfReadingFromDataStoreFails() ReadOnlySpan data = "LVLb"u8; string hash = BitConverter.ToString(SHA1.HashData(data)).Replace("-", "").ToLower(); - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new(hash, dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(hash, dataContext, importer) { MustBeInDataStoreIfHash = true, - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); Assert.That(result.Status, Is.EqualTo(InternalServerError)); Assert.That(result.AssetInfo, Is.Null); Assert.That(result.DisallowanceInfo, Is.Null); Assert.That(result.ExistsInDataStore, Is.True); - Assert.That(result.NewKey, Is.EqualTo("0")); - Assert.That(newKeySetByCallback, Is.EqualTo("0")); + Assert.That(result.NewAssetRef, Is.EqualTo("0")); + Assert.That(newRefSetByCallback, Is.EqualTo("0")); } [Test] @@ -276,11 +276,11 @@ public void RejectHashIfImportingFromDataStoreFails() string fakeHash = BitConverter.ToString(SHA1.HashData("veryreallevel"u8)).Replace("-", "").ToLower(); dataContext.DataStore.WriteToStore(fakeHash, data); - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new(fakeHash, dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(fakeHash, dataContext, importer) { MustBeInDataStoreIfHash = true, - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); Assert.That(result.Status, Is.EqualTo(BadRequest)); @@ -288,8 +288,8 @@ public void RejectHashIfImportingFromDataStoreFails() Assert.That(result.DisallowanceInfo, Is.Null); Assert.That(result.ExistsInDataStore, Is.True); Assert.That(result.ErrorMessage, Is.Not.Null); - Assert.That(result.NewKey, Is.EqualTo("0")); - Assert.That(newKeySetByCallback, Is.EqualTo("0")); + Assert.That(result.NewAssetRef, Is.EqualTo("0")); + Assert.That(newRefSetByCallback, Is.EqualTo("0")); } [Test] @@ -301,19 +301,19 @@ public void RejectHashIfInvalidHash() DataContext dataContext = context.GetDataContext(token); AssetImporter importer = new(dataContext.Logger, context.Time); - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new("lololol", dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new("lololol", dataContext, importer) { MustBeInDataStoreIfHash = true, - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); Assert.That(result.Status, Is.EqualTo(BadRequest)); Assert.That(result.AssetInfo, Is.Null); Assert.That(result.DisallowanceInfo, Is.Null); Assert.That(result.ExistsInDataStore, Is.False); // Cancelled before actually wasting time looking it up - Assert.That(result.NewKey, Is.EqualTo("0")); - Assert.That(newKeySetByCallback, Is.EqualTo("0")); + Assert.That(result.NewAssetRef, Is.EqualTo("0")); + Assert.That(newRefSetByCallback, Is.EqualTo("0")); } [Test] @@ -331,19 +331,19 @@ public void RejectHashIfAssetDisallowed() context.Database.Refresh(); - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new(hash, dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(hash, dataContext, importer) { MustBeInDataStoreIfHash = true, // Ensure disallowance is checked before the data store check - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); Assert.That(result.Status, Is.EqualTo(Unauthorized)); Assert.That(result.ErrorMessage, Is.Null); Assert.That(result.AssetInfo, Is.Null); Assert.That(result.DisallowanceInfo, Is.Not.Null); - Assert.That(result.NewKey, Is.EqualTo("0")); - Assert.That(newKeySetByCallback, Is.EqualTo("0")); + Assert.That(result.NewAssetRef, Is.EqualTo("0")); + Assert.That(newRefSetByCallback, Is.EqualTo("0")); } [Test] @@ -358,19 +358,19 @@ public void RejectHashIfHashesDisallowed() ReadOnlySpan data = "LVLb"u8; string hash = BitConverter.ToString(SHA1.HashData(data)).Replace("-", "").ToLower(); - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new(hash, dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(hash, dataContext, importer) { MayBeHash = false, - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); Assert.That(result.Status, Is.EqualTo(BadRequest)); Assert.That(result.ErrorMessage, Is.Not.Null); Assert.That(result.AssetInfo, Is.Null); Assert.That(result.DisallowanceInfo, Is.Null); - Assert.That(result.NewKey, Is.EqualTo("0")); - Assert.That(newKeySetByCallback, Is.EqualTo("0")); + Assert.That(result.NewAssetRef, Is.EqualTo("0")); + Assert.That(newRefSetByCallback, Is.EqualTo("0")); } [Test] @@ -396,17 +396,17 @@ public void AcceptTextureHashIfOnlyTexturesAllowed(bool addToDataStore) context.Database.Refresh(); - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new(hash, dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(hash, dataContext, importer) { MustBeTexture = true, MustBeInDataStoreIfHash = false, // not tested in this one - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); Assert.That(result.Status, Is.EqualTo(OK)); - Assert.That(result.NewKey, Is.EqualTo(hash)); - Assert.That(newKeySetByCallback, Is.EqualTo(hash)); + Assert.That(result.NewAssetRef, Is.EqualTo(hash)); + Assert.That(newRefSetByCallback, Is.EqualTo(hash)); Assert.That(result.AssetInfo, Is.Not.Null); Assert.That(result.AssetInfo!.AssetHash, Is.EqualTo(hash)); Assert.That(result.AssetInfo!.AssetType, Is.EqualTo(GameAssetType.Texture)); @@ -437,17 +437,17 @@ public void RejectNonTextureHashIfOnlyTexturesAllowed(bool addToDataStore) context.Database.Refresh(); - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new(hash, dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(hash, dataContext, importer) { MustBeTexture = true, MustBeInDataStoreIfHash = false, // not tested in this one - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); Assert.That(result.Status, Is.EqualTo(BadRequest)); - Assert.That(result.NewKey, Is.EqualTo("0")); - Assert.That(newKeySetByCallback, Is.EqualTo("0")); + Assert.That(result.NewAssetRef, Is.EqualTo("0")); + Assert.That(newRefSetByCallback, Is.EqualTo("0")); Assert.That(result.AssetInfo, Is.Not.Null); Assert.That(result.AssetInfo!.AssetHash, Is.EqualTo(hash)); Assert.That(result.AssetInfo!.AssetType, Is.EqualTo(GameAssetType.Level)); @@ -472,17 +472,17 @@ public void AcceptsIfMustBeTextureButUnreadablePSPAsset() context.Database.Refresh(); - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new(hash, dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(hash, dataContext, importer) { MustBeTexture = true, MustBeInDataStoreIfHash = true, - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); Assert.That(result.Status, Is.EqualTo(OK)); - Assert.That(result.NewKey, Is.EqualTo(hash)); - Assert.That(newKeySetByCallback, Is.EqualTo(hash)); + Assert.That(result.NewAssetRef, Is.EqualTo(hash)); + Assert.That(newRefSetByCallback, Is.EqualTo(hash)); Assert.That(result.AssetInfo, Is.Not.Null); Assert.That(result.AssetInfo!.AssetHash, Is.EqualTo(hash)); Assert.That(result.AssetInfo!.AssetType, Is.EqualTo(GameAssetType.Unknown)); @@ -506,17 +506,17 @@ public void RejectsIfMustBeTextureAndUnreadableNonPSPAsset() context.Database.Refresh(); - string newKeySetByCallback = "unset lol"; - ValidatedAssetResult result = ResourceValidationHelper.Validate(new(hash, dataContext, importer) + string newRefSetByCallback = "unset lol"; + ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(hash, dataContext, importer) { MustBeTexture = true, MustBeInDataStoreIfHash = true, - OnNewAssetKeyCallback = delegate(string newKey) { newKeySetByCallback = newKey; }, + OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; }, }, dataContext.Logger); Assert.That(result.Status, Is.EqualTo(BadRequest)); - Assert.That(result.NewKey, Is.EqualTo("0")); - Assert.That(newKeySetByCallback, Is.EqualTo("0")); + Assert.That(result.NewAssetRef, Is.EqualTo("0")); + Assert.That(newRefSetByCallback, Is.EqualTo("0")); Assert.That(result.AssetInfo, Is.Not.Null); Assert.That(result.AssetInfo!.AssetHash, Is.EqualTo(hash)); Assert.That(result.AssetInfo!.AssetType, Is.EqualTo(GameAssetType.Unknown));