Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Refresh.Database/Models/Levels/Tag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ public enum Tag : byte
Electric = 75,
Frosty,
Insane,
// This isn't in TagsMap on purpose, because this is only used as the default value by FromLbpString(),
// because GetValueOrDefault() will never return null in this case. "Invalid" tag uploads should be rejected.
Invalid,
}

public static class TagExtensions
Expand Down Expand Up @@ -193,5 +196,5 @@ static TagExtensions()

public static string? ToLbpString(this Tag tag) => StringMap.GetValueOrDefault(tag);

public static Tag? FromLbpString(string str) => TagsMap.GetValueOrDefault(str);
public static Tag FromLbpString(string str) => TagsMap.GetValueOrDefault(str, Tag.Invalid);
}
50 changes: 49 additions & 1 deletion Refresh.Interfaces.Game/Endpoints/Levels/LeaderboardEndpoints.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Xml.Serialization;
using Bunkum.Core;
using Bunkum.Core.Endpoints;
using Bunkum.Core.Responses;
Expand Down Expand Up @@ -26,7 +27,21 @@ public class LeaderboardEndpoints : EndpointGroup
[GameEndpoint("play/{slotType}/{id}", ContentType.Xml, HttpMethods.Post)]
[MinimumRole(GameUserRole.Restricted)]
[EndpointRateLimit(EndpointBucketId.GamePlayLevel)]
public Response PlayLevel(RequestContext context, GameUser user, GameDatabaseContext database, string slotType, int id, DataContext dataContext)
public Response PlayLevel(RequestContext context, GameUser user, GameDatabaseContext database, string slotType, int id)
{
Response realResponse = this.PlayLevelInternal(context, user, database, slotType, id);

// PSP always wants OK, else it will cancel login during downloaded level play data uploads.
if (context.IsPSP())
{
context.Logger.LogTrace(BunkumCategory.Game, $"Actual response status is {realResponse.StatusCode}, but the client is LBP PSP, so we will return OK instead.");
return OK;
}

return realResponse;
}

