diff --git a/Refresh.Core/Helpers/ResourceValidationHelper.cs b/Refresh.Core/Helpers/ResourceValidationHelper.cs
new file mode 100644
index 00000000..2efb5b6e
--- /dev/null
+++ b/Refresh.Core/Helpers/ResourceValidationHelper.cs
@@ -0,0 +1,120 @@
+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
+{
+ ///
+ /// 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? onNewAssetRefCallback = parameters.OnNewAssetRefCallback;
+
+ if (parameters.AssetRef.IsBlankHash())
+ {
+ if (!parameters.MayBeBlank) return new(BadRequest, "0", $"The {assetTypeStr} must be set.", onNewAssetRefCallback);
+ else return new(OK, "0", null, onNewAssetRefCallback);
+ }
+
+ else if (parameters.AssetRef.StartsWith('g'))
+ {
+ 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.AssetRef[1..], out long guid);
+ if (!canParseGuid)
+ 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).", 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.", onNewAssetRefCallback);
+ }
+
+ 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.", onNewAssetRefCallback);
+ }
+
+ else
+ {
+ 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, onNewAssetRefCallback: onNewAssetRefCallback);
+ }
+
+ string filename = isPSP ? $"psp/{parameters.AssetRef}" : parameters.AssetRef;
+ 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.", onNewAssetRefCallback);
+ }
+
+ asset = parameters.Cache.GetAssetInfo(parameters.AssetRef, 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.", onNewAssetRefCallback, existsInDataStore: existsInDataStore);
+ }
+
+ 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.", onNewAssetRefCallback, 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.", onNewAssetRefCallback, assetInfo: asset, existsInDataStore: existsInDataStore);
+
+ // TODO: actually use AIPI to scan image if not null
+ }
+ }
+
+ return new(OK, parameters.AssetRef, null, onNewAssetRefCallback, assetInfo: asset, existsInDataStore: existsInDataStore);
+ }
+}
\ No newline at end of file
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)
diff --git a/Refresh.Core/Types/Assets/Validation/AssetValidationParameters.cs b/Refresh.Core/Types/Assets/Validation/AssetValidationParameters.cs
new file mode 100644
index 00000000..b83f4cae
--- /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 reference (hash/guid/blank) to validate
+ ///
+ public string AssetRef { 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 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? OnNewAssetRefCallback { get; set; }
+
+ public AssetValidationParameters(string assetKey, DataContext dataContext, AssetImporter assetImporter, AipiService? aipi = null)
+ {
+ this.AssetRef = 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..a44eb9c6
--- /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 reference (hash/guid/blank) to use
+ ///
+ public string NewAssetRef { 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? newAssetRef = null, string? errorMessage = null, Action? onNewAssetRefCallback = null,
+ GameAsset? assetInfo = null, DisallowedAsset? disallowanceInfo = null, bool existsInDataStore = false)
+ {
+ this.Status = status;
+ this.NewAssetRef = newAssetRef ?? "0";
+ this.ErrorMessage = errorMessage;
+ this.AssetInfo = assetInfo;
+ this.DisallowanceInfo = disallowanceInfo;
+ this.ExistsInDataStore = existsInDataStore;
+
+ if (onNewAssetRefCallback != null)
+ onNewAssetRefCallback(this.NewAssetRef);
+ }
+}
\ No newline at end of file
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
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,
diff --git a/RefreshTests.GameServer/Tests/Assets/AssetReferenceValidationTests.cs b/RefreshTests.GameServer/Tests/Assets/AssetReferenceValidationTests.cs
new file mode 100644
index 00000000..9d986af3
--- /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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(blankHashVariation, dataContext, importer)
+ {
+ OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; },
+ }, dataContext.Logger);
+ Assert.That(result.Status, Is.EqualTo(OK));
+ Assert.That(result.NewAssetRef, Is.EqualTo("0"));
+ Assert.That(newRefSetByCallback, 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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(blankHashVariation, dataContext, importer)
+ {
+ MayBeBlank = false,
+ 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.NewAssetRef, Is.EqualTo("0"));
+ Assert.That(newRefSetByCallback, 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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(guid, dataContext, importer)
+ {
+ OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; },
+ }, dataContext.Logger);
+ Assert.That(result.Status, Is.EqualTo(OK));
+ Assert.That(result.NewAssetRef, Is.EqualTo(guid));
+ Assert.That(newRefSetByCallback, 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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(guid, dataContext, importer)
+ {
+ MayBeGuid = false,
+ 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.NewAssetRef, Is.EqualTo("0"));
+ Assert.That(newRefSetByCallback, 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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(guid, dataContext, importer)
+ {
+ 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.NewAssetRef, Is.EqualTo("0"));
+ Assert.That(newRefSetByCallback, 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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(guid, dataContext, importer)
+ {
+ MustBeTexture = true,
+ 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.NewAssetRef, Is.EqualTo("0"));
+ Assert.That(newRefSetByCallback, 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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(guid, dataContext, importer)
+ {
+ MustBeTexture = true,
+ OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; },
+ }, dataContext.Logger);
+ Assert.That(result.Status, Is.EqualTo(OK));
+ Assert.That(result.AssetInfo, Is.Null);
+ Assert.That(result.NewAssetRef, Is.EqualTo(guid));
+ Assert.That(newRefSetByCallback, 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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(hash, dataContext, importer)
+ {
+ MustBeInDataStoreIfHash = mustBeInDataStore,
+ 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.NewAssetRef, Is.EqualTo("0"));
+ Assert.That(newRefSetByCallback, 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.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.NewAssetRef, Is.EqualTo(hash));
+ Assert.That(newRefSetByCallback, 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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(hash, dataContext, importer)
+ {
+ MustBeInDataStoreIfHash = true,
+ 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.NewAssetRef, Is.EqualTo("0"));
+ Assert.That(newRefSetByCallback, 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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(fakeHash, dataContext, importer)
+ {
+ MustBeInDataStoreIfHash = true,
+ 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.True);
+ Assert.That(result.ErrorMessage, Is.Not.Null);
+ Assert.That(result.NewAssetRef, Is.EqualTo("0"));
+ Assert.That(newRefSetByCallback, 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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new("lololol", dataContext, importer)
+ {
+ MustBeInDataStoreIfHash = true,
+ 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.NewAssetRef, Is.EqualTo("0"));
+ Assert.That(newRefSetByCallback, 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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(hash, dataContext, importer)
+ {
+ MustBeInDataStoreIfHash = true, // Ensure disallowance is checked before the data store check
+ 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.NewAssetRef, Is.EqualTo("0"));
+ Assert.That(newRefSetByCallback, 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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(hash, dataContext, importer)
+ {
+ MayBeHash = false,
+ 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.NewAssetRef, Is.EqualTo("0"));
+ Assert.That(newRefSetByCallback, 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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(hash, dataContext, importer)
+ {
+ MustBeTexture = true,
+ MustBeInDataStoreIfHash = false, // not tested in this one
+ OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; },
+ }, dataContext.Logger);
+
+ Assert.That(result.Status, Is.EqualTo(OK));
+ 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));
+ 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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(hash, dataContext, importer)
+ {
+ MustBeTexture = true,
+ MustBeInDataStoreIfHash = false, // not tested in this one
+ OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; },
+ }, dataContext.Logger);
+
+ Assert.That(result.Status, Is.EqualTo(BadRequest));
+ 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));
+ 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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(hash, dataContext, importer)
+ {
+ MustBeTexture = true,
+ MustBeInDataStoreIfHash = true,
+ OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; },
+ }, dataContext.Logger);
+
+ Assert.That(result.Status, Is.EqualTo(OK));
+ 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));
+ 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 newRefSetByCallback = "unset lol";
+ ValidatedAssetResult result = ResourceValidationHelper.ValidateReference(new(hash, dataContext, importer)
+ {
+ MustBeTexture = true,
+ MustBeInDataStoreIfHash = true,
+ OnNewAssetRefCallback = delegate(string NewAssetRef) { newRefSetByCallback = NewAssetRef; },
+ }, dataContext.Logger);
+
+ Assert.That(result.Status, Is.EqualTo(BadRequest));
+ 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));
+ 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