Skip to content

Commit

Permalink
Fix level hash play (#470)
Browse files Browse the repository at this point in the history
  • Loading branch information
jvyden authored May 7, 2024
2 parents ae7403a + a255525 commit 1f3b3ac
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 62 deletions.
2 changes: 1 addition & 1 deletion Refresh.GameServer/Endpoints/ApiV3/LevelApiEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public ApiOkResponse SetLevelAsOverrideByHash(RequestContext context, GameDataba
if (!CommonPatterns.Sha1Regex().IsMatch(hash))
return ApiValidationError.HashInvalidError;

service.AddHashOverridesForUser(user, hash);
service.AddHashOverrideForUser(user, hash);

return new ApiOkResponse();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,52 @@ public class GameLevelResponse : IDataConvertableFrom<GameLevelResponse, GameLev
[XmlElement("reviewsEnabled")] public bool ReviewsEnabled { get; set; } = true;
[XmlElement("commentCount")] public int CommentCount { get; set; } = 0;
[XmlElement("commentsEnabled")] public bool CommentsEnabled { get; set; } = true;


public static GameLevelResponse FromHash(string hash)
{
return new GameLevelResponse
{
LevelId = int.MaxValue,
Title = $"Hashed Level - {hash}",
IconHash = "0",
GameVersion = 0,
RootResource = hash,
Description = "This is a hashed level. We don't know anything about it.",
Location = new GameLocation(),
Handle = new SerializedUserHandle
{
Username = $"!Hashed",
IconHash = "0",
},
Type = "user",
TeamPicked = false,
MinPlayers = 1,
MaxPlayers = 4,
HeartCount = 0,
TotalPlayCount = 0,
UniquePlayCount = 0,
YayCount = 0,
BooCount = 0,
AverageStarRating = 0,
YourStarRating = 0,
YourRating = 0,
PlayerCount = 0,
ReviewsEnabled = false,
ReviewCount = 0,
CommentsEnabled = false,
CommentCount = 0,
IsLocked = false,
IsSubLevel = false,
IsCopyable = 0,
PublishDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
UpdateDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
EnforceMinMaxPlayers = false,
SameScreenGame = false,
SkillRewards = [],
LevelType = "",
};
}

public static GameLevelResponse? FromOldWithExtraData(GameLevel? old, GameDatabaseContext database, MatchService matchService, GameUser user, IDataStore dataStore, TokenGame game)
{
if (old == null) return null;
Expand Down
32 changes: 27 additions & 5 deletions Refresh.GameServer/Endpoints/Game/Levels/LevelEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,25 @@ public class LevelEndpoints : EndpointGroup
if (overrideService.GetIdOverridesForUser(token, database, out IEnumerable<GameLevel> levelOverrides))
overrides.AddRange(levelOverrides.Select(l => GameMinimalLevelResponse.FromOldWithExtraData(l, matchService, database, dataStore, token.TokenGame))!);

if (overrideService.GetHashOverridesForUser(token, out IEnumerable<string> hashOverrides))
overrides.AddRange(hashOverrides.Select(GameMinimalLevelResponse.FromHash));
if (overrideService.GetHashOverrideForUser(token, out string hashOverride))
overrides.Add(GameMinimalLevelResponse.FromHash(hashOverride));

return new SerializedMinimalLevelList(overrides, overrides.Count, overrides.Count);
}

// If we are getting the levels by a user, and that user is "!Hashed", then we pull that user's overrides
if (route == "by"
&& (context.QueryString.Get("u") == "!Hashed" || user.Username == "!Hashed")
&& overrideService.GetLastHashOverrideForUser(token, out string hash))
{
return new SerializedMinimalLevelList
{
Total = 1,
NextPageStart = 1,
Items = [GameMinimalLevelResponse.FromHash(hash)],
};
}

(int skip, int count) = context.GetPageData();

DatabaseList<GameLevel>? levels = categoryService.Categories
Expand Down Expand Up @@ -80,9 +93,18 @@ public class LevelEndpoints : EndpointGroup
[GameEndpoint("s/{slotType}/{id}", ContentType.Xml)]
[NullStatusCode(NotFound)]
[MinimumRole(GameUserRole.Restricted)]
public GameLevelResponse? LevelById(RequestContext context, GameDatabaseContext database, MatchService matchService, GameUser user, string slotType, int id, IDataStore dataStore, Token token)
=> GameLevelResponse.FromOldWithExtraData(database.GetLevelByIdAndType(slotType, id), database, matchService, user, dataStore, token.TokenGame);

public GameLevelResponse? LevelById(RequestContext context, GameDatabaseContext database, MatchService matchService,
GameUser user, string slotType, int id, IDataStore dataStore, Token token, LevelListOverrideService overrideService)
{
if (id == int.MaxValue && overrideService.GetLastHashOverrideForUser(token, out string hash))
{
return GameLevelResponse.FromHash(hash);
}

return GameLevelResponse.FromOldWithExtraData(database.GetLevelByIdAndType(slotType, id), database,
matchService, user, dataStore, token.TokenGame);
}

[GameEndpoint("slotList", ContentType.Xml)]
[NullStatusCode(BadRequest)]
[MinimumRole(GameUserRole.Restricted)]
Expand Down
2 changes: 1 addition & 1 deletion Refresh.GameServer/Services/CommandService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public void HandleCommand(CommandInvocation command, GameDatabaseContext databas
{
if (CommonPatterns.Sha1Regex().IsMatch(command.Arguments))
{
this._levelListService.AddHashOverridesForUser(user, command.Arguments.ToString());
this._levelListService.AddHashOverrideForUser(user, command.Arguments.ToString());
}
else
{
Expand Down
60 changes: 41 additions & 19 deletions Refresh.GameServer/Services/LevelListOverrideService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public LevelListOverrideService(Logger logger) : base(logger)
{}

private readonly Dictionary<ObjectId, List<int>> _userIdsToLevelList = new(1);
private readonly Dictionary<ObjectId, List<string>> _userIdsToLevelHashList = new(1);
private readonly Dictionary<ObjectId, (bool accessed, string hash)> _userIdsToLevelHash = new(1);

private bool UserHasLevelIdOverrides(GameUser user)
{
Expand All @@ -25,45 +25,67 @@ private bool UserHasLevelIdOverrides(GameUser user)
return result;
}

private bool UserHasLevelHashOverrides(GameUser user)
private bool UserHasLevelHashOverride(GameUser user)
{
bool result = this._userIdsToLevelHashList.ContainsKey(user.UserId);
bool result;

this.Logger.LogTrace(RefreshContext.LevelListOverride, "{0} has hash overrides: {1}", user.Username, result);
if (this._userIdsToLevelHash.TryGetValue(user.UserId, out (bool accessed, string hash) value))
result = !value.accessed;
else
result = false;

this.Logger.LogTrace(RefreshContext.LevelListOverride, "{0} has hash override: {1}", user.Username, result);
return result;
}

public bool UserHasOverrides(GameUser user)
=> this.UserHasLevelHashOverrides(user) || this.UserHasLevelIdOverrides(user);
=> this.UserHasLevelHashOverride(user) || this.UserHasLevelIdOverrides(user);

public void AddHashOverridesForUser(GameUser user, string hash)
=> this.AddHashOverridesForUser(user, new[] { hash });
public void AddHashOverrideForUser(GameUser user, string hash)
{
this.Logger.LogDebug(RefreshContext.LevelListOverride, "Adding level hash override for {0}: [{1}]", user.Username, hash);

this._userIdsToLevelHash[user.UserId] = (false, hash);
}

public void AddHashOverridesForUser(GameUser user, IEnumerable<string> hashes)
public bool GetLastHashOverrideForUser(Token token, out string hash)
{
Debug.Assert(!this.UserHasLevelHashOverrides(user), "User already has overrides");
GameUser user = token.User;

if (!this._userIdsToLevelHash.TryGetValue(user.UserId, out (bool accessed, string hash) value))
{
hash = null!;
return false;
}

List<string> hashList = hashes.ToList();
hash = value.hash;

this.Logger.LogDebug(RefreshContext.LevelListOverride, "Adding level hash overrides for {0}: [{1}]", user.Username, string.Join(", ", hashList));
this._userIdsToLevelHashList.Add(user.UserId, hashList);
return true;
}

public bool GetHashOverridesForUser(Token token, out IEnumerable<string> hashes)
public bool GetHashOverrideForUser(Token token, out string hash)
{
GameUser user = token.User;

if (!this.UserHasLevelHashOverrides(user))
if (!this.UserHasLevelHashOverride(user))
{
hash = null!;
return false;
}

(bool accessed, string hash) overrides = this._userIdsToLevelHash[user.UserId];

if (overrides.accessed)
{
hashes = null!;
hash = null!;
return false;
}

List<string> overrides = this._userIdsToLevelHashList[user.UserId].ToList();
this.Logger.LogDebug(RefreshContext.LevelListOverride, "Getting level hash overrides for {0}: [{1}]", user.Username, string.Join(", ", overrides));
this._userIdsToLevelHashList.Remove(user.UserId);
this.Logger.LogDebug(RefreshContext.LevelListOverride, "Getting level hash override for {0}: [{1}]", user.Username, overrides.hash);

hash = overrides.hash;

hashes = overrides;
this._userIdsToLevelHash[user.UserId] = (true, overrides.hash);

return true;
}
Expand Down
36 changes: 1 addition & 35 deletions Refresh.GameServer/Types/Levels/GameMinimalLevelResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,41 +55,7 @@ private GameMinimalLevelResponse() {}
/// <returns></returns>
public static GameMinimalLevelResponse FromHash(string hash)
{
return new GameMinimalLevelResponse
{
LevelId = int.MaxValue,
Title = $"Hashed Level - {hash}",
IconHash = "0",
GameVersion = 0,
RootResource = hash,
Description = "This is a hashed level. We don't know anything about it.",
Location = new GameLocation(),
Handle = new SerializedUserHandle
{
Username = "!Unknown",
IconHash = "0",
},
Type = "user",
TeamPicked = false,
MinPlayers = 1,
MaxPlayers = 4,
HeartCount = 0,
TotalPlayCount = 0,
UniquePlayCount = 0,
YayCount = 0,
BooCount = 0,
AverageStarRating = 0,
YourStarRating = 0,
YourRating = 0,
PlayerCount = 0,
ReviewsEnabled = false,
ReviewCount = 0,
CommentsEnabled = false,
CommentCount = 0,
IsLocked = false,
IsSubLevel = false,
IsCopyable = 0,
};
return FromOld(GameLevelResponse.FromHash(hash))!;
}

public static GameMinimalLevelResponse? FromOldWithExtraData(GameLevelResponse? old, MatchService matchService, GameDatabaseContext database, IDataStore dataStore, TokenGame game)
Expand Down

0 comments on commit 1f3b3ac

Please sign in to comment.