private Response PlayLevelInternal(RequestContext context, GameUser user, GameDatabaseContext database, string slotType, int id)
{
GameLevel? level = database.GetLevelByIdAndType(slotType, id);
if (level == null) return NotFound;
Expand Down Expand Up @@ -94,6 +109,39 @@ public Response GetUserScores(RequestContext context, GameUser user, GameDatabas
[EndpointRateLimit(EndpointBucketId.GameUploadLevelScore)]
[RequireEmailVerified]
public Response SubmitScore(RequestContext context, GameUser user, GameServerConfig config,
GameDatabaseContext database, string slotType, int id, string body, Token token,
DataContext dataContext)
{
// Parsing manually because we don't want unparseable requests from PSP to cause MainMiddleware to return BadRequest to it,
// else it will explode or something.
SerializedScore? score = null;
try
{
XmlSerializer serializer = new(typeof(SerializedScore));
object? obj = serializer.Deserialize(new StringReader(body)) ?? throw new Exception();
score = (SerializedScore)obj;
}
catch (Exception e)
{
context.Logger.LogError(BunkumCategory.UserContent, $"Failed to parse score upload: {e}\n\nXML: {body}");
}

Response realResponse = score == null
? BadRequest
: this.SubmitScoreInternal(context, user, config, database, slotType, id, score, token, dataContext);

// PSP always wants OK, else it will cancel login during downloaded level play data uploads.
// Maybe it also expects an actual score list, so return an empty one aswell. We will see whether we need to do more later.
if (context.IsPSP())
{
context.Logger.LogTrace(BunkumCategory.Game, $"Actual response status is {realResponse.StatusCode}, but the client is LBP PSP, so we will return OK instead.");
return new Response(new SerializedScoreLeaderboardList(), ContentType.Xml);
}

return realResponse;
}

private Response SubmitScoreInternal(RequestContext context, GameUser user, GameServerConfig config,
GameDatabaseContext database, string slotType, int id, SerializedScore body, Token token,
DataContext dataContext)
{
Expand Down
27 changes: 22 additions & 5 deletions Refresh.Interfaces.Game/Endpoints/RelationEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,24 @@ public Response ClearQueue(RequestContext context, GameDatabaseContext database,

[GameEndpoint("tag/{slotType}/{id}", HttpMethods.Post)]
[RequireEmailVerified]
[AllowEmptyBody]
[EndpointRateLimit(EndpointBucketId.TagLevel)]
public Response SubmitTagsForLevel(RequestContext context, GameDatabaseContext database, GameUser user, DataContext dataContext,
public Response SubmitTagForLevel(RequestContext context, GameDatabaseContext database, GameUser user,
string slotType, int id, string? body, GameServerConfig config)
{
Response realResponse = this.SubmitTagForLevelInternal(context, database, user, slotType, id, body ?? "", config);

// PSP always wants OK, else it will cancel login during downloaded level play data uploads.
if (context.IsPSP())
{
context.Logger.LogTrace(BunkumCategory.Game, $"Actual response status is {realResponse.StatusCode}, but the client is LBP PSP, so we will return OK instead.");
return OK;
}

return realResponse;
}

private Response SubmitTagForLevelInternal(RequestContext context, GameDatabaseContext database, GameUser user,
string slotType, int id, string body, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
Expand All @@ -156,13 +172,14 @@ public Response SubmitTagsForLevel(RequestContext context, GameDatabaseContext d
if (!body.StartsWith("t="))
return BadRequest;

Tag? tag = TagExtensions.FromLbpString(body[2..]);
Tag tag = TagExtensions.FromLbpString(body[2..]);

// If it was an invalid tag, return BadRequest
if (tag == null)
// If it was an invalid tag, return BadRequest.
// Cannot be null due to weird behaviour with GetValueOrDefault(), so do it like this instead.
if (tag == Tag.Invalid)
return BadRequest;

database.AddTagRelation(user, level, tag.Value);
database.AddTagRelation(user, level, tag);

return OK;
}
Expand Down
37 changes: 35 additions & 2 deletions Refresh.Interfaces.Game/Endpoints/ReportingEndpoints.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Drawing;
using System.Text;
using System.Xml.Serialization;
using Bunkum.Core;
using Bunkum.Core.Endpoints;
using Bunkum.Core.Responses;
Expand Down Expand Up @@ -26,7 +28,38 @@ public class ReportingEndpoints : EndpointGroup
[GameEndpoint("grief", HttpMethods.Post, ContentType.Xml)]
[RequireEmailVerified]
[EndpointRateLimit(EndpointBucketId.GameUploadGriefReport)]
public Response UploadReport(RequestContext context, GameDatabaseContext database, GameReport body, GameUser user,
public Response UploadReport(RequestContext context, GameDatabaseContext database, string body, GameUser user,
IDateTimeProvider time, Token token, GameServerConfig config)
{
// Parsing manually because we don't want unparseable requests from PSP to cause MainMiddleware to return BadRequest to it,
// else it will explode or something.
GameReport? report = null;
try
{
XmlSerializer serializer = new(typeof(GameReport));
object? obj = serializer.Deserialize(new StringReader(body)) ?? throw new Exception();
report = (GameReport)obj;
}
catch (Exception e)
{
context.Logger.LogError(BunkumCategory.UserContent, $"Failed to parse grief report: {e}\n\nXML: {body}");
}

Response realResponse = report == null
? BadRequest
: this.UploadReportInternal(context, database, report, user, time, token, config);

// PSP always wants OK, else it will softlock the save for some stupid reason (according to the comment below atleast).
if (context.IsPSP())
{
context.Logger.LogTrace(BunkumCategory.Game, $"Actual response status is {realResponse.StatusCode}, but the client is LBP PSP, so we will return OK instead.");
return OK;
}

return realResponse;
}

private Response UploadReportInternal(RequestContext context, GameDatabaseContext database, GameReport body, GameUser user,
IDateTimeProvider time, Token token, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
Expand Down Expand Up @@ -105,7 +138,7 @@ public Response UploadReport(RequestContext context, GameDatabaseContext databas
GameUser? playerUser = database.GetUserByUsername(player.Username); // what if user doesnt exist
return new ReportPlayerRelation
{
User = playerUser,
User = playerUser,
IsReporter = player.Reporter,
IsInGameNow = player.IngameNow,
PlayerNumber = player.PlayerNumber,
Expand Down
18 changes: 17 additions & 1 deletion Refresh.Interfaces.Game/Endpoints/ReviewEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Refresh.Database.Models.Users;
using Refresh.Interfaces.Game.Types.Reviews;
using Refresh.Core.RateLimits.EndpointRateLimiting;
using Refresh.Common;

namespace Refresh.Interfaces.Game.Endpoints;

Expand Down Expand Up @@ -53,7 +54,22 @@ public Response SubmitLevelDpadRating(RequestContext context, GameDatabaseContex
[RequireEmailVerified]
[EndpointRateLimit(EndpointBucketId.RateLevel)]
public Response SubmitLevelStarRating(RequestContext context, GameDatabaseContext database, GameUser user, string slotType, int id,
DataContext dataContext, GameServerConfig config)
GameServerConfig config)
{
Response realResponse = this.SubmitLevelStarRatingInternal(context, database, user, slotType, id, config);

// PSP always wants OK, else it will cancel login during downloaded level play data uploads.
if (context.IsPSP())
{
context.Logger.LogTrace(BunkumCategory.Game, $"Actual response status is {realResponse.StatusCode}, but the client is LBP PSP, so we will return OK instead.");
return OK;
}

return realResponse;
}

private Response SubmitLevelStarRatingInternal(RequestContext context, GameDatabaseContext database, GameUser user, string slotType, int id,
GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return Unauthorized;
Expand Down
6 changes: 4 additions & 2 deletions RefreshTests.GameServer/Tests/Levels/ScoreLeaderboardTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -690,10 +690,12 @@ public void DoesntPlayLevelWithInvalidCount()
client.DefaultRequestHeaders.UserAgent.TryParseAdd("LBPPSP CLIENT");

HttpResponseMessage message = client.PostAsync($"/lbp/play/user/{level.LevelId}?count=gtgnyegth", new ReadOnlyMemoryContent(Array.Empty<byte>())).Result;
Assert.That(message.StatusCode, Is.EqualTo(BadRequest));
Assert.That(message.StatusCode, Is.EqualTo(OK));
Assert.That(context.Database.GetTotalPlaysForLevelByUser(level, user), Is.Zero); // invalid play count wasn't saved despite OK response

HttpResponseMessage message2 = client.PostAsync($"/lbp/play/user/{level.LevelId}?count=-5", new ReadOnlyMemoryContent(Array.Empty<byte>())).Result;
Assert.That(message2.StatusCode, Is.EqualTo(BadRequest));
Assert.That(message2.StatusCode, Is.EqualTo(OK));
Assert.That(context.Database.GetTotalPlaysForLevelByUser(level, user), Is.Zero); // invalid play count wasn't saved despite OK response
}

[Test]
Expand Down
Loading
Loading