Parse root level version instead of relying on token gameversion. (#332)

* Implement root level revision parsing

* Fix class naming

* Implement suggestions from code review

* Safety checks and remove deploy build

* Don't attempt to parse LVLt and change branch nomenclature.

* Treat text formatted resources as unsafe

* Update magic header of test script file

* Fix LBP Vita revision check

Co-authored-by: Jayden <jvyden@jvyden.xyz>
This commit is contained in:
Josh 2022-07-30 00:23:37 -05:00 committed by GitHub
parent e937f4f7cb
commit a599732894
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 96 additions and 24 deletions

View file

@ -8,7 +8,6 @@ using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@ -47,6 +46,12 @@ public class PublishController : ControllerBase
if (string.IsNullOrEmpty(slot.ResourceCollection)) slot.ResourceCollection = slot.RootLevel;
LbpFile? rootLevel = LbpFile.FromHash(slot.RootLevel);
if (rootLevel == null) return this.BadRequest();
GameVersion slotVersion = FileHelper.ParseLevelVersion(rootLevel);
if (slotVersion == GameVersion.Unknown) slotVersion = gameToken.GameVersion;
// Republish logic
if (slot.SlotId != 0)
{
@ -54,7 +59,7 @@ public class PublishController : ControllerBase
if (oldSlot == null) return this.NotFound();
if (oldSlot.CreatorId != user.UserId) return this.BadRequest();
}
else if (user.GetUsedSlotsForGame(gameToken.GameVersion) > ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
else if (user.GetUsedSlotsForGame(slotVersion) > ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
{
return this.StatusCode(403, "");
}
@ -127,6 +132,11 @@ public class PublishController : ControllerBase
return this.BadRequest();
}
GameVersion slotVersion = FileHelper.ParseLevelVersion(rootLevel);
slot.GameVersion = slotVersion;
if (slotVersion == GameVersion.Unknown) slot.GameVersion = gameToken.GameVersion;
// Republish logic
if (slot.SlotId != 0)
{
@ -177,16 +187,6 @@ public class PublishController : ControllerBase
slot.TeamPick = oldSlot.TeamPick;
// Only update a slot's gameVersion if the level was actually change
if (oldSlot.RootLevel != slot.RootLevel)
{
slot.GameVersion = gameToken.GameVersion;
}
else
{
slot.GameVersion = oldSlot.GameVersion;
}
if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
{
slot.MinimumPlayers = 1;
@ -198,7 +198,7 @@ public class PublishController : ControllerBase
return this.Ok(oldSlot.Serialize(gameToken.GameVersion));
}
if (user.GetUsedSlotsForGame(gameToken.GameVersion) > ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
if (user.GetUsedSlotsForGame(slotVersion) > ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
{
Logger.Warn("Rejecting level upload, too many published slots", LogArea.Publish);
return this.BadRequest();
@ -216,7 +216,6 @@ public class PublishController : ControllerBase
slot.CreatorId = user.UserId;
slot.FirstUploaded = TimeHelper.UnixTimeMilliseconds();
slot.LastUpdated = TimeHelper.UnixTimeMilliseconds();
slot.GameVersion = gameToken.GameVersion;
if (slot.MinimumPlayers == 0 || slot.MaximumPlayers == 0)
{

View file

@ -1,3 +1,3 @@
FSH
FSHb
this is not my stuff to upload so its just gonna be a file like this for now :/

View file

@ -0,0 +1,27 @@
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData;
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.MigrationTasks;
public class CleanupSlotVersionMismatchMigration : IMigrationTask
{
public string Name() => "Cleanup slot versions";
async Task<bool> IMigrationTask.Run(Database database)
{
foreach (Slot slot in database.Slots)
{
LbpFile rootLevel = LbpFile.FromHash(slot.RootLevel);
if (rootLevel == null) continue;
GameVersion slotVersion = FileHelper.ParseLevelVersion(rootLevel);
if (slotVersion != GameVersion.Unknown) slot.GameVersion = slotVersion;
}
await database.SaveChangesAsync();
return true;
}
}

View file

@ -11,6 +11,7 @@ using ICSharpCode.SharpZipLib.Zip.Compression;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@ -50,6 +51,51 @@ public static class FileHelper
};
}
public static GameVersion ParseLevelVersion(LbpFile file)
{
if (file.FileType != LbpFileType.Level || file.Data.Length < 16 || file.Data[3] != 'b') return GameVersion.Unknown;
// Revision numbers borrowed from https://github.com/ennuo/toolkit/blob/main/src/main/java/ennuo/craftworld/resources/structs/Revision.java
const ushort lbp2Latest = 0x3F8;
const ushort lbp1Latest = 0x272;
const ushort lbpVitaLatest = 0x3E2;
const ushort lbpVitaDescriptor = 0x4431;
// There are like 1600 revisions so this doesn't cover everything
uint revision = 0;
// construct a 32 bit number from 4 individual bytes
for (int i = 4; i <= 7; i++)
{
revision <<= 8;
revision |= file.Data[i];
}
if (revision >= 0x271)
{
// construct a 16 bit number from 2 individual bytes
ushort branchDescriptor = (ushort) (file.Data[12] << 8 | file.Data[13]);
if (revision == lbpVitaLatest && branchDescriptor == lbpVitaDescriptor) return GameVersion.LittleBigPlanetVita;
}
GameVersion version = GameVersion.Unknown;
if (revision <= lbp1Latest)
{
version = GameVersion.LittleBigPlanet1;
}
else if (revision >> 0x10 != 0)
{
version = GameVersion.LittleBigPlanet3;
}
else if(revision <= lbp2Latest)
{
version = GameVersion.LittleBigPlanet2;
}
return version;
}
public static LbpFileType DetermineFileType(byte[] data)
{
if (data.Length == 0) return LbpFileType.Unknown; // Can't be anything if theres no data.
@ -63,18 +109,18 @@ public static class FileHelper
string footer = Encoding.ASCII.GetString(readLastBytes(reader, 4));
if (footer == "FARC") return LbpFileType.FileArchive;
byte[] header = reader.ReadBytes(3);
byte[] header = reader.ReadBytes(4);
return Encoding.ASCII.GetString(header) switch
{
"REC" => LbpFileType.MotionRecording,
"PRF" => LbpFileType.CrossLevel,
"PTG" => LbpFileType.Painting,
"TEX" => LbpFileType.Texture,
"FSH" => LbpFileType.Script,
"VOP" => LbpFileType.Voice,
"LVL" => LbpFileType.Level,
"PLN" => LbpFileType.Plan,
"RECb" => LbpFileType.MotionRecording,
"PRFb" => LbpFileType.CrossLevel,
"PTGb" => LbpFileType.Painting,
"TEX " => LbpFileType.Texture,
"FSHb" => LbpFileType.Script,
"VOPb" => LbpFileType.Voice,
"LVLb" => LbpFileType.Level,
"PLNb" => LbpFileType.Plan,
_ => readAlternateHeader(reader),
};
}