-
Notifications
You must be signed in to change notification settings - Fork 34
Implement helper method for asset reference validation #1086
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Toastbrot236
merged 7 commits into
LittleBigRefresh:main
from
Toastbrot236:res-validation-helper
Jun 14, 2026
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
8e80840
Add AssetFlags.Imagery
Toastbrot236 1381db9
Catch IsPspTga() exceptions
Toastbrot236 da88500
Implement ResourceValidationHelper
Toastbrot236 c424722
TestContext: Allow outputting Token when authenticating, allow specif…
Toastbrot236 b4ccec2
Test ResourceValidationHelper
Toastbrot236 146ca07
Improve comments and naming
Toastbrot236 cd6f58e
Remove outdated comment
Toastbrot236 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| { | ||
| /// <summary> | ||
| /// 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 <see cref="GameAsset"/> and <see cref="DisallowedAsset"/>). | ||
| /// </summary> | ||
| 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<string>? 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); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
Refresh.Core/Types/Assets/Validation/AssetValidationParameters.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| { | ||
| /// <summary> | ||
| /// The reference (hash/guid/blank) to validate | ||
| /// </summary> | ||
| 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; | ||
|
|
||
| /// <summary> | ||
| /// 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. | ||
| /// </summary> | ||
| public string? AssetContextTypeStr { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Callback which is called with the new asset reference as parameter when constructing <see cref="ValidatedAssetResult"/>; | ||
| /// 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. | ||
| /// </summary> | ||
| public Action<string>? 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() | ||
| { | ||
|
|
||
| } | ||
| } |
41 changes: 41 additions & 0 deletions
41
Refresh.Core/Types/Assets/Validation/ValidatedAssetResult.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| using System.Net; | ||
| using Refresh.Database.Models.Assets; | ||
|
|
||
| namespace Refresh.Core.Types.Assets.Validation; | ||
|
|
||
| public struct ValidatedAssetResult | ||
| { | ||
| /// <summary> | ||
| /// HTTP code to return if validation failed. | ||
| /// OK: don't cancel request and proceed. | ||
| /// </summary> | ||
| public HttpStatusCode Status { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// new reference (hash/guid/blank) to use | ||
| /// </summary> | ||
| public string NewAssetRef { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// message to show to the user. | ||
| /// null: don't show anything. | ||
| /// </summary> | ||
| 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<string>? 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; | ||
|
|
||
| onNewAssetRefCallback?.Invoke(this.NewAssetRef); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.