Resolve conflicts

This commit is contained in:
jvyden 2021-10-31 17:06:41 -04:00
commit 4308ee1a71
No known key found for this signature in database
GPG key ID: 18BCF2BE0262B278
85 changed files with 3200 additions and 936 deletions

View file

@ -1,4 +1,4 @@
on: [push] on: [ push ]
name: Continuous Integration name: Continuous Integration
# Inspired by osu! lazer's CI # Inspired by osu! lazer's CI
@ -22,11 +22,6 @@ jobs:
DB_USER: root DB_USER: root
DB_PASSWORD: lighthouse DB_PASSWORD: lighthouse
steps: steps:
- name: Cancel previous runs of this workflow
uses: styfle/cancel-workflow-action@0.6.0
with:
access_token: ${{ github.token }}
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -36,11 +31,11 @@ jobs:
with: with:
mysql-version: '8.0' mysql-version: '8.0'
root-password: ${{ env.DB_PASSWORD }} root-password: ${{ env.DB_PASSWORD }}
- name: Create Lighthouse Database - name: Create Lighthouse Database
if: ${{ matrix.os.database }} if: ${{ matrix.os.database }}
run: mysql -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} -h 127.0.0.1 -e "CREATE DATABASE ${{ env.DB_DATABASE }};"; run: mysql -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} -h 127.0.0.1 -e "CREATE DATABASE ${{ env.DB_DATABASE }};";
- name: Install .NET 5.0 - name: Install .NET 5.0
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
@ -52,10 +47,10 @@ jobs:
with: with:
dotnet-version: "6.0.x" dotnet-version: "6.0.x"
include-prerelease: true include-prerelease: true
- name: Compile - name: Compile
run: dotnet build -c Debug run: dotnet build -c Debug
- name: Test - name: Test
continue-on-error: true continue-on-error: true
run: dotnet test --logger "trx;LogFileName=${{github.workspace}}/TestResults-${{matrix.os.prettyName}}.trx" run: dotnet test --logger "trx;LogFileName=${{github.workspace}}/TestResults-${{matrix.os.prettyName}}.trx"
@ -84,7 +79,7 @@ jobs:
create-status-check: false create-status-check: false
create-pr-comment: false create-pr-comment: false
update-comment-if-one-exists: false update-comment-if-one-exists: false
- name: Check Test Results - name: Check Test Results
if: steps.process-trx.outputs.test-outcome == 'Failed' if: steps.process-trx.outputs.test-outcome == 'Failed'
run: | run: |

View file

@ -1,11 +1,12 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Development Database" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker"> <configuration default="false" name="Development Database" type="docker-deploy" factoryName="docker-compose.yml"
<deployment type="docker-compose.yml"> server-name="Docker">
<settings> <deployment type="docker-compose.yml">
<option name="envFilePath" value="" /> <settings>
<option name="sourceFilePath" value="docker-compose.yml" /> <option name="envFilePath" value=""/>
</settings> <option name="sourceFilePath" value="docker-compose.yml"/>
</deployment> </settings>
<method v="2" /> </deployment>
</configuration> <method v="2"/>
</configuration>
</component> </component>

View file

@ -2,12 +2,19 @@ using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Xunit; using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests { namespace LBPUnion.ProjectLighthouse.Tests
public sealed class DatabaseFact : FactAttribute { {
public DatabaseFact() { public sealed class DatabaseFact : FactAttribute
{
public DatabaseFact()
{
ServerSettings.DbConnectionString = "server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse"; ServerSettings.DbConnectionString = "server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse";
if(!ServerSettings.DbConnected) this.Skip = "Database not available"; if (!ServerSettings.DbConnected)
else { {
this.Skip = "Database not available";
}
else
{
using Database database = new(); using Database database = new();
database.Database.Migrate(); database.Database.Migrate();
} }

View file

@ -8,31 +8,33 @@ using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost; using Microsoft.AspNetCore.TestHost;
namespace LBPUnion.ProjectLighthouse.Tests { namespace LBPUnion.ProjectLighthouse.Tests
{
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
public class LighthouseTest { public class LighthouseTest
public readonly TestServer Server; {
public readonly HttpClient Client; public readonly HttpClient Client;
public readonly TestServer Server;
public LighthouseTest()
{
this.Server = new TestServer(new WebHostBuilder().UseStartup<Startup>());
public LighthouseTest() {
this.Server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>());
this.Client = this.Server.CreateClient(); this.Client = this.Server.CreateClient();
} }
public async Task<HttpResponseMessage> AuthenticateResponse(int number = 0) { public async Task<HttpResponseMessage> AuthenticateResponse(int number = 0)
const char nullChar = (char)0x00; {
const char sepChar = (char)0x20;
const string username = "unitTestUser"; const string username = "unitTestUser";
string stringContent = $"{nullChar}{sepChar}{username}{number}{nullChar}"; string stringContent = $"{LoginData.UsernamePrefix}{username}{number}{(char)0x00}";
HttpResponseMessage response = await this.Client.PostAsync("/LITTLEBIGPLANETPS3_XML/login", new StringContent(stringContent)); HttpResponseMessage response = await this.Client.PostAsync("/LITTLEBIGPLANETPS3_XML/login", new StringContent(stringContent));
return response; return response;
} }
public async Task<LoginResult> Authenticate(int number = 0) { public async Task<LoginResult> Authenticate(int number = 0)
{
HttpResponseMessage response = await this.AuthenticateResponse(number); HttpResponseMessage response = await this.AuthenticateResponse(number);
string responseContent = LbpSerializer.StringElement("loginResult", await response.Content.ReadAsStringAsync()); string responseContent = LbpSerializer.StringElement("loginResult", await response.Content.ReadAsStringAsync());
@ -43,30 +45,31 @@ namespace LBPUnion.ProjectLighthouse.Tests {
public Task<HttpResponseMessage> AuthenticatedRequest(string endpoint, string mmAuth) => this.AuthenticatedRequest(endpoint, mmAuth, HttpMethod.Get); public Task<HttpResponseMessage> AuthenticatedRequest(string endpoint, string mmAuth) => this.AuthenticatedRequest(endpoint, mmAuth, HttpMethod.Get);
public Task<HttpResponseMessage> AuthenticatedRequest(string endpoint, string mmAuth, HttpMethod method) { public Task<HttpResponseMessage> AuthenticatedRequest(string endpoint, string mmAuth, HttpMethod method)
using var requestMessage = new HttpRequestMessage(method, endpoint); {
using HttpRequestMessage? requestMessage = new(method, endpoint);
requestMessage.Headers.Add("Cookie", mmAuth); requestMessage.Headers.Add("Cookie", mmAuth);
return this.Client.SendAsync(requestMessage); return this.Client.SendAsync(requestMessage);
} }
public async Task<HttpResponseMessage> UploadFileRequest(string endpoint, string filePath) { public async Task<HttpResponseMessage> UploadFileRequest(string endpoint, string filePath)
return await this.Client.PostAsync(endpoint, new StringContent(await File.ReadAllTextAsync(filePath))); => await this.Client.PostAsync(endpoint, new StringContent(await File.ReadAllTextAsync(filePath)));
}
public async Task<HttpResponseMessage> UploadDataRequest(string endpoint, byte[] data) { public async Task<HttpResponseMessage> UploadDataRequest(string endpoint, byte[] data)
return await this.Client.PostAsync(endpoint, new ByteArrayContent(data)); => await this.Client.PostAsync(endpoint, new ByteArrayContent(data));
}
public async Task<HttpResponseMessage> AuthenticatedUploadFileRequest(string endpoint, string filePath, string mmAuth) { public async Task<HttpResponseMessage> AuthenticatedUploadFileRequest(string endpoint, string filePath, string mmAuth)
using var requestMessage = new HttpRequestMessage(HttpMethod.Post, endpoint); {
using HttpRequestMessage? requestMessage = new(HttpMethod.Post, endpoint);
requestMessage.Headers.Add("Cookie", mmAuth); requestMessage.Headers.Add("Cookie", mmAuth);
requestMessage.Content = new StringContent(await File.ReadAllTextAsync(filePath)); requestMessage.Content = new StringContent(await File.ReadAllTextAsync(filePath));
return await this.Client.SendAsync(requestMessage); return await this.Client.SendAsync(requestMessage);
} }
public async Task<HttpResponseMessage> AuthenticatedUploadDataRequest(string endpoint, byte[] data, string mmAuth) { public async Task<HttpResponseMessage> AuthenticatedUploadDataRequest(string endpoint, byte[] data, string mmAuth)
using var requestMessage = new HttpRequestMessage(HttpMethod.Post, endpoint); {
using HttpRequestMessage? requestMessage = new(HttpMethod.Post, endpoint);
requestMessage.Headers.Add("Cookie", mmAuth); requestMessage.Headers.Add("Cookie", mmAuth);
requestMessage.Content = new ByteArrayContent(data); requestMessage.Content = new ByteArrayContent(data);
return await this.Client.SendAsync(requestMessage); return await this.Client.SendAsync(requestMessage);

View file

@ -13,9 +13,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.11" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.11"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0"/>
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.1"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -27,7 +27,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\ProjectLighthouse\ProjectLighthouse.csproj" /> <ProjectReference Include="..\ProjectLighthouse\ProjectLighthouse.csproj"/>
<Content Include="ExampleFiles\**"> <Content Include="ExampleFiles\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>

View file

@ -5,10 +5,13 @@ using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Settings; using LBPUnion.ProjectLighthouse.Types.Settings;
using Xunit; using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests { namespace LBPUnion.ProjectLighthouse.Tests
public class AuthenticationTests : LighthouseTest { {
public class AuthenticationTests : LighthouseTest
{
[Fact] [Fact]
public async Task ShouldReturnErrorOnNoPostData() { public async Task ShouldReturnErrorOnNoPostData()
{
HttpResponseMessage response = await this.Client.PostAsync("/LITTLEBIGPLANETPS3_XML/login", null!); HttpResponseMessage response = await this.Client.PostAsync("/LITTLEBIGPLANETPS3_XML/login", null!);
Assert.False(response.IsSuccessStatusCode); Assert.False(response.IsSuccessStatusCode);
#if NET6_0_OR_GREATER #if NET6_0_OR_GREATER
@ -19,7 +22,8 @@ namespace LBPUnion.ProjectLighthouse.Tests {
} }
[DatabaseFact] [DatabaseFact]
public async Task ShouldReturnWithValidData() { public async Task ShouldReturnWithValidData()
{
HttpResponseMessage response = await this.AuthenticateResponse(); HttpResponseMessage response = await this.AuthenticateResponse();
Assert.True(response.IsSuccessStatusCode); Assert.True(response.IsSuccessStatusCode);
string responseContent = await response.Content.ReadAsStringAsync(); string responseContent = await response.Content.ReadAsStringAsync();
@ -28,9 +32,10 @@ namespace LBPUnion.ProjectLighthouse.Tests {
} }
[DatabaseFact] [DatabaseFact]
public async Task CanSerializeBack() { public async Task CanSerializeBack()
{
LoginResult loginResult = await this.Authenticate(); LoginResult loginResult = await this.Authenticate();
Assert.NotNull(loginResult); Assert.NotNull(loginResult);
Assert.NotNull(loginResult.AuthTicket); Assert.NotNull(loginResult.AuthTicket);
Assert.NotNull(loginResult.LbpEnvVer); Assert.NotNull(loginResult.LbpEnvVer);
@ -40,18 +45,20 @@ namespace LBPUnion.ProjectLighthouse.Tests {
} }
[DatabaseFact] [DatabaseFact]
public async Task CanUseToken() { public async Task CanUseToken()
{
LoginResult loginResult = await this.Authenticate(); LoginResult loginResult = await this.Authenticate();
HttpResponseMessage response = await this.AuthenticatedRequest("/LITTLEBIGPLANETPS3_XML/eula", loginResult.AuthTicket); HttpResponseMessage response = await this.AuthenticatedRequest("/LITTLEBIGPLANETPS3_XML/eula", loginResult.AuthTicket);
string responseContent = await response.Content.ReadAsStringAsync(); string responseContent = await response.Content.ReadAsStringAsync();
Assert.True(response.IsSuccessStatusCode); Assert.True(response.IsSuccessStatusCode);
Assert.Contains("You are logged in", responseContent); Assert.Contains("You are now logged in", responseContent);
} }
[DatabaseFact] [DatabaseFact]
public async Task ShouldReturnForbiddenWhenNotAuthenticated() { public async Task ShouldReturnForbiddenWhenNotAuthenticated()
{
HttpResponseMessage response = await this.Client.GetAsync("/LITTLEBIGPLANETPS3_XML/eula"); HttpResponseMessage response = await this.Client.GetAsync("/LITTLEBIGPLANETPS3_XML/eula");
Assert.False(response.IsSuccessStatusCode); Assert.False(response.IsSuccessStatusCode);
Assert.True(response.StatusCode == HttpStatusCode.Forbidden); Assert.True(response.StatusCode == HttpStatusCode.Forbidden);

View file

@ -1,10 +1,13 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace LBPUnion.ProjectLighthouse.Tests { namespace LBPUnion.ProjectLighthouse.Tests
public class DatabaseTests : LighthouseTest { {
public class DatabaseTests : LighthouseTest
{
[DatabaseFact] [DatabaseFact]
public async Task CanCreateUserTwice() { public async Task CanCreateUserTwice()
{
await using Database database = new(); await using Database database = new();
int rand = new Random().Next(); int rand = new Random().Next();

View file

@ -4,47 +4,56 @@ using System.Text;
using LBPUnion.ProjectLighthouse.Types.Files; using LBPUnion.ProjectLighthouse.Types.Files;
using Xunit; using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests { namespace LBPUnion.ProjectLighthouse.Tests
public class FileTypeTests { {
public class FileTypeTests
{
[Fact] [Fact]
public void ShouldRecognizeLevel() { public void ShouldRecognizeLevel()
{
LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestLevel.lvl")); LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestLevel.lvl"));
Assert.True(file.FileType == LbpFileType.Level); Assert.True(file.FileType == LbpFileType.Level);
} }
[Fact] [Fact]
public void ShouldRecognizeScript() { public void ShouldRecognizeScript()
{
LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestScript.ff")); LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestScript.ff"));
Assert.True(file.FileType == LbpFileType.Script); Assert.True(file.FileType == LbpFileType.Script);
} }
[Fact] [Fact]
public void ShouldRecognizeTexture() { public void ShouldRecognizeTexture()
{
LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestTexture.tex")); LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestTexture.tex"));
Assert.True(file.FileType == LbpFileType.Texture); Assert.True(file.FileType == LbpFileType.Texture);
} }
[Fact] [Fact]
public void ShouldRecognizeFileArchive() { public void ShouldRecognizeFileArchive()
{
LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestFarc.farc")); LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestFarc.farc"));
Assert.True(file.FileType == LbpFileType.FileArchive); Assert.True(file.FileType == LbpFileType.FileArchive);
} }
[Fact] [Fact]
public void ShouldNotRecognizeFileArchiveAsScript() { public void ShouldNotRecognizeFileArchiveAsScript()
{
LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestFarc.farc")); LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestFarc.farc"));
Assert.False(file.FileType == LbpFileType.Script); Assert.False(file.FileType == LbpFileType.Script);
Assert.True(file.FileType == LbpFileType.FileArchive); Assert.True(file.FileType == LbpFileType.FileArchive);
} }
[Fact] [Fact]
public void ShouldRecognizeNothingAsUnknown() { public void ShouldRecognizeNothingAsUnknown()
{
LbpFile file = new(Array.Empty<byte>()); LbpFile file = new(Array.Empty<byte>());
Assert.True(file.FileType == LbpFileType.Unknown); Assert.True(file.FileType == LbpFileType.Unknown);
} }
[Fact] [Fact]
public void ShouldRecognizeGarbageAsUnknown() { public void ShouldRecognizeGarbageAsUnknown()
{
LbpFile file = new(Encoding.ASCII.GetBytes("free pc only $900")); LbpFile file = new(Encoding.ASCII.GetBytes("free pc only $900"));
Assert.True(file.FileType == LbpFileType.Unknown); Assert.True(file.FileType == LbpFileType.Unknown);
} }

View file

@ -1,39 +1,59 @@
using System; using System;
using System.Net.Http; using System.Net.Http;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Xunit; using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests { namespace LBPUnion.ProjectLighthouse.Tests
public class MatchTests : LighthouseTest { {
public class MatchTests : LighthouseTest
{
private static readonly SemaphoreSlim semaphore = new(1, 1); private static readonly SemaphoreSlim semaphore = new(1, 1);
[DatabaseFact] [DatabaseFact]
public async Task ShouldReturnOk() { public async Task ShouldRejectEmptyData()
{
LoginResult loginResult = await this.Authenticate(); LoginResult loginResult = await this.Authenticate();
await semaphore.WaitAsync(); await semaphore.WaitAsync();
HttpResponseMessage result = await this.AuthenticatedUploadDataRequest("LITTLEBIGPLANETPS3_XML/match", Array.Empty<byte>(), loginResult.AuthTicket); HttpResponseMessage result = await this.AuthenticatedUploadDataRequest("LITTLEBIGPLANETPS3_XML/match", Array.Empty<byte>(), loginResult.AuthTicket);
Assert.True(result.IsSuccessStatusCode);
semaphore.Release(); semaphore.Release();
Assert.False(result.IsSuccessStatusCode);
}
[DatabaseFact]
public async Task ShouldReturnOk()
{
LoginResult loginResult = await this.Authenticate();
await semaphore.WaitAsync();
HttpResponseMessage result = await this.AuthenticatedUploadDataRequest
("LITTLEBIGPLANETPS3_XML/match", Encoding.ASCII.GetBytes("[UpdateMyPlayerData,[\"Player\":\"1984\"]]"), loginResult.AuthTicket);
semaphore.Release();
Assert.True(result.IsSuccessStatusCode);
} }
public async Task<int> GetPlayerCount() => Convert.ToInt32(await this.Client.GetStringAsync("LITTLEBIGPLANETPS3_XML/totalPlayerCount")); public async Task<int> GetPlayerCount() => Convert.ToInt32(await this.Client.GetStringAsync("LITTLEBIGPLANETPS3_XML/totalPlayerCount"));
[DatabaseFact] [DatabaseFact]
public async Task ShouldIncrementPlayerCount() { public async Task ShouldIncrementPlayerCount()
{
LoginResult loginResult = await this.Authenticate(new Random().Next()); LoginResult loginResult = await this.Authenticate(new Random().Next());
await semaphore.WaitAsync(); await semaphore.WaitAsync();
int oldPlayerCount = await this.GetPlayerCount(); int oldPlayerCount = await this.GetPlayerCount();
HttpResponseMessage result = await this.AuthenticatedUploadDataRequest("LITTLEBIGPLANETPS3_XML/match", Array.Empty<byte>(), loginResult.AuthTicket); HttpResponseMessage result = await this.AuthenticatedUploadDataRequest
("LITTLEBIGPLANETPS3_XML/match", Encoding.ASCII.GetBytes("[UpdateMyPlayerData,[\"Player\":\"1984\"]]"), loginResult.AuthTicket);
Assert.True(result.IsSuccessStatusCode); Assert.True(result.IsSuccessStatusCode);
int playerCount = await this.GetPlayerCount(); int playerCount = await this.GetPlayerCount();
semaphore.Release(); semaphore.Release();
Assert.Equal(oldPlayerCount + 1, playerCount); Assert.Equal(oldPlayerCount + 1, playerCount);
} }

View file

@ -2,30 +2,42 @@ using System.Collections.Generic;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using Xunit; using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests { namespace LBPUnion.ProjectLighthouse.Tests
public class SerializerTests : LighthouseTest { {
public class SerializerTests : LighthouseTest
{
[Fact] [Fact]
public void BlankElementWorks() { public void BlankElementWorks()
{
Assert.Equal("<test></test>", LbpSerializer.BlankElement("test")); Assert.Equal("<test></test>", LbpSerializer.BlankElement("test"));
} }
[Fact] [Fact]
public void StringElementWorks() { public void StringElementWorks()
{
Assert.Equal("<test>asd</test>", LbpSerializer.StringElement("test", "asd")); Assert.Equal("<test>asd</test>", LbpSerializer.StringElement("test", "asd"));
Assert.Equal("<test>asd</test>", LbpSerializer.StringElement(new KeyValuePair<string, object>("test", "asd"))); Assert.Equal("<test>asd</test>", LbpSerializer.StringElement(new KeyValuePair<string, object>("test", "asd")));
} }
[Fact] [Fact]
public void TaggedStringElementWorks() { public void TaggedStringElementWorks()
{
Assert.Equal("<test foo=\"bar\">asd</test>", LbpSerializer.TaggedStringElement("test", "asd", "foo", "bar")); Assert.Equal("<test foo=\"bar\">asd</test>", LbpSerializer.TaggedStringElement("test", "asd", "foo", "bar"));
Assert.Equal("<test foo=\"bar\">asd</test>", LbpSerializer.TaggedStringElement(new KeyValuePair<string, object>("test", "asd"), Assert.Equal
new KeyValuePair<string, object>("foo", "bar"))); (
"<test foo=\"bar\">asd</test>",
LbpSerializer.TaggedStringElement(new KeyValuePair<string, object>("test", "asd"), new KeyValuePair<string, object>("foo", "bar"))
);
} }
[Fact] [Fact]
public void ElementsWorks() { public void ElementsWorks()
Assert.Equal("<test>asd</test><foo>bar</foo>", LbpSerializer.Elements(new KeyValuePair<string, object>("test", "asd"), {
new KeyValuePair<string, object>("foo", "bar"))); Assert.Equal
(
"<test>asd</test><foo>bar</foo>",
LbpSerializer.Elements(new KeyValuePair<string, object>("test", "asd"), new KeyValuePair<string, object>("foo", "bar"))
);
} }
} }
} }

View file

@ -4,10 +4,13 @@ using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Profiles; using LBPUnion.ProjectLighthouse.Types.Profiles;
using Xunit; using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests { namespace LBPUnion.ProjectLighthouse.Tests
public class SlotTests : LighthouseTest { {
public class SlotTests : LighthouseTest
{
[DatabaseFact] [DatabaseFact]
public async Task ShouldOnlyShowUsersLevels() { public async Task ShouldOnlyShowUsersLevels()
{
await using Database database = new(); await using Database database = new();
User userA = await database.CreateUser("unitTestUser0"); User userA = await database.CreateUser("unitTestUser0");
@ -17,7 +20,8 @@ namespace LBPUnion.ProjectLighthouse.Tests {
database.Locations.Add(l); database.Locations.Add(l);
await database.SaveChangesAsync(); await database.SaveChangesAsync();
Slot slotA = new() { Slot slotA = new()
{
Creator = userA, Creator = userA,
Name = "slotA", Name = "slotA",
Location = l, Location = l,
@ -25,7 +29,8 @@ namespace LBPUnion.ProjectLighthouse.Tests {
ResourceCollection = "", ResourceCollection = "",
}; };
Slot slotB = new() { Slot slotB = new()
{
Creator = userB, Creator = userB,
Name = "slotB", Name = "slotB",
Location = l, Location = l,
@ -47,7 +52,7 @@ namespace LBPUnion.ProjectLighthouse.Tests {
Assert.NotEqual(respA, respB); Assert.NotEqual(respA, respB);
Assert.DoesNotContain(respA, "slotB"); Assert.DoesNotContain(respA, "slotB");
Assert.DoesNotContain(respB, "slotA"); Assert.DoesNotContain(respB, "slotA");
// Cleanup // Cleanup
database.Slots.Remove(slotA); database.Slots.Remove(slotA);

View file

@ -4,39 +4,47 @@ using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xunit; using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests { namespace LBPUnion.ProjectLighthouse.Tests
public class UploadTests : LighthouseTest { {
public UploadTests() { public class UploadTests : LighthouseTest
{
public UploadTests()
{
string assetsDirectory = Path.Combine(Environment.CurrentDirectory, "r"); string assetsDirectory = Path.Combine(Environment.CurrentDirectory, "r");
if(Directory.Exists(assetsDirectory)) Directory.Delete(assetsDirectory, true); if (Directory.Exists(assetsDirectory)) Directory.Delete(assetsDirectory, true);
} }
[Fact] [Fact]
public async Task ShouldNotAcceptScript() { public async Task ShouldNotAcceptScript()
{
HttpResponseMessage response = await this.UploadFileRequest("/LITTLEBIGPLANETPS3_XML/upload/scriptTest", "ExampleFiles/TestScript.ff"); HttpResponseMessage response = await this.UploadFileRequest("/LITTLEBIGPLANETPS3_XML/upload/scriptTest", "ExampleFiles/TestScript.ff");
Assert.False(response.IsSuccessStatusCode); Assert.False(response.IsSuccessStatusCode);
} }
[Fact] [Fact]
public async Task ShouldNotAcceptFarc() { public async Task ShouldNotAcceptFarc()
{
HttpResponseMessage response = await this.UploadFileRequest("/LITTLEBIGPLANETPS3_XML/upload/farcTest", "ExampleFiles/TestFarc.farc"); HttpResponseMessage response = await this.UploadFileRequest("/LITTLEBIGPLANETPS3_XML/upload/farcTest", "ExampleFiles/TestFarc.farc");
Assert.False(response.IsSuccessStatusCode); Assert.False(response.IsSuccessStatusCode);
} }
[Fact] [Fact]
public async Task ShouldNotAcceptGarbage() { public async Task ShouldNotAcceptGarbage()
{
HttpResponseMessage response = await this.UploadFileRequest("/LITTLEBIGPLANETPS3_XML/upload/garbageTest", "ExampleFiles/TestGarbage.bin"); HttpResponseMessage response = await this.UploadFileRequest("/LITTLEBIGPLANETPS3_XML/upload/garbageTest", "ExampleFiles/TestGarbage.bin");
Assert.False(response.IsSuccessStatusCode); Assert.False(response.IsSuccessStatusCode);
} }
[Fact] [Fact]
public async Task ShouldAcceptTexture() { public async Task ShouldAcceptTexture()
{
HttpResponseMessage response = await this.UploadFileRequest("/LITTLEBIGPLANETPS3_XML/upload/textureTest", "ExampleFiles/TestTexture.tex"); HttpResponseMessage response = await this.UploadFileRequest("/LITTLEBIGPLANETPS3_XML/upload/textureTest", "ExampleFiles/TestTexture.tex");
Assert.True(response.IsSuccessStatusCode); Assert.True(response.IsSuccessStatusCode);
} }
[Fact] [Fact]
public async Task ShouldAcceptLevel() { public async Task ShouldAcceptLevel()
{
HttpResponseMessage response = await this.UploadFileRequest("/LITTLEBIGPLANETPS3_XML/upload/levelTest", "ExampleFiles/TestLevel.lvl"); HttpResponseMessage response = await this.UploadFileRequest("/LITTLEBIGPLANETPS3_XML/upload/levelTest", "ExampleFiles/TestLevel.lvl");
Assert.True(response.IsSuccessStatusCode); Assert.True(response.IsSuccessStatusCode);
} }

View file

@ -1,8 +1,81 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeConstructorOrDestructorBody/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeLocalFunctionBody/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeMethodOrOperatorBody/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeObjectCreationWhenTypeNotEvident/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeTrailingCommaInMultilineLists/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeTrailingCommaInSinglelineLists/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FBuiltInTypes/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FSimpleTypes/@EntryIndexedValue">ERROR</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/LOCAL_FUNCTION_BODY/@EntryValue">ExpressionBody</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/METHOD_OR_OPERATOR_BODY/@EntryValue">ExpressionBody</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ThisQualifier/INSTANCE_MEMBERS_QUALIFY_MEMBERS/@EntryValue">Field, Property, Event, Method</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/TRAILING_COMMA_IN_MULTILINE_LISTS/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/TRAILING_COMMA_IN_SINGLELINE_LISTS/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">NEXT_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">TOGETHER</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_INSIDE_NAMESPACE/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_PREPROCESSOR_IF/@EntryValue">USUAL_INDENT</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_PREPROCESSOR_OTHER/@EntryValue">USUAL_INDENT</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">NEXT_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INVOCABLE_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_CODE/@EntryValue">1</s:Int64>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue">1</s:Int64>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_DECLARATION_PARENS_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_EMBEDDED_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_EXPR_MEMBER_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_INITIALIZER_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_PROPERTY_PATTERNS_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_SWITCH_EXPRESSION_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_USER_LINEBREAKS/@EntryValue">False</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_FORMAL_PARAMETERS_ON_LINE/@EntryValue">5</s:Int64>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_INITIALIZER_ELEMENTS_ON_LINE/@EntryValue">1</s:Int64>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/NESTED_TERNARY_STYLE/@EntryValue">EXPANDED</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/OTHER_BRACES/@EntryValue">NEXT_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ANONYMOUSMETHOD_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_CASE_STATEMENT_ON_SAME_LINE/@EntryValue">IF_OWNER_IS_SINGLE_LINE</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_INITIALIZER_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_PROPERTY_PATTERN_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_WHILE_ON_NEW_LINE/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AROUND_ARROW_OP/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_FOREACH_PARENTHESES/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_IF_PARENTHESES/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_SWITCH_PARENTHESES/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_USING_PARENTHESES/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_WHILE_PARENTHESES/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/TYPE_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_DECLARATION_LPAR/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_INVOCATION_LPAR/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARRAY_INITIALIZER_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_DECLARATION_LPAR/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_DECLARATION_RPAR/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_FIRST_TYPE_PARAMETER_CONSTRAINT/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_INVOCATION_LPAR/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_INVOCATION_RPAR/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_LINQ_EXPRESSION/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_CHAINED_BINARY_EXPRESSIONS/@EntryValue">CHOP_IF_LONG</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_CHAINED_METHOD_CALLS/@EntryValue">CHOP_IF_LONG</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_EXTENDS_LIST_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_OBJECT_AND_COLLECTION_INITIALIZER_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PARAMETERS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PROPERTY_PATTERN/@EntryValue">CHOP_ALWAYS</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MM/@EntryIndexedValue">MM</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MM/@EntryIndexedValue">MM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Method/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Method/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Affero/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Braaains/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Braaains/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=brun/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=brun/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ezoiar/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=ezoiar/@EntryIndexedValue">True</s:Boolean>
@ -11,6 +84,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=lbpme/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=lbpme/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=LBPU/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=LBPU/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=lolcatftw/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=lolcatftw/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mmpick/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Swingy/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Swingy/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=thumbsup/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=thumbsup/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unfavourite/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=unfavourite/@EntryIndexedValue">True</s:Boolean>

View file

@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using LBPUnion.ProjectLighthouse.Types.Settings; using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers
@ -13,24 +14,20 @@ namespace LBPUnion.ProjectLighthouse.Controllers
[SuppressMessage("ReSharper", "StringLiteralTypo")] [SuppressMessage("ReSharper", "StringLiteralTypo")]
public IActionResult NetworkSettings() public IActionResult NetworkSettings()
{ {
var hostname = this.Request.Host; HostString hostname = this.Request.Host;
return this.Ok( return this.Ok
"ProbabilityOfPacketDelay 0.0\nMinPacketDelayFrames 0\nMaxPacketDelayFrames 3\nProbabilityOfPacketDrop 0.0\nEnableFakeConditionsForLoopback true\nNumberOfFramesPredictionAllowedForNonLocalPlayer 1000\nEnablePrediction true\nMinPredictedFrames 0\nMaxPredictedFrames 10\nAllowGameRendCameraSplit true\nFramesBeforeAgressiveCatchup 30\nPredictionPadSides 200\nPredictionPadTop 200\nPredictionPadBottom 200\nShowErrorNumbers true\nAllowModeratedLevels true\nAllowModeratedPoppetItems true\nShowLevelBoos true\nTIMEOUT_WAIT_FOR_JOIN_RESPONSE_FROM_PREV_PARTY_HOST 50.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_HOST 30.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_MEMBER 45.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN_FRIEND 15.0\nTIMEOUT_WAIT_FOR_CONNECTION_FROM_HOST 30.0\nTIMEOUT_WAIT_FOR_ROOM_ID_TO_JOIN 60.0\nTIMEOUT_WAIT_FOR_GET_NUM_PLAYERS_ONLINE 60.0\nTIMEOUT_WAIT_FOR_SIGNALLING_CONNECTIONS 120.0\nTIMEOUT_WAIT_FOR_PARTY_DATA 60.0\nTIME_TO_WAIT_FOR_LEAVE_MESSAGE_TO_COME_BACK 20.0\nTIME_TO_WAIT_FOR_FOLLOWING_REQUESTS_TO_ARRIVE 30.0\nTIMEOUT_WAIT_FOR_FINISHED_MIGRATING_HOST 30.0\nTIMEOUT_WAIT_FOR_PARTY_LEADER_FINISH_JOINING 45.0\nTIMEOUT_WAIT_FOR_QUICKPLAY_LEVEL 60.0\nTIMEOUT_WAIT_FOR_PLAYERS_TO_JOIN 30.0\nTIMEOUT_WAIT_FOR_DIVE_IN_PLAYERS 120.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 30.0\nTIMEOUT_DIVE_IN_TOTAL 1000000.0\nTIMEOUT_WAIT_FOR_SOCKET_CONNECTION 120.0\nTIMEOUT_WAIT_FOR_REQUEST_RESOURCE_MESSAGE 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_GET_RESOURCE_LIST 120.0\nTIMEOUT_WAIT_FOR_CLIENT_TO_LOAD_RESOURCES 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_SAVE_GAME_STATE 30.0\nTIMEOUT_WAIT_FOR_ADD_PLAYERS_TO_TAKE 30.0\nTIMEOUT_WAIT_FOR_UPDATE_FROM_CLIENT 90.0\nTIMEOUT_WAIT_FOR_HOST_TO_GET_RESOURCE_LIST 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_SAVE_GAME_STATE 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_ADD_US 30.0\nTIMEOUT_WAIT_FOR_UPDATE 60.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN 50.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_PRESENCE 60.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_CONNECTION 120.0\nSECONDS_BETWEEN_PINS_AWARDED_UPLOADS 300.0\nEnableKeepAlive true\nAllowVoIPRecordingPlayback true\nCDNHostName localhost\nTelemetryServer localhost\nOverheatingThresholdDisallowMidgameJoin 0.95\nMaxCatchupFrames 3\nMaxLagBeforeShowLoading 23\nMinLagBeforeHideLoading 30\nLagImprovementInflectionPoint -1.0\nFlickerThreshold 2.0\nClosedDemo2014Version 1\nClosedDemo2014Expired false\nEnablePlayedFilter true\nEnableCommunityDecorations true\nGameStateUpdateRate 10.0\nGameStateUpdateRateWithConsumers 1.0\nDisableDLCPublishCheck false\nEnableDiveIn true\nEnableHackChecks false\n" (
+ $"TelemetryServer {hostname}\nCDNHostName {hostname}"); "ProbabilityOfPacketDelay 0.0\nMinPacketDelayFrames 0\nMaxPacketDelayFrames 3\nProbabilityOfPacketDrop 0.0\nEnableFakeConditionsForLoopback true\nNumberOfFramesPredictionAllowedForNonLocalPlayer 1000\nEnablePrediction true\nMinPredictedFrames 0\nMaxPredictedFrames 10\nAllowGameRendCameraSplit true\nFramesBeforeAgressiveCatchup 30\nPredictionPadSides 200\nPredictionPadTop 200\nPredictionPadBottom 200\nShowErrorNumbers true\nAllowModeratedLevels true\nAllowModeratedPoppetItems true\nShowLevelBoos true\nTIMEOUT_WAIT_FOR_JOIN_RESPONSE_FROM_PREV_PARTY_HOST 50.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_HOST 30.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_MEMBER 45.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN_FRIEND 15.0\nTIMEOUT_WAIT_FOR_CONNECTION_FROM_HOST 30.0\nTIMEOUT_WAIT_FOR_ROOM_ID_TO_JOIN 60.0\nTIMEOUT_WAIT_FOR_GET_NUM_PLAYERS_ONLINE 60.0\nTIMEOUT_WAIT_FOR_SIGNALLING_CONNECTIONS 120.0\nTIMEOUT_WAIT_FOR_PARTY_DATA 60.0\nTIME_TO_WAIT_FOR_LEAVE_MESSAGE_TO_COME_BACK 20.0\nTIME_TO_WAIT_FOR_FOLLOWING_REQUESTS_TO_ARRIVE 30.0\nTIMEOUT_WAIT_FOR_FINISHED_MIGRATING_HOST 30.0\nTIMEOUT_WAIT_FOR_PARTY_LEADER_FINISH_JOINING 45.0\nTIMEOUT_WAIT_FOR_QUICKPLAY_LEVEL 60.0\nTIMEOUT_WAIT_FOR_PLAYERS_TO_JOIN 30.0\nTIMEOUT_WAIT_FOR_DIVE_IN_PLAYERS 120.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 30.0\nTIMEOUT_DIVE_IN_TOTAL 1000000.0\nTIMEOUT_WAIT_FOR_SOCKET_CONNECTION 120.0\nTIMEOUT_WAIT_FOR_REQUEST_RESOURCE_MESSAGE 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_GET_RESOURCE_LIST 120.0\nTIMEOUT_WAIT_FOR_CLIENT_TO_LOAD_RESOURCES 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_SAVE_GAME_STATE 30.0\nTIMEOUT_WAIT_FOR_ADD_PLAYERS_TO_TAKE 30.0\nTIMEOUT_WAIT_FOR_UPDATE_FROM_CLIENT 90.0\nTIMEOUT_WAIT_FOR_HOST_TO_GET_RESOURCE_LIST 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_SAVE_GAME_STATE 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_ADD_US 30.0\nTIMEOUT_WAIT_FOR_UPDATE 60.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN 50.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_PRESENCE 60.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_CONNECTION 120.0\nSECONDS_BETWEEN_PINS_AWARDED_UPLOADS 300.0\nEnableKeepAlive true\nAllowVoIPRecordingPlayback true\nCDNHostName localhost\nTelemetryServer localhost\nOverheatingThresholdDisallowMidgameJoin 0.95\nMaxCatchupFrames 3\nMaxLagBeforeShowLoading 23\nMinLagBeforeHideLoading 30\nLagImprovementInflectionPoint -1.0\nFlickerThreshold 2.0\nClosedDemo2014Version 1\nClosedDemo2014Expired false\nEnablePlayedFilter true\nEnableCommunityDecorations true\nGameStateUpdateRate 10.0\nGameStateUpdateRateWithConsumers 1.0\nDisableDLCPublishCheck false\nEnableDiveIn true\nEnableHackChecks false\n" +
$"TelemetryServer {hostname}\nCDNHostName {hostname}"
);
} }
[HttpGet("t_conf")] [HttpGet("t_conf")]
[Produces("text/json")] [Produces("text/json")]
public IActionResult Conf() public IActionResult Conf() => this.Ok("[{\"StatusCode\":200}]");
{
return this.Ok("[{\"StatusCode\":200}]");
}
[HttpGet("farc_hashes")] [HttpGet("farc_hashes")]
public IActionResult FarcHashes() public IActionResult FarcHashes() => this.Ok();
{
return this.Ok();
}
[HttpGet("privacySettings")] [HttpGet("privacySettings")]
[Produces("text/xml")] [Produces("text/xml")]

View file

@ -3,28 +3,34 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Serialization; using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Profiles; using LBPUnion.ProjectLighthouse.Types.Profiles;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers { namespace LBPUnion.ProjectLighthouse.Controllers
{
[ApiController] [ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class CommentController : ControllerBase { public class CommentController : ControllerBase
{
private readonly Database database; private readonly Database database;
public CommentController(Database database) { public CommentController(Database database)
{
this.database = database; this.database = database;
} }
[HttpGet("userComments/{username}")] [HttpGet("userComments/{username}")]
public async Task<IActionResult> GetComments(string username) { public async Task<IActionResult> GetComments(string username)
List<Comment> comments = await this.database.Comments {
.Include(c => c.Target) List<Comment> comments = await this.database.Comments.Include
(c => c.Target)
.Include(c => c.Poster) .Include(c => c.Poster)
.Where(c => c.Target.Username == username) .Where(c => c.Target.Username == username)
.OrderByDescending(c => c.Timestamp)
.ToListAsync(); .ToListAsync();
string outputXml = comments.Aggregate(string.Empty, (current, comment) => current + comment.Serialize()); string outputXml = comments.Aggregate(string.Empty, (current, comment) => current + comment.Serialize());
@ -32,7 +38,8 @@ namespace LBPUnion.ProjectLighthouse.Controllers {
} }
[HttpPost("postUserComment/{username}")] [HttpPost("postUserComment/{username}")]
public async Task<IActionResult> PostComment(string username) { public async Task<IActionResult> PostComment(string username)
{
this.Request.Body.Position = 0; this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
@ -41,18 +48,36 @@ namespace LBPUnion.ProjectLighthouse.Controllers {
User poster = await this.database.UserFromRequest(this.Request); User poster = await this.database.UserFromRequest(this.Request);
if(poster == null) return this.StatusCode(403, ""); if (poster == null) return this.StatusCode(403, "");
User target = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); User target = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if(comment == null || target == null) return this.BadRequest(); if (comment == null || target == null) return this.BadRequest();
comment.PosterUserId = poster.UserId; comment.PosterUserId = poster.UserId;
comment.TargetUserId = target.UserId; comment.TargetUserId = target.UserId;
comment.Timestamp = TimeHelper.UnixTimeMilliseconds();
this.database.Comments.Add(comment); this.database.Comments.Add(comment);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
return this.Ok(); return this.Ok();
} }
[HttpPost("deleteUserComment/{username}")]
public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string username)
{
User user = await this.database.UserFromRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
Comment comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId);
if (comment.TargetUserId != user.UserId && comment.PosterUserId != user.UserId) return this.StatusCode(403, "");
this.database.Comments.Remove(comment);
await this.database.SaveChangesAsync();
return this.Ok();
}
} }
} }

View file

@ -1,5 +1,4 @@
using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers
{ {
@ -8,9 +7,6 @@ namespace LBPUnion.ProjectLighthouse.Controllers
public class DeveloperController : Controller public class DeveloperController : Controller
{ {
[HttpGet("/developer_videos")] [HttpGet("/developer_videos")]
public IActionResult DeveloperVideos() public IActionResult DeveloperVideos() => this.Ok();
{
return this.Ok();
}
} }
} }

View file

@ -1,13 +1,13 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Controllers { namespace LBPUnion.ProjectLighthouse.Controllers
{
[ApiController] [ApiController]
[Route("LITTLEBIGPLANETPS3_XML/enterLevel")] [Route("LITTLEBIGPLANETPS3_XML/enterLevel")]
// [Produces("text/plain")] // [Produces("text/plain")]
public class EnterLevelController : ControllerBase { public class EnterLevelController : ControllerBase
{
[HttpGet("enterLevel/{id}")] [HttpGet("enterLevel/{id}")]
public IActionResult EnterLevel(string id) { public IActionResult EnterLevel(string id) => this.Ok();
return this.Ok();
}
} }
} }

View file

@ -2,17 +2,21 @@ using System;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Controllers { namespace LBPUnion.ProjectLighthouse.Controllers
{
[ApiController] [ApiController]
[Route("LITTLEBIGPLANETPS3_XML/tags")] [Route("LITTLEBIGPLANETPS3_XML/tags")]
[Produces("text/plain")] [Produces("text/plain")]
public class LevelTagsController : ControllerBase { public class LevelTagsController : ControllerBase
{
[HttpGet] [HttpGet]
public IActionResult Get() { public IActionResult Get()
{
string[] tags = Enum.GetNames(typeof(LevelTags)); string[] tags = Enum.GetNames(typeof(LevelTags));
int i = 0; int i = 0;
foreach(string tag in tags) { foreach (string tag in tags)
{
tags[i] = $"TAG_{tag.Replace("_", "-")}"; tags[i] = $"TAG_{tag.Replace("_", "-")}";
i++; i++;
} }

View file

@ -8,23 +8,28 @@ using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers { namespace LBPUnion.ProjectLighthouse.Controllers
{
[ApiController] [ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class ListController : ControllerBase { public class ListController : ControllerBase
{
private readonly Database database; private readonly Database database;
public ListController(Database database) { public ListController(Database database)
{
this.database = database; this.database = database;
} }
#region Levels #region Levels
#region Level Queue (lolcatftw) #region Level Queue (lolcatftw)
[HttpGet("slots/lolcatftw/{username}")] [HttpGet("slots/lolcatftw/{username}")]
public IActionResult GetLevelQueue(string username) { public IActionResult GetLevelQueue(string username)
IEnumerable<QueuedLevel> queuedLevels = new Database().QueuedLevels {
.Include(q => q.User) IEnumerable<QueuedLevel> queuedLevels = new Database().QueuedLevels.Include
(q => q.User)
.Include(q => q.Slot) .Include(q => q.Slot)
.Include(q => q.Slot.Location) .Include(q => q.Slot.Location)
.Where(q => q.User.Username == username) .Where(q => q.User.Username == username)
@ -36,30 +41,36 @@ namespace LBPUnion.ProjectLighthouse.Controllers {
} }
[HttpPost("lolcatftw/add/user/{id:int}")] [HttpPost("lolcatftw/add/user/{id:int}")]
public async Task<IActionResult> AddQueuedLevel(int id) { public async Task<IActionResult> AddQueuedLevel(int id)
{
User? user = await this.database.UserFromRequest(this.Request); User? user = await this.database.UserFromRequest(this.Request);
if(user == null) return this.StatusCode(403, ""); if (user == null) return this.StatusCode(403, "");
QueuedLevel queuedLevel = await this.database.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == id); QueuedLevel queuedLevel = await this.database.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == id);
if(queuedLevel != null) return this.Ok(); if (queuedLevel != null) return this.Ok();
this.database.QueuedLevels.Add(new QueuedLevel { this.database.QueuedLevels.Add
SlotId = id, (
UserId = user.UserId, new QueuedLevel
}); {
SlotId = id,
UserId = user.UserId,
}
);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
return this.Ok(); return this.Ok();
} }
[HttpPost("lolcatftw/remove/user/{id:int}")] [HttpPost("lolcatftw/remove/user/{id:int}")]
public async Task<IActionResult> RemoveQueuedLevel(int id) { public async Task<IActionResult> RemoveQueuedLevel(int id)
{
User? user = await this.database.UserFromRequest(this.Request); User? user = await this.database.UserFromRequest(this.Request);
if(user == null) return this.StatusCode(403, ""); if (user == null) return this.StatusCode(403, "");
QueuedLevel queuedLevel = await this.database.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == id); QueuedLevel queuedLevel = await this.database.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == id);
if(queuedLevel != null) this.database.QueuedLevels.Remove(queuedLevel); if (queuedLevel != null) this.database.QueuedLevels.Remove(queuedLevel);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
@ -71,9 +82,10 @@ namespace LBPUnion.ProjectLighthouse.Controllers {
#region Hearted Levels #region Hearted Levels
[HttpGet("favouriteSlots/{username}")] [HttpGet("favouriteSlots/{username}")]
public IActionResult GetFavouriteSlots(string username) { public IActionResult GetFavouriteSlots(string username)
IEnumerable<HeartedLevel> heartedLevels = new Database().HeartedLevels {
.Include(q => q.User) IEnumerable<HeartedLevel> heartedLevels = new Database().HeartedLevels.Include
(q => q.User)
.Include(q => q.Slot) .Include(q => q.Slot)
.Include(q => q.Slot.Location) .Include(q => q.Slot.Location)
.Include(q => q.Slot.Creator) .Include(q => q.Slot.Creator)
@ -86,17 +98,22 @@ namespace LBPUnion.ProjectLighthouse.Controllers {
} }
[HttpPost("favourite/slot/user/{id:int}")] [HttpPost("favourite/slot/user/{id:int}")]
public async Task<IActionResult> AddFavouriteSlot(int id) { public async Task<IActionResult> AddFavouriteSlot(int id)
{
User? user = await this.database.UserFromRequest(this.Request); User? user = await this.database.UserFromRequest(this.Request);
if(user == null) return this.StatusCode(403, ""); if (user == null) return this.StatusCode(403, "");
HeartedLevel heartedLevel = await this.database.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == id); HeartedLevel heartedLevel = await this.database.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == id);
if(heartedLevel != null) return this.Ok(); if (heartedLevel != null) return this.Ok();
this.database.HeartedLevels.Add(new HeartedLevel { this.database.HeartedLevels.Add
SlotId = id, (
UserId = user.UserId, new HeartedLevel
}); {
SlotId = id,
UserId = user.UserId,
}
);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
@ -104,12 +121,13 @@ namespace LBPUnion.ProjectLighthouse.Controllers {
} }
[HttpPost("unfavourite/slot/user/{id:int}")] [HttpPost("unfavourite/slot/user/{id:int}")]
public async Task<IActionResult> RemoveFavouriteSlot(int id) { public async Task<IActionResult> RemoveFavouriteSlot(int id)
{
User? user = await this.database.UserFromRequest(this.Request); User? user = await this.database.UserFromRequest(this.Request);
if(user == null) return this.StatusCode(403, ""); if (user == null) return this.StatusCode(403, "");
HeartedLevel heartedLevel = await this.database.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == id); HeartedLevel heartedLevel = await this.database.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == id);
if(heartedLevel != null) this.database.HeartedLevels.Remove(heartedLevel); if (heartedLevel != null) this.database.HeartedLevels.Remove(heartedLevel);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
@ -117,16 +135,16 @@ namespace LBPUnion.ProjectLighthouse.Controllers {
} }
#endregion #endregion
#endregion Levels #endregion Levels
#region Users #region Users
[HttpGet("favouriteUsers/{username}")] [HttpGet("favouriteUsers/{username}")]
public IActionResult GetFavouriteUsers(string username) { public IActionResult GetFavouriteUsers(string username)
IEnumerable<HeartedProfile> heartedProfiles = new Database().HeartedProfiles {
.Include(q => q.User) IEnumerable<HeartedProfile> heartedProfiles = new Database().HeartedProfiles.Include
(q => q.User)
.Include(q => q.HeartedUser) .Include(q => q.HeartedUser)
.Include(q => q.HeartedUser.Location) .Include(q => q.HeartedUser.Location)
.Where(q => q.User.Username == username) .Where(q => q.User.Username == username)
@ -136,42 +154,46 @@ namespace LBPUnion.ProjectLighthouse.Controllers {
return this.Ok(LbpSerializer.TaggedStringElement("favouriteUsers", response, "total", 1)); return this.Ok(LbpSerializer.TaggedStringElement("favouriteUsers", response, "total", 1));
} }
[HttpPost("favourite/user/{username}")] [HttpPost("favourite/user/{username}")]
public async Task<IActionResult> AddFavouriteUser(string username) { public async Task<IActionResult> AddFavouriteUser(string username)
{
User? user = await this.database.UserFromRequest(this.Request); User? user = await this.database.UserFromRequest(this.Request);
if(user == null) return this.StatusCode(403, ""); if (user == null) return this.StatusCode(403, "");
User? heartedUser = await this.database.Users User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
.FirstOrDefaultAsync(u => u.Username == username); if (heartedUser == null) return this.NotFound();
if(heartedUser == null) return this.NotFound();
HeartedProfile heartedProfile = await this.database.HeartedProfiles HeartedProfile heartedProfile = await this.database.HeartedProfiles.FirstOrDefaultAsync
.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId); (q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId);
if(heartedProfile != null) return this.Ok(); if (heartedProfile != null) return this.Ok();
this.database.HeartedProfiles.Add(new HeartedProfile { this.database.HeartedProfiles.Add
HeartedUserId = heartedUser.UserId, (
UserId = user.UserId, new HeartedProfile
}); {
HeartedUserId = heartedUser.UserId,
UserId = user.UserId,
}
);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
return this.Ok(); return this.Ok();
} }
[HttpPost("unfavourite/user/{username}")] [HttpPost("unfavourite/user/{username}")]
public async Task<IActionResult> RemoveFavouriteUser(string username) { public async Task<IActionResult> RemoveFavouriteUser(string username)
{
User? user = await this.database.UserFromRequest(this.Request); User? user = await this.database.UserFromRequest(this.Request);
if(user == null) return this.StatusCode(403, ""); if (user == null) return this.StatusCode(403, "");
User? heartedUser = await this.database.Users User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
.FirstOrDefaultAsync(u => u.Username == username); if (heartedUser == null) return this.NotFound();
if(heartedUser == null) return this.NotFound();
HeartedProfile heartedProfile = await this.database.HeartedProfiles HeartedProfile heartedProfile = await this.database.HeartedProfiles.FirstOrDefaultAsync
.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId); (q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId);
if(heartedProfile != null) this.database.HeartedProfiles.Remove(heartedProfile); if (heartedProfile != null) this.database.HeartedProfiles.Remove(heartedProfile);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
@ -179,5 +201,6 @@ namespace LBPUnion.ProjectLighthouse.Controllers {
} }
#endregion #endregion
} }
} }

View file

@ -24,25 +24,30 @@ namespace LBPUnion.ProjectLighthouse.Controllers
{ {
string body = await new StreamReader(this.Request.Body).ReadToEndAsync(); string body = await new StreamReader(this.Request.Body).ReadToEndAsync();
LoginData loginData; LoginData? loginData;
try try
{ {
loginData = LoginData.CreateFromString(body); loginData = LoginData.CreateFromString(body);
} }
catch catch
{ {
return this.BadRequest(); loginData = null;
} }
if (loginData == null) return this.BadRequest();
Token? token = await this.database.AuthenticateUser(loginData); Token? token = await this.database.AuthenticateUser(loginData);
if (token == null) return this.StatusCode(403, ""); if (token == null) return this.StatusCode(403, "");
return this.Ok(new LoginResult return this.Ok
{ (
AuthTicket = "MM_AUTH=" + token.UserToken, new LoginResult
LbpEnvVer = ServerSettings.ServerName, {
}.Serialize()); AuthTicket = "MM_AUTH=" + token.UserToken,
LbpEnvVer = ServerSettings.ServerName,
}.Serialize()
);
} }
} }
} }

View file

@ -40,10 +40,10 @@ namespace LBPUnion.ProjectLighthouse.Controllers
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
if (bodyString.Contains("FindBestRoom")) if (bodyString.Contains("FindBestRoom"))
{ return this.Ok
return this.Ok( (
"[{\"StatusCode\":200},{\"Players\":[{\"PlayerId\":\"literally1984\",\"matching_res\":0},{\"PlayerId\":\"jvyden\",\"matching_res\":1}],\"Slots\":[[5,0]],\"RoomState\":\"E_ROOM_IN_POD\",\"HostMood\":\"E_MOOD_EVERYONE\",\"LevelCompletionEstimate\":0,\"PassedNoJoinPoint\":0,\"MoveConnected\":false,\"Location\":[\"127.0.0.1\"],\"BuildVersion\":289,\"Language\":1,\"FirstSeenTimestamp\":1427331263756,\"LastSeenTimestamp\":1635112546000,\"GameId\":1,\"NatType\":2,\"Friends\":[],\"Blocked\":[],\"RecentlyLeft\":[],\"FailedJoin\":[]}]"); "[{\"StatusCode\":200},{\"Players\":[{\"PlayerId\":\"literally1984\",\"matching_res\":0},{\"PlayerId\":\"jvyden\",\"matching_res\":1}],\"Slots\":[[5,0]],\"RoomState\":\"E_ROOM_IN_POD\",\"HostMood\":\"E_MOOD_EVERYONE\",\"LevelCompletionEstimate\":0,\"PassedNoJoinPoint\":0,\"MoveConnected\":false,\"Location\":[\"127.0.0.1\"],\"BuildVersion\":289,\"Language\":1,\"FirstSeenTimestamp\":1427331263756,\"LastSeenTimestamp\":1635112546000,\"GameId\":1,\"NatType\":2,\"Friends\":[],\"Blocked\":[],\"RecentlyLeft\":[],\"FailedJoin\":[]}]"
} );
if (bodyString[0] != '[') return this.BadRequest(); if (bodyString[0] != '[') return this.BadRequest();
@ -52,7 +52,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers
{ {
matchData = MatchHelper.Deserialize(bodyString); matchData = MatchHelper.Deserialize(bodyString);
} }
catch (Exception e) catch(Exception e)
{ {
Logger.Log("Exception while parsing MatchData: " + e); Logger.Log("Exception while parsing MatchData: " + e);
Logger.Log("Data: " + bodyString); Logger.Log("Data: " + bodyString);
@ -66,8 +66,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers
#region Update LastMatch #region Update LastMatch
LastMatch? lastMatch = await this.database.LastMatches LastMatch? lastMatch = await this.database.LastMatches.Where(l => l.UserId == user.UserId).FirstOrDefaultAsync();
.Where(l => l.UserId == user.UserId).FirstOrDefaultAsync();
// below makes it not look like trash // below makes it not look like trash
// ReSharper disable once ConvertIfStatementToNullCoalescingExpression // ReSharper disable once ConvertIfStatementToNullCoalescingExpression
@ -88,16 +87,5 @@ namespace LBPUnion.ProjectLighthouse.Controllers
return this.Ok("[{\"StatusCode\":200}]"); return this.Ok("[{\"StatusCode\":200}]");
} }
[HttpGet("playersInPodCount")]
[HttpGet("totalPlayerCount")]
public async Task<IActionResult> TotalPlayerCount()
{
int recentMatches = await this.database.LastMatches
.Where(l => TimestampHelper.Timestamp - l.Timestamp < 60)
.CountAsync();
return this.Ok(recentMatches.ToString());
}
} }
} }

View file

@ -1,5 +1,8 @@
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kettu;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -22,34 +25,36 @@ namespace LBPUnion.ProjectLighthouse.Controllers
{ {
User user = await this.database.UserFromRequest(this.Request); User user = await this.database.UserFromRequest(this.Request);
return user == null return user == null
? this.Forbid() ? this.StatusCode(403, "")
: this.Ok($"You are now logged in as user {user.Username} (id {user.UserId}).\n" + : this.Ok
"This is a private testing instance. Please do not make anything public for now, and keep in mind security isn't as tight as a full release would."); (
$"You are now logged in as user {user.Username} (id {user.UserId}).\n" +
// ReSharper disable once UnreachableCode
(EulaHelper.ShowPrivateInstanceNotice ? "\n" + EulaHelper.PrivateInstanceNotice : "") +
"\n" +
$"{EulaHelper.License}\n"
);
} }
[HttpGet("announce")] [HttpGet("announce")]
public async Task<IActionResult> Announce() public IActionResult Announce() => this.Ok("");
{
User user = await this.database.UserFromRequest(this.Request);
return user == null
? this.Forbid()
: this.Ok($"You are now logged in as user {user.Username} (id {user.UserId}).\n" +
"This is a private testing instance. Please do not make anything public for now, and keep in mind security isn't as tight as a full release would.");
}
[HttpGet("notification")] [HttpGet("notification")]
public IActionResult Notification() public IActionResult Notification() => this.Ok();
{
return this.Ok();
}
/// <summary> /// <summary>
/// Filters chat messages sent by a user. /// Filters chat messages sent by a user.
/// The reponse sent is the text that will appear in-game.
/// </summary> /// </summary>
[HttpPost("filter")] [HttpPost("filter")]
public async Task<IActionResult> Filter() public async Task<IActionResult> Filter()
{ {
return this.Ok(await new StreamReader(this.Request.Body).ReadToEndAsync()); User user = await this.database.UserFromRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
string loggedText = await new StreamReader(this.Request.Body).ReadToEndAsync();
Logger.Log($"{user.Username}: {loggedText}", LoggerLevelFilter.Instance);
return this.Ok(loggedText);
} }
} }
} }

View file

@ -1,4 +1,3 @@
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types.News; using LBPUnion.ProjectLighthouse.Types.News;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -13,20 +12,24 @@ namespace LBPUnion.ProjectLighthouse.Controllers
[HttpGet] [HttpGet]
public IActionResult Get() public IActionResult Get()
{ {
string newsEntry = LbpSerializer.StringElement("item", new NewsEntry string newsEntry = LbpSerializer.StringElement
{ (
Category = "no_category", "item",
Summary = "test summary", new NewsEntry
Image = new NewsImage
{ {
Hash = "4947269c5f7061b27225611ee58a9a91a8031bbe", Category = "no_category",
Alignment = "right", Summary = "test summary",
}, Image = new NewsImage
Id = 1, {
Title = "Test Title", Hash = "4947269c5f7061b27225611ee58a9a91a8031bbe",
Text = "Test Text", Alignment = "right",
Date = 1348755214000, },
}.Serialize()); Id = 1,
Title = "Test Title",
Text = "Test Text",
Date = 1348755214000,
}.Serialize()
);
return this.Ok(LbpSerializer.StringElement("news", newsEntry)); return this.Ok(LbpSerializer.StringElement("news", newsEntry));
} }

View file

@ -10,64 +10,102 @@ using LBPUnion.ProjectLighthouse.Types.Profiles;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers { namespace LBPUnion.ProjectLighthouse.Controllers
{
[ApiController] [ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class PublishController : ControllerBase { public class PublishController : ControllerBase
{
private readonly Database database; private readonly Database database;
public PublishController(Database database) { public PublishController(Database database)
{
this.database = database; this.database = database;
} }
/// <summary> /// <summary>
/// Endpoint the game uses to verify that the level is compatible (?) /// Endpoint the game uses to check what resources need to be uploaded and if the level can be uploaded
/// </summary> /// </summary>
[HttpPost("startPublish")] [HttpPost("startPublish")]
public async Task<IActionResult> StartPublish() { public async Task<IActionResult> StartPublish()
Slot slot = await this.GetSlotFromBody(); {
if(slot == null) return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded User user = await this.database.UserFromRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
string resources = slot.Resources Slot slot = await this.GetSlotFromBody();
.Where(hash => !FileHelper.ResourceExists(hash)) if (slot == null) return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded
.Aggregate("", (current, hash) =>
current + LbpSerializer.StringElement("resource", hash)); // Republish logic
if (slot.SlotId != 0)
{
Slot oldSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slot.SlotId);
if (oldSlot == null) return this.NotFound();
if (oldSlot.CreatorId != user.UserId) return this.BadRequest();
}
string resources = slot.Resources.Where
(hash => !FileHelper.ResourceExists(hash))
.Aggregate("", (current, hash) => current + LbpSerializer.StringElement("resource", hash));
return this.Ok(LbpSerializer.TaggedStringElement("slot", resources, "type", "user")); return this.Ok(LbpSerializer.TaggedStringElement("slot", resources, "type", "user"));
} }
/// <summary> /// <summary>
/// Endpoint actually used to publish a level /// Endpoint actually used to publish a level
/// </summary> /// </summary>
[HttpPost("publish")] [HttpPost("publish")]
public async Task<IActionResult> Publish() { public async Task<IActionResult> Publish()
{
User user = await this.database.UserFromRequest(this.Request); User user = await this.database.UserFromRequest(this.Request);
if(user == null) return this.StatusCode(403, ""); if (user == null) return this.StatusCode(403, "");
Slot slot = await this.GetSlotFromBody(); Slot slot = await this.GetSlotFromBody();
// Republish logic
if (slot.SlotId != 0)
{
Slot oldSlot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slot.SlotId);
if (oldSlot == null) return this.NotFound();
if (oldSlot.CreatorId != user.UserId) return this.BadRequest();
oldSlot.Location.X = slot.Location.X;
oldSlot.Location.Y = slot.Location.Y;
slot.CreatorId = oldSlot.CreatorId;
slot.LocationId = oldSlot.LocationId;
slot.SlotId = oldSlot.SlotId;
slot.FirstUploaded = oldSlot.FirstUploaded;
slot.LastUpdated = TimeHelper.UnixTimeMilliseconds();
this.database.Entry(oldSlot).CurrentValues.SetValues(slot);
await this.database.SaveChangesAsync();
return this.Ok(oldSlot.Serialize());
}
//TODO: parse location in body //TODO: parse location in body
Location l = new() { Location l = new()
X = 0, {
Y = 0, X = slot.Location.X,
Y = slot.Location.Y,
}; };
this.database.Locations.Add(l); this.database.Locations.Add(l);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
slot.LocationId = l.Id; slot.LocationId = l.Id;
slot.CreatorId = user.UserId; slot.CreatorId = user.UserId;
slot.FirstUploaded = TimeHelper.UnixTimeMilliseconds();
slot.LastUpdated = TimeHelper.UnixTimeMilliseconds();
this.database.Slots.Add(slot); this.database.Slots.Add(slot);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
return this.Ok(slot.Serialize()); return this.Ok(slot.Serialize());
} }
[HttpPost("unpublish/{id:int}")] [HttpPost("unpublish/{id:int}")]
public async Task<IActionResult> Unpublish(int id) { public async Task<IActionResult> Unpublish(int id)
Slot slot = await this.database.Slots {
.Include(s => s.Location) Slot slot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == id);
.FirstOrDefaultAsync(s => s.SlotId == id);
this.database.Locations.Remove(slot.Location); this.database.Locations.Remove(slot.Location);
this.database.Slots.Remove(slot); this.database.Slots.Remove(slot);
@ -76,8 +114,9 @@ namespace LBPUnion.ProjectLighthouse.Controllers {
return this.Ok(); return this.Ok();
} }
public async Task<Slot> GetSlotFromBody() { public async Task<Slot> GetSlotFromBody()
{
this.Request.Body.Position = 0; this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();

View file

@ -18,10 +18,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers
public class ResourcesController : ControllerBase public class ResourcesController : ControllerBase
{ {
[HttpPost("showModerated")] [HttpPost("showModerated")]
public IActionResult ShowModerated() public IActionResult ShowModerated() => this.Ok(LbpSerializer.BlankElement("resources"));
{
return this.Ok(LbpSerializer.BlankElement("resources"));
}
[HttpPost("filterResources")] [HttpPost("filterResources")]
[HttpPost("showNotUploaded")] [HttpPost("showNotUploaded")]
@ -30,14 +27,13 @@ namespace LBPUnion.ProjectLighthouse.Controllers
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(ResourceList)); XmlSerializer serializer = new(typeof(ResourceList));
ResourceList resourceList = (ResourceList) serializer.Deserialize(new StringReader(bodyString)); ResourceList resourceList = (ResourceList)serializer.Deserialize(new StringReader(bodyString));
if (resourceList == null) return this.BadRequest(); if (resourceList == null) return this.BadRequest();
string resources = resourceList.Resources string resources = resourceList.Resources.Where
.Where(s => !FileHelper.ResourceExists(s)) (s => !FileHelper.ResourceExists(s))
.Aggregate("", (current, hash) => .Aggregate("", (current, hash) => current + LbpSerializer.StringElement("resource", hash));
current + LbpSerializer.StringElement("resource", hash));
return this.Ok(LbpSerializer.StringElement("resources", resources)); return this.Ok(LbpSerializer.StringElement("resources", resources));
} }
@ -47,10 +43,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers
{ {
string path = FileHelper.GetResourcePath(hash); string path = FileHelper.GetResourcePath(hash);
if (FileHelper.ResourceExists(hash)) if (FileHelper.ResourceExists(hash)) return this.File(IOFile.OpenRead(path), "application/octet-stream");
{
return this.File(IOFile.OpenRead(path), "application/octet-stream");
}
return this.NotFound(); return this.NotFound();
} }
@ -68,7 +61,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers
if (FileHelper.ResourceExists(hash)) this.Ok(); // no reason to fail if it's already uploaded if (FileHelper.ResourceExists(hash)) this.Ok(); // no reason to fail if it's already uploaded
Logger.Log($"Processing resource upload (hash: {hash})"); Logger.Log($"Processing resource upload (hash: {hash})");
LbpFile file = new(await BinaryHelper.ReadFromPipeReader(Request.BodyReader)); LbpFile file = new(await BinaryHelper.ReadFromPipeReader(this.Request.BodyReader));
if (!FileHelper.IsFileSafe(file)) return this.UnprocessableEntity(); if (!FileHelper.IsFileSafe(file)) return this.UnprocessableEntity();

View file

@ -6,18 +6,24 @@ using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers { namespace LBPUnion.ProjectLighthouse.Controllers
{
[ApiController] [ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class SearchController : ControllerBase { public class SearchController : ControllerBase
{
private readonly Database database; private readonly Database database;
public SearchController(Database database) { public SearchController(Database database)
{
this.database = database; this.database = database;
} }
[HttpGet("slots/search")] [HttpGet("slots/search")]
public async Task<IActionResult> SearchSlots([FromQuery] string query) { public async Task<IActionResult> SearchSlots([FromQuery] string query)
{
if (query == null) return this.BadRequest();
query = query.ToLower(); query = query.ToLower();
string[] keywords = query.Split(" "); string[] keywords = query.Split(" ");
@ -26,16 +32,16 @@ namespace LBPUnion.ProjectLighthouse.Controllers {
.Include(s => s.Creator) .Include(s => s.Creator)
.Include(s => s.Location) .Include(s => s.Location)
.Where(s => s.SlotId >= 0); // dumb query to conv into IQueryable .Where(s => s.SlotId >= 0); // dumb query to conv into IQueryable
// ReSharper disable once LoopCanBeConvertedToQuery // ReSharper disable once LoopCanBeConvertedToQuery
foreach(string keyword in keywords) { foreach (string keyword in keywords)
dbQuery = dbQuery.Where(s => dbQuery = dbQuery.Where
s.Name.ToLower().Contains(keyword) || (
s.Description.ToLower().Contains(keyword) || s => s.Name.ToLower().Contains(keyword) ||
s.Creator.Username.ToLower().Contains(keyword) || s.Description.ToLower().Contains(keyword) ||
s.SlotId.ToString().Equals(keyword) s.Creator.Username.ToLower().Contains(keyword) ||
s.SlotId.ToString().Equals(keyword)
); );
}
List<Slot> slots = await dbQuery.ToListAsync(); List<Slot> slots = await dbQuery.ToListAsync();
string response = slots.Aggregate("", (current, slot) => current + slot.Serialize()); string response = slots.Aggregate("", (current, slot) => current + slot.Serialize());

View file

@ -1,3 +1,4 @@
using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
@ -5,35 +6,54 @@ using LBPUnion.ProjectLighthouse.Types.Levels;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers { namespace LBPUnion.ProjectLighthouse.Controllers
{
[ApiController] [ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class SlotsController : ControllerBase { public class SlotsController : ControllerBase
{
private readonly Database database; private readonly Database database;
public SlotsController(Database database) { public SlotsController(Database database)
{
this.database = database; this.database = database;
} }
[HttpGet("slots/by")] [HttpGet("slots/by")]
public IActionResult SlotsBy([FromQuery] string u) { public IActionResult SlotsBy([FromQuery] string u)
string response = Enumerable.Aggregate( {
this.database.Slots string response = Enumerable.Aggregate
.Include(s => s.Creator) (
.Include(s => s.Location) this.database.Slots.Include(s => s.Creator).Include(s => s.Location).Where(s => s.Creator.Username == u),
.Where(s => s.Creator.Username == u) string.Empty,
, string.Empty, (current, slot) => current + slot.Serialize()); (current, slot) => current + slot.Serialize()
);
return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "total", 1)); return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "total", 1));
} }
public async Task<IActionResult> SUser(int id) { [HttpGet("s/user/{id:int}")]
Slot slot = await this.database.Slots public async Task<IActionResult> SUser(int id)
.Include(s => s.Creator) {
.Include(s => s.Location) Slot slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == id);
.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
return this.Ok(slot.Serialize()); return this.Ok(slot.Serialize());
} }
[HttpGet("slots")]
public IActionResult NewestSlots([FromQuery] int pageStart, [FromQuery] int pageSize)
{
IQueryable<Slot> slots = this.database.Slots.Include
(s => s.Creator)
.Include(s => s.Location)
.OrderByDescending(s => s.FirstUploaded)
.Skip(pageStart - 1)
.Take(Math.Min(pageSize, 30));
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize());
return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "hint_start", pageStart + Math.Min(pageSize, 30)));
}
} }
} }

View file

@ -0,0 +1,43 @@
using System.Linq;
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Controllers
{
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/plain")]
public class StatisticsController : ControllerBase
{
private readonly Database database;
public StatisticsController(Database database)
{
this.database = database;
}
[HttpGet("playersInPodCount")]
[HttpGet("totalPlayerCount")]
public async Task<IActionResult> TotalPlayerCount()
{
int recentMatches = await this.database.LastMatches.Where(l => TimestampHelper.Timestamp - l.Timestamp < 60).CountAsync();
return this.Ok(recentMatches.ToString());
}
[HttpGet("planetStats")]
public async Task<IActionResult> PlanetStats()
{
int totalSlotCount = await this.database.Slots.CountAsync();
const int mmPicksCount = 0;
return this.Ok
(
LbpSerializer.StringElement
("planetStats", LbpSerializer.StringElement("totalSlotCount", totalSlotCount) + LbpSerializer.StringElement("mmPicksCount", mmPicksCount))
);
}
}
}

View file

@ -1,5 +1,4 @@
using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Controllers namespace LBPUnion.ProjectLighthouse.Controllers
{ {
@ -9,9 +8,6 @@ namespace LBPUnion.ProjectLighthouse.Controllers
public class StoreController : Controller public class StoreController : Controller
{ {
[HttpGet("promotions")] [HttpGet("promotions")]
public IActionResult Promotions() public IActionResult Promotions() => this.Ok();
{
return Ok();
}
} }
} }

View file

@ -25,25 +25,18 @@ namespace LBPUnion.ProjectLighthouse.Controllers
[HttpGet("user/{username}")] [HttpGet("user/{username}")]
public async Task<IActionResult> GetUser(string username) public async Task<IActionResult> GetUser(string username)
{ {
User user = await this.database.Users User user = await this.database.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.Username == username);
.Include(u => u.Location)
.FirstOrDefaultAsync(u => u.Username == username);
if (user == null) return this.NotFound(); if (user == null) return this.NotFound();
return this.Ok(user.Serialize()); return this.Ok(user.Serialize());
} }
[HttpGet("user/{username}/playlists")] [HttpGet("users")]
public IActionResult GetUserPlaylists(string username) public async Task<IActionResult> GetUserAlt([FromQuery] string u) => await this.GetUser(u);
{
return this.Ok();
}
// [HttpPost("user/{username}")] [HttpGet("user/{username}/playlists")]
// public async Task<IActionResult> CreateUser(string username) { public IActionResult GetUserPlaylists(string username) => this.Ok();
// await new Database().CreateUser(username);
// return await GetUser(username);
// }
[HttpPost("updateUser")] [HttpPost("updateUser")]
public async Task<IActionResult> UpdateUser() public async Task<IActionResult> UpdateUser()
@ -77,11 +70,8 @@ namespace LBPUnion.ProjectLighthouse.Controllers
// if you find a way to make it not stupid feel free to replace this // if you find a way to make it not stupid feel free to replace this
using (XmlReader reader = XmlReader.Create(this.Request.Body, settings)) using (XmlReader reader = XmlReader.Create(this.Request.Body, settings))
{ {
List<string> List<string> path = new(); // you can think of this as a file path in the XML, like <updateUser> -> <location> -> <x>
path = new(); // you can think of this as a file path in the XML, like <updateUser> -> <location> -> <x> while (await reader.ReadAsync()) // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
while (await reader.ReadAsync())
{
// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
switch (reader.NodeType) switch (reader.NodeType)
{ {
case XmlNodeType.Element: case XmlNodeType.Element:
@ -97,21 +87,12 @@ namespace LBPUnion.ProjectLighthouse.Controllers
} }
case "location": case "location":
{ {
locationChanged = locationChanged = true; // if we're here then we're probably about to change the location.
true; // if we're here then we're probably about to change the location.
// ReSharper disable once ConvertIfStatementToSwitchStatement // ReSharper disable once ConvertIfStatementToSwitchStatement
if (path[2] == "x") if (path[2] == "x")
{ user.Location.X = Convert.ToInt32
user.Location.X = (await reader.GetValueAsync()); // GetValue only returns a string, i guess we just hope its a number lol
Convert.ToInt32( else if (path[2] == "y") user.Location.Y = Convert.ToInt32(await reader.GetValueAsync());
await reader
.GetValueAsync()); // GetValue only returns a string, i guess we just hope its a number lol
}
else if (path[2] == "y")
{
user.Location.Y = Convert.ToInt32(await reader.GetValueAsync());
}
break; break;
} }
case "icon": case "icon":
@ -131,14 +112,12 @@ namespace LBPUnion.ProjectLighthouse.Controllers
path.RemoveAt(path.Count - 1); path.RemoveAt(path.Count - 1);
break; break;
} }
}
} }
// the way location on a user card works is stupid and will not save with the way below as-is, so we do the following: // the way location on a user card works is stupid and will not save with the way below as-is, so we do the following:
if (locationChanged) // only modify the database if we modify here if (locationChanged) // only modify the database if we modify here
{ {
Location l = await this.database.Locations.Where(l => l.Id == user.LocationId) Location l = await this.database.Locations.Where(l => l.Id == user.LocationId).FirstOrDefaultAsync(); // find the location in the database again
.FirstOrDefaultAsync(); // find the location in the database again
// set the location in the database to the one we modified above // set the location in the database to the one we modified above
l.X = user.Location.X; l.X = user.Location.X;
@ -147,8 +126,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers
// now both are in sync, and will update in the database. // now both are in sync, and will update in the database.
} }
if (this.database.ChangeTracker.HasChanges()) if (this.database.ChangeTracker.HasChanges()) await this.database.SaveChangesAsync(); // save the user to the database if we changed anything
await this.database.SaveChangesAsync(); // save the user to the database if we changed anything
return this.Ok(); return this.Ok();
} }
} }

View file

@ -23,16 +23,13 @@ namespace LBPUnion.ProjectLighthouse
public DbSet<LastMatch> LastMatches { get; set; } public DbSet<LastMatch> LastMatches { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseMySql( protected override void OnConfiguring(DbContextOptionsBuilder options)
ServerSettings.DbConnectionString, => options.UseMySql(ServerSettings.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion);
MySqlServerVersion.LatestSupportedServerVersion
);
public async Task<User> CreateUser(string username) public async Task<User> CreateUser(string username)
{ {
User user; User user;
if ((user = await this.Users.Where(u => u.Username == username).FirstOrDefaultAsync()) != null) if ((user = await this.Users.Where(u => u.Username == username).FirstOrDefaultAsync()) != null) return user;
return user;
Location l = new(); // store to get id after submitting Location l = new(); // store to get id after submitting
this.Locations.Add(l); // add to table this.Locations.Add(l); // add to table
@ -51,12 +48,11 @@ namespace LBPUnion.ProjectLighthouse
return user; return user;
} }
#nullable enable #nullable enable
public async Task<Token?> AuthenticateUser(LoginData loginData) public async Task<Token?> AuthenticateUser(LoginData loginData)
{ {
// TODO: don't use psn name to authenticate // TODO: don't use psn name to authenticate
User user = await this.Users.FirstOrDefaultAsync(u => u.Username == loginData.Username) User user = await this.Users.FirstOrDefaultAsync(u => u.Username == loginData.Username) ?? await this.CreateUser(loginData.Username);
?? await this.CreateUser(loginData.Username);
Token token = new() Token token = new()
{ {
@ -74,20 +70,16 @@ namespace LBPUnion.ProjectLighthouse
{ {
Token? token = await this.Tokens.FirstOrDefaultAsync(t => t.UserToken == authToken); Token? token = await this.Tokens.FirstOrDefaultAsync(t => t.UserToken == authToken);
if (token == null) return null; if (token == null) return null;
return await this.Users
.Include(u => u.Location) return await this.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.UserId == token.UserId);
.FirstOrDefaultAsync(u => u.UserId == token.UserId);
} }
public async Task<User?> UserFromRequest(HttpRequest request) public async Task<User?> UserFromRequest(HttpRequest request)
{ {
if (!request.Cookies.TryGetValue("MM_AUTH", out string? mmAuth) || mmAuth == null) if (!request.Cookies.TryGetValue("MM_AUTH", out string? mmAuth) || mmAuth == null) return null;
{
return null;
}
return await this.UserFromAuthToken(mmAuth); return await this.UserFromAuthToken(mmAuth);
} }
#nullable disable #nullable disable
} }
} }

View file

@ -2,17 +2,20 @@ using System;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
namespace LBPUnion.ProjectLighthouse.Helpers { namespace LBPUnion.ProjectLighthouse.Helpers
{
// Yoinked from https://stackoverflow.com/a/68530667 // Yoinked from https://stackoverflow.com/a/68530667
// Thanks to T-moty! // Thanks to T-moty!
/// <summary> /// <summary>
/// Allows synchronous stream operations for this request. /// Allows synchronous stream operations for this request.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AllowSynchronousIoAttribute : ActionFilterAttribute { public class AllowSynchronousIoAttribute : ActionFilterAttribute
public override void OnResultExecuting(ResultExecutingContext context) { {
public override void OnResultExecuting(ResultExecutingContext context)
{
IHttpBodyControlFeature syncIoFeature = context.HttpContext.Features.Get<IHttpBodyControlFeature>(); IHttpBodyControlFeature syncIoFeature = context.HttpContext.Features.Get<IHttpBodyControlFeature>();
if(syncIoFeature != null) syncIoFeature.AllowSynchronousIO = true; if (syncIoFeature != null) syncIoFeature.AllowSynchronousIO = true;
} }
} }
} }

View file

@ -6,52 +6,57 @@ using System.IO.Pipelines;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace LBPUnion.ProjectLighthouse.Helpers { namespace LBPUnion.ProjectLighthouse.Helpers
public static class BinaryHelper { {
public static string ReadString(BinaryReader reader) { public static class BinaryHelper
{
public static string ReadString(BinaryReader reader)
{
List<byte> readBytes = new(); List<byte> readBytes = new();
byte readByte; byte readByte;
do { do readBytes.Add(readByte = reader.ReadByte());
readBytes.Add(readByte = reader.ReadByte()); while (readByte != 0x00);
} while(readByte != 0x00);
return Encoding.UTF8.GetString(readBytes.ToArray()); return Encoding.UTF8.GetString(readBytes.ToArray());
} }
public static void ReadUntilByte(BinaryReader reader, byte byteToReadTo) { public static void ReadUntilByte(BinaryReader reader, byte byteToReadTo)
{
byte readByte; byte readByte;
do { do readByte = reader.ReadByte();
readByte = reader.ReadByte(); while (readByte != byteToReadTo);
} while(readByte != byteToReadTo);
} }
public static byte[] ReadLastBytes(BinaryReader reader, int count, bool restoreOldPosition = true) { public static byte[] ReadLastBytes(BinaryReader reader, int count, bool restoreOldPosition = true)
{
long oldPosition = reader.BaseStream.Position; long oldPosition = reader.BaseStream.Position;
if(reader.BaseStream.Length < count) return Array.Empty<byte>(); if (reader.BaseStream.Length < count) return Array.Empty<byte>();
reader.BaseStream.Position = reader.BaseStream.Length - count; reader.BaseStream.Position = reader.BaseStream.Length - count;
byte[] data = reader.ReadBytes(count); byte[] data = reader.ReadBytes(count);
if(restoreOldPosition) reader.BaseStream.Position = oldPosition; if (restoreOldPosition) reader.BaseStream.Position = oldPosition;
return data; return data;
} }
// Written with reference from // Written with reference from
// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/request-response?view=aspnetcore-5.0 // https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/request-response?view=aspnetcore-5.0
// Surprisingly doesn't take seconds. (67ms for a 100kb file) // Surprisingly doesn't take seconds. (67ms for a 100kb file)
public static async Task<byte[]> ReadFromPipeReader(PipeReader reader) { public static async Task<byte[]> ReadFromPipeReader(PipeReader reader)
{
List<byte> data = new(); List<byte> data = new();
while(true) { while (true)
{
ReadResult readResult = await reader.ReadAsync(); ReadResult readResult = await reader.ReadAsync();
ReadOnlySequence<byte> buffer = readResult.Buffer; ReadOnlySequence<byte> buffer = readResult.Buffer;
if(readResult.IsCompleted && buffer.Length > 0) data.AddRange(buffer.ToArray()); if (readResult.IsCompleted && buffer.Length > 0) data.AddRange(buffer.ToArray());
reader.AdvanceTo(buffer.Start, buffer.End); reader.AdvanceTo(buffer.Start, buffer.End);
if(readResult.IsCompleted) break; if (readResult.IsCompleted) break;
} }
return data.ToArray(); return data.ToArray();

View file

@ -0,0 +1,24 @@
namespace LBPUnion.ProjectLighthouse.Helpers
{
public static class EulaHelper
{
public const string License = @"
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.";
public const string PrivateInstanceNotice = @"This server is a private testing instance.
Please do not make anything public for now, and keep in mind security isn't as tight as a full release would.";
public const bool ShowPrivateInstanceNotice = false;
}
}

View file

@ -3,17 +3,23 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
namespace LBPUnion.ProjectLighthouse.Helpers.Extensions { namespace LBPUnion.ProjectLighthouse.Helpers.Extensions
{
// https://stackoverflow.com/a/8039737 // https://stackoverflow.com/a/8039737
public static class ExceptionExtensions { public static class ExceptionExtensions
public static string ToDetailedException(this Exception exception) { {
public static string ToDetailedException(this Exception exception)
{
PropertyInfo[] properties = exception.GetType().GetProperties(); PropertyInfo[] properties = exception.GetType().GetProperties();
IEnumerable<string> fields = properties IEnumerable<string> fields = properties.Select
.Select(property => new { (
property.Name, property => new
Value = property.GetValue(exception, null), {
}) property.Name,
Value = property.GetValue(exception, null),
}
)
.Select(x => $"{x.Name} = {(x.Value != null ? x.Value.ToString() : string.Empty)}"); .Select(x => $"{x.Name} = {(x.Value != null ? x.Value.ToString() : string.Empty)}");
return string.Join("\n", fields); return string.Join("\n", fields);

View file

@ -4,17 +4,22 @@ using System.Linq;
using System.Text; using System.Text;
using LBPUnion.ProjectLighthouse.Types.Files; using LBPUnion.ProjectLighthouse.Types.Files;
namespace LBPUnion.ProjectLighthouse.Helpers { namespace LBPUnion.ProjectLighthouse.Helpers
public static class FileHelper { {
public static class FileHelper
{
public static readonly string ResourcePath = Path.Combine(Environment.CurrentDirectory, "r"); public static readonly string ResourcePath = Path.Combine(Environment.CurrentDirectory, "r");
public static string GetResourcePath(string hash) => Path.Combine(ResourcePath, hash); public static string GetResourcePath(string hash) => Path.Combine(ResourcePath, hash);
public static bool IsFileSafe(LbpFile file) { public static bool IsFileSafe(LbpFile file)
if(file.FileType == LbpFileType.Unknown) file.FileType = DetermineFileType(file.Data); {
if (file.FileType == LbpFileType.Unknown) file.FileType = DetermineFileType(file.Data);
return file.FileType switch {
return file.FileType switch
{
LbpFileType.FileArchive => false, LbpFileType.FileArchive => false,
LbpFileType.Painting => true,
LbpFileType.Unknown => false, LbpFileType.Unknown => false,
LbpFileType.Texture => true, LbpFileType.Texture => true,
LbpFileType.Script => false, LbpFileType.Script => false,
@ -29,16 +34,19 @@ namespace LBPUnion.ProjectLighthouse.Helpers {
}; };
} }
public static LbpFileType DetermineFileType(byte[] data) { public static LbpFileType DetermineFileType(byte[] data)
{
using MemoryStream ms = new(data); using MemoryStream ms = new(data);
using BinaryReader reader = new(ms); using BinaryReader reader = new(ms);
string footer = Encoding.ASCII.GetString(BinaryHelper.ReadLastBytes(reader, 4)); string footer = Encoding.ASCII.GetString(BinaryHelper.ReadLastBytes(reader, 4));
if(footer == "FARC") return LbpFileType.FileArchive; if (footer == "FARC") return LbpFileType.FileArchive;
byte[] header = reader.ReadBytes(3); byte[] header = reader.ReadBytes(3);
return Encoding.ASCII.GetString(header) switch { return Encoding.ASCII.GetString(header) switch
{
"PTG" => LbpFileType.Painting,
"TEX" => LbpFileType.Texture, "TEX" => LbpFileType.Texture,
"FSH" => LbpFileType.Script, "FSH" => LbpFileType.Script,
"VOP" => LbpFileType.Voice, "VOP" => LbpFileType.Voice,
@ -50,8 +58,9 @@ namespace LBPUnion.ProjectLighthouse.Helpers {
public static bool ResourceExists(string hash) => File.Exists(GetResourcePath(hash)); public static bool ResourceExists(string hash) => File.Exists(GetResourcePath(hash));
public static void EnsureDirectoryCreated(string path) { public static void EnsureDirectoryCreated(string path)
if(!Directory.Exists(path)) Directory.CreateDirectory(path ?? throw new ArgumentNullException(nameof(path))); {
if (!Directory.Exists(path)) Directory.CreateDirectory(path ?? throw new ArgumentNullException(nameof(path)));
} }
public static string[] ResourcesNotUploaded(params string[] hashes) => hashes.Where(hash => !ResourceExists(hash)).ToArray(); public static string[] ResourcesNotUploaded(params string[] hashes) => hashes.Where(hash => !ResourceExists(hash)).ToArray();

View file

@ -6,32 +6,22 @@ using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace LBPUnion.ProjectLighthouse.Helpers { namespace LBPUnion.ProjectLighthouse.Helpers
{
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
public static class HashHelper { public static class HashHelper
{
// private static readonly SHA1 sha1 = SHA1.Create(); // private static readonly SHA1 sha1 = SHA1.Create();
private static readonly SHA256 sha256 = SHA256.Create(); private static readonly SHA256 sha256 = SHA256.Create();
private static readonly Random random = new(); private static readonly Random random = new();
#region Hash Functions
public static string Sha256Hash(string str) => Sha256Hash(Encoding.UTF8.GetBytes(str));
public static string Sha256Hash(byte[] bytes) {
byte[] hash = sha256.ComputeHash(bytes);
return Encoding.UTF8.GetString(hash, 0, hash.Length);
}
public static string BCryptHash(string str) => BCrypt.Net.BCrypt.HashPassword(str);
public static string BCryptHash(byte[] bytes) => BCrypt.Net.BCrypt.HashPassword(Encoding.UTF8.GetString(bytes));
#endregion
/// <summary> /// <summary>
/// Generates a specified amount of random bytes in an array. /// Generates a specified amount of random bytes in an array.
/// </summary> /// </summary>
/// <param name="count">The amount of bytes to generate.</param> /// <param name="count">The amount of bytes to generate.</param>
/// <returns>The bytes generated</returns> /// <returns>The bytes generated</returns>
public static IEnumerable<byte> GenerateRandomBytes(int count) { public static IEnumerable<byte> GenerateRandomBytes(int count)
{
byte[] b = new byte[count]; byte[] b = new byte[count];
random.NextBytes(b); random.NextBytes(b);
@ -39,34 +29,31 @@ namespace LBPUnion.ProjectLighthouse.Helpers {
} }
/// <summary> /// <summary>
/// Generates a random SHA256 & BCrypted token /// Generates a random SHA256 & BCrypted token
/// </summary> /// </summary>
/// <returns>The token as a string.</returns> /// <returns>The token as a string.</returns>
public static string GenerateAuthToken() { public static string GenerateAuthToken()
byte[] bytes = (byte[]) GenerateRandomBytes(256); {
byte[] bytes = (byte[])GenerateRandomBytes(256);
return BCryptHash(Sha256Hash(bytes)); return BCryptHash(Sha256Hash(bytes));
} }
public static async Task<string> ComputeDigest(string path, string authCookie, Stream body, public static async Task<string> ComputeDigest(string path, string authCookie, Stream body, string digestKey)
string digestKey)
{ {
MemoryStream memoryStream = new MemoryStream(); MemoryStream memoryStream = new();
byte[] pathBytes = Encoding.UTF8.GetBytes(path); byte[] pathBytes = Encoding.UTF8.GetBytes(path);
byte[] cookieBytes = string.IsNullOrEmpty(authCookie) byte[] cookieBytes = string.IsNullOrEmpty(authCookie) ? Array.Empty<byte>() : Encoding.UTF8.GetBytes(authCookie);
? Array.Empty<byte>()
: Encoding.UTF8.GetBytes(authCookie);
byte[] keyBytes = Encoding.UTF8.GetBytes(digestKey); byte[] keyBytes = Encoding.UTF8.GetBytes(digestKey);
await body.CopyToAsync(memoryStream); await body.CopyToAsync(memoryStream);
byte[] bodyBytes = memoryStream.ToArray(); byte[] bodyBytes = memoryStream.ToArray();
using IncrementalHash sha1 = IncrementalHash.CreateHash(HashAlgorithmName.SHA1); using IncrementalHash sha1 = IncrementalHash.CreateHash(HashAlgorithmName.SHA1);
sha1.AppendData(bodyBytes); sha1.AppendData(bodyBytes);
if (cookieBytes.Length > 0) if (cookieBytes.Length > 0) sha1.AppendData(cookieBytes);
sha1.AppendData(cookieBytes);
sha1.AppendData(pathBytes); sha1.AppendData(pathBytes);
sha1.AppendData(keyBytes); sha1.AppendData(keyBytes);
@ -75,5 +62,22 @@ namespace LBPUnion.ProjectLighthouse.Helpers {
return digestString; return digestString;
} }
#region Hash Functions
public static string Sha256Hash(string str) => Sha256Hash(Encoding.UTF8.GetBytes(str));
public static string Sha256Hash(byte[] bytes)
{
byte[] hash = sha256.ComputeHash(bytes);
return Encoding.UTF8.GetString(hash, 0, hash.Length);
}
public static string BCryptHash(string str) => BCrypt.Net.BCrypt.HashPassword(str);
public static string BCryptHash(byte[] bytes) => BCrypt.Net.BCrypt.HashPassword(Encoding.UTF8.GetString(bytes));
#endregion
} }
} }

View file

@ -0,0 +1,13 @@
using System;
namespace LBPUnion.ProjectLighthouse.Helpers
{
public static class TimeHelper
{
public static long UnixTimeMilliseconds() => DateTimeOffset.Now.ToUnixTimeMilliseconds();
public static long UnixTimeSeconds() => DateTimeOffset.Now.ToUnixTimeSeconds();
}
}
// 1397109686193
// 1635389749454

View file

@ -1,7 +1,9 @@
using System; using System;
namespace LBPUnion.ProjectLighthouse.Helpers { namespace LBPUnion.ProjectLighthouse.Helpers
public static class TimestampHelper { {
public static class TimestampHelper
{
public static long Timestamp => (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds; public static long Timestamp => (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
} }
} }

View file

@ -3,32 +3,23 @@ using Kettu;
using LBPUnion.ProjectLighthouse.Helpers.Extensions; using LBPUnion.ProjectLighthouse.Helpers.Extensions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace LBPUnion.ProjectLighthouse.Logging { namespace LBPUnion.ProjectLighthouse.Logging
public class AspNetToKettuLogger : ILogger { {
public class AspNetToKettuLogger : ILogger
{
public IDisposable BeginScope<TState>(TState state) { public IDisposable BeginScope<TState>(TState state) => NullScope.Instance;
return NullScope.Instance;
}
public bool IsEnabled(LogLevel logLevel) => true; public bool IsEnabled(LogLevel logLevel) => true;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) {
LoggerLevel loggerLevel = logLevel switch {
LogLevel.Trace => LoggerLevelAspNetTrace.Instance, public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
LogLevel.Debug => LoggerLevelAspNetDebug.Instance, {
LogLevel.Information => LoggerLevelAspNetInformation.Instance, LoggerLevel loggerLevel = new LoggerLevelAspNet(logLevel);
LogLevel.Warning => LoggerLevelAspNetWarning.Instance,
LogLevel.Error => LoggerLevelAspNetError.Instance,
LogLevel.Critical => LoggerLevelAspNetCritical.Instance,
LogLevel.None => LoggerLevelAspNetNone.Instance,
_ => throw new ArgumentOutOfRangeException(nameof(logLevel), logLevel, null),
};
Logger.Log(state.ToString(), loggerLevel); Logger.Log(state.ToString(), loggerLevel);
if(exception == null) return; if (exception == null) return;
string[] lines = exception.ToDetailedException().Replace("\r", "").Split("\n"); string[] lines = exception.ToDetailedException().Replace("\r", "").Split("\n");
foreach(string line in lines) Logger.Log(line, loggerLevel); foreach (string line in lines) Logger.Log(line, loggerLevel);
} }
} }
} }

View file

@ -1,15 +1,16 @@
using System; using System;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace LBPUnion.ProjectLighthouse.Logging { namespace LBPUnion.ProjectLighthouse.Logging
{
[ProviderAlias("Kettu")] [ProviderAlias("Kettu")]
public class AspNetToKettuLoggerProvider : ILoggerProvider, IDisposable { public class AspNetToKettuLoggerProvider : ILoggerProvider, IDisposable
public void Dispose() { {
public void Dispose()
{
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
public ILogger CreateLogger(string categoryName) { public ILogger CreateLogger(string categoryName) => new AspNetToKettuLogger();
return new AspNetToKettuLogger();
}
} }
} }

View file

@ -3,16 +3,23 @@ using System.IO;
using Kettu; using Kettu;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
namespace LBPUnion.ProjectLighthouse.Logging { namespace LBPUnion.ProjectLighthouse.Logging
public class LighthouseFileLogger : LoggerBase { {
public class LighthouseFileLogger : LoggerBase
{
private static readonly string logsDirectory = Path.Combine(Environment.CurrentDirectory, "logs"); private static readonly string logsDirectory = Path.Combine(Environment.CurrentDirectory, "logs");
public override void Send(LoggerLine line) public override void Send(LoggerLine line)
{ {
FileHelper.EnsureDirectoryCreated(logsDirectory); FileHelper.EnsureDirectoryCreated(logsDirectory);
File.AppendAllText(Path.Combine(logsDirectory, line.LoggerLevel + ".log"), line.LineData + "\n"); string channel = string.IsNullOrEmpty(line.LoggerLevel.Channel) ? "" : $"[{line.LoggerLevel.Channel}] ";
File.AppendAllText(Path.Combine(logsDirectory, "all.log"), line.LineData + "\n");
string contentFile = $"{channel}{line.LineData}\n";
string contentAll = $"[{$"{line.LoggerLevel.Name} {channel}".TrimEnd()}] {line.LineData}\n";
File.AppendAllText(Path.Combine(logsDirectory, line.LoggerLevel.Name + ".log"), contentFile);
File.AppendAllText(Path.Combine(logsDirectory, "all.log"), contentAll);
} }
} }
} }

View file

@ -1,55 +1,39 @@
using Kettu; using Kettu;
using Microsoft.Extensions.Logging;
namespace LBPUnion.ProjectLighthouse.Logging { namespace LBPUnion.ProjectLighthouse.Logging
public class LoggerLevelStartup : LoggerLevel { {
public override string Name => "Startup"; public class LoggerLevelStartup : LoggerLevel
{
public static readonly LoggerLevelStartup Instance = new(); public static readonly LoggerLevelStartup Instance = new();
public override string Name => "Startup";
} }
public class LoggerLevelDatabase : LoggerLevel { public class LoggerLevelDatabase : LoggerLevel
public override string Name => "Database"; {
public static readonly LoggerLevelDatabase Instance = new(); public static readonly LoggerLevelDatabase Instance = new();
public override string Name => "Database";
} }
public class LoggerLevelHttp : LoggerLevel { public class LoggerLevelHttp : LoggerLevel
public override string Name => "HTTP"; {
public static readonly LoggerLevelHttp Instance = new(); public static readonly LoggerLevelHttp Instance = new();
public override string Name => "HTTP";
} }
#region ASP.NET public class LoggerLevelFilter : LoggerLevel
public class LoggerLevelAspNetTrace : LoggerLevel { {
public override string Name => "ASP.NET: Trace"; public static readonly LoggerLevelFilter Instance = new();
public static readonly LoggerLevelAspNetTrace Instance = new(); public override string Name => "Filter";
} }
public class LoggerLevelAspNetDebug : LoggerLevel { public class LoggerLevelAspNet : LoggerLevel
public override string Name => "ASP.NET: Debug"; {
public static readonly LoggerLevelAspNetDebug Instance = new();
public LoggerLevelAspNet(LogLevel level)
{
this.Channel = level.ToString();
}
public override string Name => "AspNet";
} }
public class LoggerLevelAspNetInformation : LoggerLevel {
public override string Name => "ASP.NET: Information";
public static readonly LoggerLevelAspNetInformation Instance = new();
}
public class LoggerLevelAspNetWarning : LoggerLevel {
public override string Name => "ASP.NET: Warning";
public static readonly LoggerLevelAspNetWarning Instance = new();
}
public class LoggerLevelAspNetError : LoggerLevel {
public override string Name => "ASP.NET: Error";
public static readonly LoggerLevelAspNetError Instance = new();
}
public class LoggerLevelAspNetCritical : LoggerLevel {
public override string Name => "ASP.NET: Critical";
public static readonly LoggerLevelAspNetCritical Instance = new();
}
public class LoggerLevelAspNetNone : LoggerLevel {
public override string Name => "ASP.NET: None";
public static readonly LoggerLevelAspNetNone Instance = new();
}
#endregion
} }

View file

@ -1,12 +1,16 @@
using System; using System;
namespace LBPUnion.ProjectLighthouse.Logging { namespace LBPUnion.ProjectLighthouse.Logging
public class NullScope : IDisposable{ {
public class NullScope : IDisposable
{
private NullScope()
{}
public static NullScope Instance { get; } = new(); public static NullScope Instance { get; } = new();
private NullScope() {} public void Dispose()
{
public void Dispose() {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
} }

View file

@ -0,0 +1,421 @@
// <auto-generated />
using LBPUnion.ProjectLighthouse;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace ProjectLighthouse.Migrations
{
[DbContext(typeof(Database))]
[Migration("20211028015915_AddSlotTimestamp")]
partial class AddSlotTimestamp
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Relational:MaxIdentifierLength", 64)
.HasAnnotation("ProductVersion", "5.0.11");
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b =>
{
b.Property<int>("HeartedProfileId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("HeartedUserId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("HeartedProfileId");
b.HasIndex("HeartedUserId");
b.HasIndex("UserId");
b.ToTable("HeartedProfiles");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b =>
{
b.Property<int>("HeartedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("HeartedLevelId");
b.HasIndex("SlotId");
b.HasIndex("UserId");
b.ToTable("HeartedLevels");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b =>
{
b.Property<int>("QueuedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("QueuedLevelId");
b.HasIndex("SlotId");
b.HasIndex("UserId");
b.ToTable("QueuedLevels");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b =>
{
b.Property<int>("SlotId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("AuthorLabels")
.HasColumnType("longtext");
b.Property<string>("BackgroundHash")
.HasColumnType("longtext");
b.Property<int>("CreatorId")
.HasColumnType("int");
b.Property<string>("Description")
.HasColumnType("longtext");
b.Property<string>("IconHash")
.HasColumnType("longtext");
b.Property<bool>("InitiallyLocked")
.HasColumnType("tinyint(1)");
b.Property<bool>("Lbp1Only")
.HasColumnType("tinyint(1)");
b.Property<int>("LocationId")
.HasColumnType("int");
b.Property<int>("MaximumPlayers")
.HasColumnType("int");
b.Property<int>("MinimumPlayers")
.HasColumnType("int");
b.Property<bool>("MoveRequired")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.HasColumnType("longtext");
b.Property<string>("ResourceCollection")
.HasColumnType("longtext");
b.Property<string>("RootLevel")
.HasColumnType("longtext");
b.Property<int>("Shareable")
.HasColumnType("int");
b.Property<bool>("SubLevel")
.HasColumnType("tinyint(1)");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("SlotId");
b.HasIndex("CreatorId");
b.HasIndex("LocationId");
b.ToTable("Slots");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b =>
{
b.Property<int>("CommentId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Message")
.HasColumnType("longtext");
b.Property<int>("PosterUserId")
.HasColumnType("int");
b.Property<int>("TargetUserId")
.HasColumnType("int");
b.Property<int>("ThumbsDown")
.HasColumnType("int");
b.Property<int>("ThumbsUp")
.HasColumnType("int");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("CommentId");
b.HasIndex("PosterUserId");
b.HasIndex("TargetUserId");
b.ToTable("Comments");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.LastMatch", b =>
{
b.Property<int>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("UserId");
b.ToTable("LastMatches");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Location", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("X")
.HasColumnType("int");
b.Property<int>("Y")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("Locations");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Token", b =>
{
b.Property<int>("TokenId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<string>("UserToken")
.HasColumnType("longtext");
b.HasKey("TokenId");
b.ToTable("Tokens");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b =>
{
b.Property<int>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Biography")
.HasColumnType("longtext");
b.Property<string>("BooHash")
.HasColumnType("longtext");
b.Property<int>("CommentCount")
.HasColumnType("int");
b.Property<bool>("CommentsEnabled")
.HasColumnType("tinyint(1)");
b.Property<int>("FavouriteSlotCount")
.HasColumnType("int");
b.Property<int>("FavouriteUserCount")
.HasColumnType("int");
b.Property<int>("Game")
.HasColumnType("int");
b.Property<int>("HeartCount")
.HasColumnType("int");
b.Property<string>("IconHash")
.HasColumnType("longtext");
b.Property<int>("Lists")
.HasColumnType("int");
b.Property<int>("LocationId")
.HasColumnType("int");
b.Property<int>("LolCatFtwCount")
.HasColumnType("int");
b.Property<int>("PhotosByMeCount")
.HasColumnType("int");
b.Property<int>("PhotosWithMeCount")
.HasColumnType("int");
b.Property<string>("Pins")
.HasColumnType("longtext");
b.Property<string>("PlanetHash")
.HasColumnType("longtext");
b.Property<int>("ReviewCount")
.HasColumnType("int");
b.Property<int>("StaffChallengeBronzeCount")
.HasColumnType("int");
b.Property<int>("StaffChallengeGoldCount")
.HasColumnType("int");
b.Property<int>("StaffChallengeSilverCount")
.HasColumnType("int");
b.Property<int>("UsedSlots")
.HasColumnType("int");
b.Property<string>("Username")
.HasColumnType("longtext");
b.Property<string>("YayHash")
.HasColumnType("longtext");
b.HasKey("UserId");
b.HasIndex("LocationId");
b.ToTable("Users");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "HeartedUser")
.WithMany()
.HasForeignKey("HeartedUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("HeartedUser");
b.Navigation("User");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot")
.WithMany()
.HasForeignKey("SlotId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Slot");
b.Navigation("User");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot")
.WithMany()
.HasForeignKey("SlotId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Slot");
b.Navigation("User");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Creator")
.WithMany()
.HasForeignKey("CreatorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location")
.WithMany()
.HasForeignKey("LocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Creator");
b.Navigation("Location");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Poster")
.WithMany()
.HasForeignKey("PosterUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Target")
.WithMany()
.HasForeignKey("TargetUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Poster");
b.Navigation("Target");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location")
.WithMany()
.HasForeignKey("LocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Location");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,25 @@
using LBPUnion.ProjectLighthouse.Helpers;
using Microsoft.EntityFrameworkCore.Migrations;
namespace ProjectLighthouse.Migrations
{
public partial class AddSlotTimestamp : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<long>(
name: "Timestamp",
table: "Slots",
type: "bigint",
nullable: false,
defaultValue: TimeHelper.UnixTimeMilliseconds());
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Timestamp",
table: "Slots");
}
}
}

View file

@ -0,0 +1,424 @@
// <auto-generated />
using LBPUnion.ProjectLighthouse;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace ProjectLighthouse.Migrations
{
[DbContext(typeof(Database))]
[Migration("20211028021513_AddSlotFirstUploadedAndLastUpdated")]
partial class AddSlotFirstUploadedAndLastUpdated
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Relational:MaxIdentifierLength", 64)
.HasAnnotation("ProductVersion", "5.0.11");
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b =>
{
b.Property<int>("HeartedProfileId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("HeartedUserId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("HeartedProfileId");
b.HasIndex("HeartedUserId");
b.HasIndex("UserId");
b.ToTable("HeartedProfiles");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b =>
{
b.Property<int>("HeartedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("HeartedLevelId");
b.HasIndex("SlotId");
b.HasIndex("UserId");
b.ToTable("HeartedLevels");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b =>
{
b.Property<int>("QueuedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("QueuedLevelId");
b.HasIndex("SlotId");
b.HasIndex("UserId");
b.ToTable("QueuedLevels");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b =>
{
b.Property<int>("SlotId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("AuthorLabels")
.HasColumnType("longtext");
b.Property<string>("BackgroundHash")
.HasColumnType("longtext");
b.Property<int>("CreatorId")
.HasColumnType("int");
b.Property<string>("Description")
.HasColumnType("longtext");
b.Property<long>("FirstUploaded")
.HasColumnType("bigint");
b.Property<string>("IconHash")
.HasColumnType("longtext");
b.Property<bool>("InitiallyLocked")
.HasColumnType("tinyint(1)");
b.Property<long>("LastUpdated")
.HasColumnType("bigint");
b.Property<bool>("Lbp1Only")
.HasColumnType("tinyint(1)");
b.Property<int>("LocationId")
.HasColumnType("int");
b.Property<int>("MaximumPlayers")
.HasColumnType("int");
b.Property<int>("MinimumPlayers")
.HasColumnType("int");
b.Property<bool>("MoveRequired")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.HasColumnType("longtext");
b.Property<string>("ResourceCollection")
.HasColumnType("longtext");
b.Property<string>("RootLevel")
.HasColumnType("longtext");
b.Property<int>("Shareable")
.HasColumnType("int");
b.Property<bool>("SubLevel")
.HasColumnType("tinyint(1)");
b.HasKey("SlotId");
b.HasIndex("CreatorId");
b.HasIndex("LocationId");
b.ToTable("Slots");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b =>
{
b.Property<int>("CommentId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Message")
.HasColumnType("longtext");
b.Property<int>("PosterUserId")
.HasColumnType("int");
b.Property<int>("TargetUserId")
.HasColumnType("int");
b.Property<int>("ThumbsDown")
.HasColumnType("int");
b.Property<int>("ThumbsUp")
.HasColumnType("int");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("CommentId");
b.HasIndex("PosterUserId");
b.HasIndex("TargetUserId");
b.ToTable("Comments");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.LastMatch", b =>
{
b.Property<int>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("UserId");
b.ToTable("LastMatches");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Location", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("X")
.HasColumnType("int");
b.Property<int>("Y")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("Locations");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Token", b =>
{
b.Property<int>("TokenId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<string>("UserToken")
.HasColumnType("longtext");
b.HasKey("TokenId");
b.ToTable("Tokens");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b =>
{
b.Property<int>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Biography")
.HasColumnType("longtext");
b.Property<string>("BooHash")
.HasColumnType("longtext");
b.Property<int>("CommentCount")
.HasColumnType("int");
b.Property<bool>("CommentsEnabled")
.HasColumnType("tinyint(1)");
b.Property<int>("FavouriteSlotCount")
.HasColumnType("int");
b.Property<int>("FavouriteUserCount")
.HasColumnType("int");
b.Property<int>("Game")
.HasColumnType("int");
b.Property<int>("HeartCount")
.HasColumnType("int");
b.Property<string>("IconHash")
.HasColumnType("longtext");
b.Property<int>("Lists")
.HasColumnType("int");
b.Property<int>("LocationId")
.HasColumnType("int");
b.Property<int>("LolCatFtwCount")
.HasColumnType("int");
b.Property<int>("PhotosByMeCount")
.HasColumnType("int");
b.Property<int>("PhotosWithMeCount")
.HasColumnType("int");
b.Property<string>("Pins")
.HasColumnType("longtext");
b.Property<string>("PlanetHash")
.HasColumnType("longtext");
b.Property<int>("ReviewCount")
.HasColumnType("int");
b.Property<int>("StaffChallengeBronzeCount")
.HasColumnType("int");
b.Property<int>("StaffChallengeGoldCount")
.HasColumnType("int");
b.Property<int>("StaffChallengeSilverCount")
.HasColumnType("int");
b.Property<int>("UsedSlots")
.HasColumnType("int");
b.Property<string>("Username")
.HasColumnType("longtext");
b.Property<string>("YayHash")
.HasColumnType("longtext");
b.HasKey("UserId");
b.HasIndex("LocationId");
b.ToTable("Users");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "HeartedUser")
.WithMany()
.HasForeignKey("HeartedUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("HeartedUser");
b.Navigation("User");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot")
.WithMany()
.HasForeignKey("SlotId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Slot");
b.Navigation("User");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot")
.WithMany()
.HasForeignKey("SlotId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Slot");
b.Navigation("User");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Creator")
.WithMany()
.HasForeignKey("CreatorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location")
.WithMany()
.HasForeignKey("LocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Creator");
b.Navigation("Location");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Poster")
.WithMany()
.HasForeignKey("PosterUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Target")
.WithMany()
.HasForeignKey("TargetUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Poster");
b.Navigation("Target");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location")
.WithMany()
.HasForeignKey("LocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Location");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,35 @@
using LBPUnion.ProjectLighthouse.Helpers;
using Microsoft.EntityFrameworkCore.Migrations;
namespace ProjectLighthouse.Migrations
{
public partial class AddSlotFirstUploadedAndLastUpdated : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "Timestamp",
table: "Slots",
newName: "LastUpdated");
migrationBuilder.AddColumn<long>(
name: "FirstUploaded",
table: "Slots",
type: "bigint",
nullable: false,
defaultValue: TimeHelper.UnixTimeMilliseconds());
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "FirstUploaded",
table: "Slots");
migrationBuilder.RenameColumn(
name: "LastUpdated",
table: "Slots",
newName: "Timestamp");
}
}
}

View file

@ -0,0 +1,421 @@
// <auto-generated />
using LBPUnion.ProjectLighthouse;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace ProjectLighthouse.Migrations
{
[DbContext(typeof(Database))]
[Migration("20211029213334_RemoveUsedSlotsFromDb")]
partial class RemoveUsedSlotsFromDb
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Relational:MaxIdentifierLength", 64)
.HasAnnotation("ProductVersion", "5.0.11");
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b =>
{
b.Property<int>("HeartedProfileId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("HeartedUserId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("HeartedProfileId");
b.HasIndex("HeartedUserId");
b.HasIndex("UserId");
b.ToTable("HeartedProfiles");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b =>
{
b.Property<int>("HeartedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("HeartedLevelId");
b.HasIndex("SlotId");
b.HasIndex("UserId");
b.ToTable("HeartedLevels");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b =>
{
b.Property<int>("QueuedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("QueuedLevelId");
b.HasIndex("SlotId");
b.HasIndex("UserId");
b.ToTable("QueuedLevels");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b =>
{
b.Property<int>("SlotId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("AuthorLabels")
.HasColumnType("longtext");
b.Property<string>("BackgroundHash")
.HasColumnType("longtext");
b.Property<int>("CreatorId")
.HasColumnType("int");
b.Property<string>("Description")
.HasColumnType("longtext");
b.Property<long>("FirstUploaded")
.HasColumnType("bigint");
b.Property<string>("IconHash")
.HasColumnType("longtext");
b.Property<bool>("InitiallyLocked")
.HasColumnType("tinyint(1)");
b.Property<long>("LastUpdated")
.HasColumnType("bigint");
b.Property<bool>("Lbp1Only")
.HasColumnType("tinyint(1)");
b.Property<int>("LocationId")
.HasColumnType("int");
b.Property<int>("MaximumPlayers")
.HasColumnType("int");
b.Property<int>("MinimumPlayers")
.HasColumnType("int");
b.Property<bool>("MoveRequired")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.HasColumnType("longtext");
b.Property<string>("ResourceCollection")
.HasColumnType("longtext");
b.Property<string>("RootLevel")
.HasColumnType("longtext");
b.Property<int>("Shareable")
.HasColumnType("int");
b.Property<bool>("SubLevel")
.HasColumnType("tinyint(1)");
b.HasKey("SlotId");
b.HasIndex("CreatorId");
b.HasIndex("LocationId");
b.ToTable("Slots");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b =>
{
b.Property<int>("CommentId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Message")
.HasColumnType("longtext");
b.Property<int>("PosterUserId")
.HasColumnType("int");
b.Property<int>("TargetUserId")
.HasColumnType("int");
b.Property<int>("ThumbsDown")
.HasColumnType("int");
b.Property<int>("ThumbsUp")
.HasColumnType("int");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("CommentId");
b.HasIndex("PosterUserId");
b.HasIndex("TargetUserId");
b.ToTable("Comments");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.LastMatch", b =>
{
b.Property<int>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("UserId");
b.ToTable("LastMatches");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Location", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("X")
.HasColumnType("int");
b.Property<int>("Y")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("Locations");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Token", b =>
{
b.Property<int>("TokenId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<string>("UserToken")
.HasColumnType("longtext");
b.HasKey("TokenId");
b.ToTable("Tokens");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b =>
{
b.Property<int>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Biography")
.HasColumnType("longtext");
b.Property<string>("BooHash")
.HasColumnType("longtext");
b.Property<int>("CommentCount")
.HasColumnType("int");
b.Property<bool>("CommentsEnabled")
.HasColumnType("tinyint(1)");
b.Property<int>("FavouriteSlotCount")
.HasColumnType("int");
b.Property<int>("FavouriteUserCount")
.HasColumnType("int");
b.Property<int>("Game")
.HasColumnType("int");
b.Property<int>("HeartCount")
.HasColumnType("int");
b.Property<string>("IconHash")
.HasColumnType("longtext");
b.Property<int>("Lists")
.HasColumnType("int");
b.Property<int>("LocationId")
.HasColumnType("int");
b.Property<int>("LolCatFtwCount")
.HasColumnType("int");
b.Property<int>("PhotosByMeCount")
.HasColumnType("int");
b.Property<int>("PhotosWithMeCount")
.HasColumnType("int");
b.Property<string>("Pins")
.HasColumnType("longtext");
b.Property<string>("PlanetHash")
.HasColumnType("longtext");
b.Property<int>("ReviewCount")
.HasColumnType("int");
b.Property<int>("StaffChallengeBronzeCount")
.HasColumnType("int");
b.Property<int>("StaffChallengeGoldCount")
.HasColumnType("int");
b.Property<int>("StaffChallengeSilverCount")
.HasColumnType("int");
b.Property<string>("Username")
.HasColumnType("longtext");
b.Property<string>("YayHash")
.HasColumnType("longtext");
b.HasKey("UserId");
b.HasIndex("LocationId");
b.ToTable("Users");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "HeartedUser")
.WithMany()
.HasForeignKey("HeartedUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("HeartedUser");
b.Navigation("User");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot")
.WithMany()
.HasForeignKey("SlotId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Slot");
b.Navigation("User");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot")
.WithMany()
.HasForeignKey("SlotId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Slot");
b.Navigation("User");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Creator")
.WithMany()
.HasForeignKey("CreatorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location")
.WithMany()
.HasForeignKey("LocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Creator");
b.Navigation("Location");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Poster")
.WithMany()
.HasForeignKey("PosterUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Target")
.WithMany()
.HasForeignKey("TargetUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Poster");
b.Navigation("Target");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location")
.WithMany()
.HasForeignKey("LocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Location");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace ProjectLighthouse.Migrations
{
public partial class RemoveUsedSlotsFromDb : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "UsedSlots",
table: "Users");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "UsedSlots",
table: "Users",
type: "int",
nullable: false,
defaultValue: 0);
}
}
}

View file

@ -0,0 +1,424 @@
// <auto-generated />
using LBPUnion.ProjectLighthouse;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace ProjectLighthouse.Migrations
{
[DbContext(typeof(Database))]
[Migration("20211030203837_AddMMPickToSlot")]
partial class AddMMPickToSlot
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Relational:MaxIdentifierLength", 64)
.HasAnnotation("ProductVersion", "5.0.11");
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b =>
{
b.Property<int>("HeartedProfileId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("HeartedUserId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("HeartedProfileId");
b.HasIndex("HeartedUserId");
b.HasIndex("UserId");
b.ToTable("HeartedProfiles");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b =>
{
b.Property<int>("HeartedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("HeartedLevelId");
b.HasIndex("SlotId");
b.HasIndex("UserId");
b.ToTable("HeartedLevels");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b =>
{
b.Property<int>("QueuedLevelId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("SlotId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("QueuedLevelId");
b.HasIndex("SlotId");
b.HasIndex("UserId");
b.ToTable("QueuedLevels");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b =>
{
b.Property<int>("SlotId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("AuthorLabels")
.HasColumnType("longtext");
b.Property<string>("BackgroundHash")
.HasColumnType("longtext");
b.Property<int>("CreatorId")
.HasColumnType("int");
b.Property<string>("Description")
.HasColumnType("longtext");
b.Property<long>("FirstUploaded")
.HasColumnType("bigint");
b.Property<string>("IconHash")
.HasColumnType("longtext");
b.Property<bool>("InitiallyLocked")
.HasColumnType("tinyint(1)");
b.Property<long>("LastUpdated")
.HasColumnType("bigint");
b.Property<bool>("Lbp1Only")
.HasColumnType("tinyint(1)");
b.Property<int>("LocationId")
.HasColumnType("int");
b.Property<bool>("MMPick")
.HasColumnType("tinyint(1)");
b.Property<int>("MaximumPlayers")
.HasColumnType("int");
b.Property<int>("MinimumPlayers")
.HasColumnType("int");
b.Property<bool>("MoveRequired")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.HasColumnType("longtext");
b.Property<string>("ResourceCollection")
.HasColumnType("longtext");
b.Property<string>("RootLevel")
.HasColumnType("longtext");
b.Property<int>("Shareable")
.HasColumnType("int");
b.Property<bool>("SubLevel")
.HasColumnType("tinyint(1)");
b.HasKey("SlotId");
b.HasIndex("CreatorId");
b.HasIndex("LocationId");
b.ToTable("Slots");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b =>
{
b.Property<int>("CommentId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Message")
.HasColumnType("longtext");
b.Property<int>("PosterUserId")
.HasColumnType("int");
b.Property<int>("TargetUserId")
.HasColumnType("int");
b.Property<int>("ThumbsDown")
.HasColumnType("int");
b.Property<int>("ThumbsUp")
.HasColumnType("int");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("CommentId");
b.HasIndex("PosterUserId");
b.HasIndex("TargetUserId");
b.ToTable("Comments");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.LastMatch", b =>
{
b.Property<int>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<long>("Timestamp")
.HasColumnType("bigint");
b.HasKey("UserId");
b.ToTable("LastMatches");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Location", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("X")
.HasColumnType("int");
b.Property<int>("Y")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("Locations");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Token", b =>
{
b.Property<int>("TokenId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<string>("UserToken")
.HasColumnType("longtext");
b.HasKey("TokenId");
b.ToTable("Tokens");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b =>
{
b.Property<int>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Biography")
.HasColumnType("longtext");
b.Property<string>("BooHash")
.HasColumnType("longtext");
b.Property<int>("CommentCount")
.HasColumnType("int");
b.Property<bool>("CommentsEnabled")
.HasColumnType("tinyint(1)");
b.Property<int>("FavouriteSlotCount")
.HasColumnType("int");
b.Property<int>("FavouriteUserCount")
.HasColumnType("int");
b.Property<int>("Game")
.HasColumnType("int");
b.Property<int>("HeartCount")
.HasColumnType("int");
b.Property<string>("IconHash")
.HasColumnType("longtext");
b.Property<int>("Lists")
.HasColumnType("int");
b.Property<int>("LocationId")
.HasColumnType("int");
b.Property<int>("LolCatFtwCount")
.HasColumnType("int");
b.Property<int>("PhotosByMeCount")
.HasColumnType("int");
b.Property<int>("PhotosWithMeCount")
.HasColumnType("int");
b.Property<string>("Pins")
.HasColumnType("longtext");
b.Property<string>("PlanetHash")
.HasColumnType("longtext");
b.Property<int>("ReviewCount")
.HasColumnType("int");
b.Property<int>("StaffChallengeBronzeCount")
.HasColumnType("int");
b.Property<int>("StaffChallengeGoldCount")
.HasColumnType("int");
b.Property<int>("StaffChallengeSilverCount")
.HasColumnType("int");
b.Property<string>("Username")
.HasColumnType("longtext");
b.Property<string>("YayHash")
.HasColumnType("longtext");
b.HasKey("UserId");
b.HasIndex("LocationId");
b.ToTable("Users");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "HeartedUser")
.WithMany()
.HasForeignKey("HeartedUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("HeartedUser");
b.Navigation("User");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot")
.WithMany()
.HasForeignKey("SlotId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Slot");
b.Navigation("User");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot")
.WithMany()
.HasForeignKey("SlotId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Slot");
b.Navigation("User");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Creator")
.WithMany()
.HasForeignKey("CreatorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location")
.WithMany()
.HasForeignKey("LocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Creator");
b.Navigation("Location");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Poster")
.WithMany()
.HasForeignKey("PosterUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Target")
.WithMany()
.HasForeignKey("TargetUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Poster");
b.Navigation("Target");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location")
.WithMany()
.HasForeignKey("LocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Location");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace ProjectLighthouse.Migrations
{
public partial class AddMMPickToSlot : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "MMPick",
table: "Slots",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "MMPick",
table: "Slots");
}
}
}

View file

@ -97,18 +97,27 @@ namespace ProjectLighthouse.Migrations
b.Property<string>("Description") b.Property<string>("Description")
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<long>("FirstUploaded")
.HasColumnType("bigint");
b.Property<string>("IconHash") b.Property<string>("IconHash")
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<bool>("InitiallyLocked") b.Property<bool>("InitiallyLocked")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<long>("LastUpdated")
.HasColumnType("bigint");
b.Property<bool>("Lbp1Only") b.Property<bool>("Lbp1Only")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<int>("LocationId") b.Property<int>("LocationId")
.HasColumnType("int"); .HasColumnType("int");
b.Property<bool>("MMPick")
.HasColumnType("tinyint(1)");
b.Property<int>("MaximumPlayers") b.Property<int>("MaximumPlayers")
.HasColumnType("int"); .HasColumnType("int");
@ -289,9 +298,6 @@ namespace ProjectLighthouse.Migrations
b.Property<int>("StaffChallengeSilverCount") b.Property<int>("StaffChallengeSilverCount")
.HasColumnType("int"); .HasColumnType("int");
b.Property<int>("UsedSlots")
.HasColumnType("int");
b.Property<string>("Username") b.Property<string>("Username")
.HasColumnType("longtext"); .HasColumnType("longtext");

View file

@ -1,7 +1,11 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using Kettu; using Kettu;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Profiles;
using LBPUnion.ProjectLighthouse.Types.Settings; using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -17,8 +21,8 @@ namespace LBPUnion.ProjectLighthouse
public static void Main(string[] args) public static void Main(string[] args)
{ {
// Log startup time // Log startup time
Stopwatch startupStopwatch = new(); Stopwatch stopwatch = new();
startupStopwatch.Start(); stopwatch.Start();
// Setup logging // Setup logging
@ -30,39 +34,67 @@ namespace LBPUnion.ProjectLighthouse
Logger.Log("Welcome to Project Lighthouse!", LoggerLevelStartup.Instance); Logger.Log("Welcome to Project Lighthouse!", LoggerLevelStartup.Instance);
Logger.Log("Determining if the database is available...", LoggerLevelStartup.Instance); Logger.Log("Determining if the database is available...", LoggerLevelStartup.Instance);
bool dbConnected = ServerSettings.DbConnected; bool dbConnected = ServerSettings.DbConnected;
Logger.Log(dbConnected ? "Connected to the database." : "Database unavailable! Exiting.", Logger.Log(dbConnected ? "Connected to the database." : "Database unavailable! Exiting.", LoggerLevelStartup.Instance);
LoggerLevelStartup.Instance);
if (dbConnected) if (!dbConnected) Environment.Exit(1);
{ using Database database = new();
Stopwatch migrationStopwatch = new();
migrationStopwatch.Start();
Logger.Log("Migrating database...", LoggerLevelDatabase.Instance); Logger.Log("Migrating database...", LoggerLevelDatabase.Instance);
using Database database = new(); MigrateDatabase(database);
database.Database.Migrate();
migrationStopwatch.Stop(); Logger.Log("Fixing broken timestamps...", LoggerLevelDatabase.Instance);
Logger.Log($"Migration took {migrationStopwatch.ElapsedMilliseconds}ms.", LoggerLevelDatabase.Instance); FixTimestamps(database);
}
else Environment.Exit(1);
startupStopwatch.Stop(); stopwatch.Stop();
Logger.Log( Logger.Log($"Ready! Startup took {stopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", LoggerLevelStartup.Instance);
$"Ready! Startup took {startupStopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...",
LoggerLevelStartup.Instance);
CreateHostBuilder(args).Build().Run(); CreateHostBuilder(args).Build().Run();
} }
public static IHostBuilder CreateHostBuilder(string[] args) => public static void MigrateDatabase(Database database)
Host.CreateDefaultBuilder(args) {
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }) Stopwatch stopwatch = new();
.ConfigureLogging(logging => stopwatch.Start();
{
logging.ClearProviders(); database.Database.Migrate();
logging.Services.TryAddEnumerable(ServiceDescriptor
.Singleton<ILoggerProvider, AspNetToKettuLoggerProvider>()); stopwatch.Stop();
}); Logger.Log($"Migration took {stopwatch.ElapsedMilliseconds}ms.", LoggerLevelDatabase.Instance);
}
public static void FixTimestamps(Database database)
{
Stopwatch stopwatch = new();
stopwatch.Start();
foreach (Slot slot in database.Slots.Where(s => s.FirstUploaded == 0)) slot.FirstUploaded = TimeHelper.UnixTimeMilliseconds();
foreach (Slot slot in database.Slots.Where(s => s.LastUpdated == 0)) slot.LastUpdated = TimeHelper.UnixTimeMilliseconds();
foreach (Comment comment in database.Comments.Where(c => c.Timestamp == 0)) comment.Timestamp = TimeHelper.UnixTimeMilliseconds();
database.SaveChanges();
stopwatch.Stop();
Logger.Log($"Fixing timestamps took {stopwatch.ElapsedMilliseconds}ms.", LoggerLevelDatabase.Instance);
}
public static IHostBuilder CreateHostBuilder(string[] args)
=> Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults
(
webBuilder =>
{
webBuilder.UseStartup<Startup>();
}
)
.ConfigureLogging
(
logging =>
{
logging.ClearProviders();
logging.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, AspNetToKettuLoggerProvider>());
}
);
} }
} }

View file

@ -8,19 +8,23 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.2" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.2"/>
<PackageReference Include="Kettu" Version="1.0.2" /> <PackageReference Include="Kettu" Version="1.1.0"/>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="5.0.11" /> <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="5.0.11"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.11" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.11"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.11"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.11">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="5.0.2" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="5.0.2"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="Types\SlotXsd.cs" /> <Compile Remove="Types\SlotXsd.cs"/>
</ItemGroup>
<ItemGroup>
<Folder Include="logs"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -18,7 +18,7 @@
"ProjectLighthouse": { "ProjectLighthouse": {
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": "true", "dotnetRunMessages": "true",
"applicationUrl": "http://localhost:10060;http://localhost:10061;http://localhost:1062", "applicationUrl": "http://localhost:10060",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_ENVIRONMENT": "Development",
"LIGHTHOUSE_DB_CONNECTION_STRING": "server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse" "LIGHTHOUSE_DB_CONNECTION_STRING": "server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse"

View file

@ -2,26 +2,30 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
namespace LBPUnion.ProjectLighthouse.Serialization { namespace LBPUnion.ProjectLighthouse.Serialization
{
/// <summary> /// <summary>
/// LBP doesn't like the XML serializer by C# that much, and it cant be controlled that much (cant have two root elements), /// LBP doesn't like the XML serializer by C# that much, and it cant be controlled that much (cant have two root
/// so I wrote my own crappy one. /// elements),
/// so I wrote my own crappy one.
/// </summary> /// </summary>
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
public static class LbpSerializer { public static class LbpSerializer
{
public static string BlankElement(string key) => $"<{key}></{key}>"; public static string BlankElement(string key) => $"<{key}></{key}>";
public static string StringElement(KeyValuePair<string, object> pair) => $"<{pair.Key}>{pair.Value}</{pair.Key}>"; public static string StringElement(KeyValuePair<string, object> pair) => $"<{pair.Key}>{pair.Value}</{pair.Key}>";
public static string StringElement(string key, object value) => $"<{key}>{value}</{key}>"; public static string StringElement(string key, object value) => $"<{key}>{value}</{key}>";
public static string TaggedStringElement(KeyValuePair<string, object> pair, KeyValuePair<string, object> tagPair) =>
$"<{pair.Key} {tagPair.Key}=\"{tagPair.Value}\">{pair.Value}</{pair.Key}>";
public static string TaggedStringElement(string key, object value, string tagKey, object tagValue) =>
$"<{key} {tagKey}=\"{tagValue}\">{value}</{key}>";
public static string Elements(params KeyValuePair<string, object>[] pairs) => public static string TaggedStringElement
pairs.Aggregate(string.Empty, (current, pair) => current + StringElement(pair)); (KeyValuePair<string, object> pair, KeyValuePair<string, object> tagPair)
=> $"<{pair.Key} {tagPair.Key}=\"{tagPair.Value}\">{pair.Value}</{pair.Key}>";
public static string TaggedStringElement(string key, object value, string tagKey, object tagValue) => $"<{key} {tagKey}=\"{tagValue}\">{value}</{key}>";
public static string Elements
(params KeyValuePair<string, object>[] pairs)
=> pairs.Aggregate(string.Empty, (current, pair) => current + StringElement(pair));
} }
} }

View file

@ -1,8 +1,11 @@
using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Formatters;
namespace LBPUnion.ProjectLighthouse.Serialization { namespace LBPUnion.ProjectLighthouse.Serialization
public class XmlOutputFormatter : StringOutputFormatter { {
public XmlOutputFormatter() { public class XmlOutputFormatter : StringOutputFormatter
{
public XmlOutputFormatter()
{
this.SupportedMediaTypes.Add("text/xml"); this.SupportedMediaTypes.Add("text/xml");
this.SupportedMediaTypes.Add("application/xml"); this.SupportedMediaTypes.Add("application/xml");
} }

View file

@ -1,9 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.InteropServices.ComTypes;
using System.Threading.Tasks;
using Kettu; using Kettu;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
@ -14,7 +12,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives;
namespace LBPUnion.ProjectLighthouse namespace LBPUnion.ProjectLighthouse
{ {
@ -31,8 +29,8 @@ namespace LBPUnion.ProjectLighthouse
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddControllers(); services.AddControllers();
services.AddMvc(options =>
options.OutputFormatters.Add(new XmlOutputFormatter())); services.AddMvc(options => options.OutputFormatters.Add(new XmlOutputFormatter()));
services.AddDbContext<Database>(); services.AddDbContext<Database>();
} }
@ -44,100 +42,96 @@ namespace LBPUnion.ProjectLighthouse
string serverDigestKey = Environment.GetEnvironmentVariable("SERVER_DIGEST_KEY"); string serverDigestKey = Environment.GetEnvironmentVariable("SERVER_DIGEST_KEY");
if (string.IsNullOrWhiteSpace(serverDigestKey)) if (string.IsNullOrWhiteSpace(serverDigestKey))
{ {
Logger.Log( Logger.Log
(
"The SERVER_DIGEST_KEY environment variable wasn't set, so digest headers won't be set or verified. This will prevent LBP 1 and LBP 3 from working. " + "The SERVER_DIGEST_KEY environment variable wasn't set, so digest headers won't be set or verified. This will prevent LBP 1 and LBP 3 from working. " +
"To increase security, it is recommended that you find and set this variable." "To increase security, it is recommended that you find and set this variable."
); );
computeDigests = false; computeDigests = false;
} }
if (env.IsDevelopment()) if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
{
app.UseDeveloperExceptionPage();
}
// Logs every request and the response to it // Logs every request and the response to it
// Example: "200, 13ms: GET /LITTLEBIGPLANETPS3_XML/news" // Example: "200, 13ms: GET /LITTLEBIGPLANETPS3_XML/news"
// Example: "404, 127ms: GET /asdasd?query=osucookiezi727ppbluezenithtopplayhdhr" // Example: "404, 127ms: GET /asdasd?query=osucookiezi727ppbluezenithtopplayhdhr"
app.Use(async (context, next) => app.Use
{ (
Stopwatch requestStopwatch = new(); async (context, next) =>
requestStopwatch.Start();
// Log all headers.
foreach (var header in context.Request.Headers)
Logger.Log($"{header.Key}: {header.Value}");
context.Request.EnableBuffering(); // Allows us to reset the position of Request.Body for later logging
// Client digest check.
string authCookie;
if (!context.Request.Cookies.TryGetValue("MM_AUTH", out authCookie))
authCookie = string.Empty;
string digestPath = context.Request.Path;
Stream body = context.Request.Body;
if (computeDigests)
{ {
string clientRequestDigest = Stopwatch requestStopwatch = new();
await HashHelper.ComputeDigest(digestPath, authCookie, body, serverDigestKey); requestStopwatch.Start();
// Check the digest we've just calculated against the X-Digest-A header if the game set the header. They should match. // Log all headers.
if (context.Request.Headers.TryGetValue("X-Digest-A", out var sentDigest)) foreach (KeyValuePair<string, StringValues> header in context.Request.Headers) Logger.Log($"{header.Key}: {header.Value}");
context.Request.EnableBuffering(); // Allows us to reset the position of Request.Body for later logging
// Client digest check.
string authCookie;
if (!context.Request.Cookies.TryGetValue("MM_AUTH", out authCookie)) authCookie = string.Empty;
string digestPath = context.Request.Path;
Stream body = context.Request.Body;
if (computeDigests)
{ {
if (clientRequestDigest != sentDigest) string clientRequestDigest = await HashHelper.ComputeDigest(digestPath, authCookie, body, serverDigestKey);
{
context.Response.StatusCode = 403; // Check the digest we've just calculated against the X-Digest-A header if the game set the header. They should match.
context.Abort(); if (context.Request.Headers.TryGetValue("X-Digest-A", out StringValues sentDigest))
return; if (clientRequestDigest != sentDigest)
} {
context.Response.StatusCode = 403;
context.Abort();
return;
}
context.Response.Headers.Add("X-Digest-B", clientRequestDigest);
context.Request.Body.Position = 0;
} }
context.Response.Headers.Add("X-Digest-B", clientRequestDigest); // This does the same as above, but for the response stream.
context.Request.Body.Position = 0; using MemoryStream responseBuffer = new();
} Stream oldResponseStream = context.Response.Body;
context.Response.Body = responseBuffer;
// This does the same as above, but for the response stream. await next(); // Handle the request so we can get the status code from it
using MemoryStream responseBuffer = new MemoryStream();
Stream oldResponseStream = context.Response.Body;
context.Response.Body = responseBuffer;
await next(); // Handle the request so we can get the status code from it // Compute the server digest hash.
if (computeDigests)
{
responseBuffer.Position = 0;
// Compute the server digest hash. // Compute the digest for the response.
if (computeDigests) string serverDigest = await HashHelper.ComputeDigest(context.Request.Path, authCookie, responseBuffer, serverDigestKey);
{ context.Response.Headers.Add("X-Digest-A", serverDigest);
}
// Set the X-Original-Content-Length header to the length of the response buffer.
context.Response.Headers.Add("X-Original-Content-Length", responseBuffer.Length.ToString());
// Copy the buffered response to the actual respose stream.
responseBuffer.Position = 0; responseBuffer.Position = 0;
// Compute the digest for the response. await responseBuffer.CopyToAsync(oldResponseStream);
string serverDigest = await HashHelper.ComputeDigest(context.Request.Path, authCookie,
responseBuffer, serverDigestKey); context.Response.Body = oldResponseStream;
context.Response.Headers.Add("X-Digest-A", serverDigest);
requestStopwatch.Stop();
Logger.Log
(
$"{context.Response.StatusCode}, {requestStopwatch.ElapsedMilliseconds}ms: {context.Request.Method} {context.Request.Path}{context.Request.QueryString}",
LoggerLevelHttp.Instance
);
if (context.Request.Method == "POST")
{
context.Request.Body.Position = 0;
Logger.Log(await new StreamReader(context.Request.Body).ReadToEndAsync(), LoggerLevelHttp.Instance);
}
} }
);
// Set the X-Original-Content-Length header to the length of the response buffer.
context.Response.Headers.Add("X-Original-Content-Length", responseBuffer.Length.ToString());
// Copy the buffered response to the actual respose stream.
responseBuffer.Position = 0;
await responseBuffer.CopyToAsync(oldResponseStream);
context.Response.Body = oldResponseStream;
requestStopwatch.Stop();
Logger.Log(
$"{context.Response.StatusCode}, {requestStopwatch.ElapsedMilliseconds}ms: {context.Request.Method} {context.Request.Path}{context.Request.QueryString}",
LoggerLevelHttp.Instance
);
if (context.Request.Method == "POST")
{
context.Request.Body.Position = 0;
Logger.Log(await new StreamReader(context.Request.Body).ReadToEndAsync(), LoggerLevelHttp.Instance);
}
});
app.UseRouting(); app.UseRouting();

View file

@ -1,21 +1,24 @@
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
namespace LBPUnion.ProjectLighthouse.Types.Files { namespace LBPUnion.ProjectLighthouse.Types.Files
public class LbpFile { {
public LbpFile(byte[] data) { public class LbpFile
this.Data = data; {
this.FileType = FileHelper.DetermineFileType(this.Data);
}
/// <summary>
/// The type of file.
/// </summary>
public LbpFileType FileType;
/// <summary> /// <summary>
/// A buffer of the file's data. /// A buffer of the file's data.
/// </summary> /// </summary>
public readonly byte[] Data; public readonly byte[] Data;
/// <summary>
/// The type of file.
/// </summary>
public LbpFileType FileType;
public LbpFile(byte[] data)
{
this.Data = data;
this.FileType = FileHelper.DetermineFileType(this.Data);
}
} }
} }

View file

@ -1,11 +1,14 @@
namespace LBPUnion.ProjectLighthouse.Types.Files { namespace LBPUnion.ProjectLighthouse.Types.Files
public enum LbpFileType { {
public enum LbpFileType
{
Script, // .ff, FSH Script, // .ff, FSH
Texture, // TEX Texture, // TEX
Level, // LVL Level, // LVL
FileArchive, // .farc, (ends with FARC) FileArchive, // .farc, (ends with FARC)
Plan, // PLN, uploaded with levels Plan, // PLN, uploaded with levels
Voice, // VOP, voice data Voice, // VOP, voice data
Painting, // PTG, paintings
Unknown, Unknown,
} }
} }

View file

@ -2,22 +2,27 @@ using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace LBPUnion.ProjectLighthouse.Types { namespace LBPUnion.ProjectLighthouse.Types
public class HeartedProfile { {
public class HeartedProfile
{
// ReSharper disable once UnusedMember.Global // ReSharper disable once UnusedMember.Global
#if NET6_0_OR_GREATER #if NET6_0_OR_GREATER
[Obsolete($"Use {nameof(HeartedUserId)} instead, this is a key which you should never need to use.")] [Obsolete($"Use {nameof(HeartedUserId)} instead, this is a key which you should never need to use.")]
#else #else
[Obsolete("Use HeartedUserId instead, this is a key which you should never need to use.")] [Obsolete("Use HeartedUserId instead, this is a key which you should never need to use.")]
#endif #endif
[Key] public int HeartedProfileId { get; set; } [Key]
public int HeartedProfileId { get; set; }
public int UserId { get; set; } public int UserId { get; set; }
[ForeignKey(nameof(UserId))] public User User { get; set; } [ForeignKey(nameof(UserId))]
public User User { get; set; }
public int HeartedUserId { get; set; } public int HeartedUserId { get; set; }
[ForeignKey(nameof(HeartedUserId))] public User HeartedUser { get; set; } [ForeignKey(nameof(HeartedUserId))]
public User HeartedUser { get; set; }
} }
} }

View file

@ -1,17 +1,22 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace LBPUnion.ProjectLighthouse.Types.Levels { namespace LBPUnion.ProjectLighthouse.Types.Levels
public class HeartedLevel { {
public class HeartedLevel
{
// ReSharper disable once UnusedMember.Global // ReSharper disable once UnusedMember.Global
[Key] public int HeartedLevelId { get; set; } [Key]
public int HeartedLevelId { get; set; }
public int UserId { get; set; } public int UserId { get; set; }
[ForeignKey(nameof(UserId))] public User User { get; set; } [ForeignKey(nameof(UserId))]
public User User { get; set; }
public int SlotId { get; set; } public int SlotId { get; set; }
[ForeignKey(nameof(SlotId))] public Slot Slot { get; set; } [ForeignKey(nameof(SlotId))]
public Slot Slot { get; set; }
} }
} }

View file

@ -1,12 +1,14 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
namespace LBPUnion.ProjectLighthouse.Types.Levels { namespace LBPUnion.ProjectLighthouse.Types.Levels
{
/// <summary> /// <summary>
/// A series of tags that can be applied to a level /// A series of tags that can be applied to a level
/// </summary> /// </summary>
[SuppressMessage("ReSharper", "InconsistentNaming")] [SuppressMessage("ReSharper", "InconsistentNaming")]
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
public enum LevelTags { public enum LevelTags
{
Brilliant, Brilliant,
Beautiful, Beautiful,
Funky, Funky,

View file

@ -1,16 +1,19 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace LBPUnion.ProjectLighthouse.Types.Levels { namespace LBPUnion.ProjectLighthouse.Types.Levels
public class QueuedLevel { {
public class QueuedLevel
{
// ReSharper disable once UnusedMember.Global // ReSharper disable once UnusedMember.Global
[Key] public int QueuedLevelId { get; set; } [Key]
public int QueuedLevelId { get; set; }
public int UserId { get; set; } public int UserId { get; set; }
[ForeignKey(nameof(UserId))] [ForeignKey(nameof(UserId))]
public User User { get; set; } public User User { get; set; }
public int SlotId { get; set; } public int SlotId { get; set; }
[ForeignKey(nameof(SlotId))] [ForeignKey(nameof(SlotId))]

View file

@ -5,92 +5,103 @@ using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types.Profiles; using LBPUnion.ProjectLighthouse.Types.Profiles;
namespace LBPUnion.ProjectLighthouse.Types.Levels { namespace LBPUnion.ProjectLighthouse.Types.Levels
{
/// <summary> /// <summary>
/// A LittleBigPlanet level. /// A LittleBigPlanet level.
/// </summary> /// </summary>
[XmlRoot("slot"), XmlType("slot")] [XmlRoot("slot")]
public class Slot { [XmlType("slot")]
public class Slot
{
[XmlAttribute("type")] [XmlAttribute("type")]
[NotMapped] [NotMapped]
public string Type { get; set; } public string Type { get; set; }
[Key] [Key]
[XmlIgnore] [XmlElement("id")]
public int SlotId { get; set; } public int SlotId { get; set; }
[XmlElement("name")] [XmlElement("name")]
public string Name { get; set; } public string Name { get; set; }
[XmlElement("description")] [XmlElement("description")]
public string Description { get; set; } public string Description { get; set; }
[XmlElement("icon")] [XmlElement("icon")]
public string IconHash { get; set; } public string IconHash { get; set; }
[XmlElement("rootLevel")] [XmlElement("rootLevel")]
public string RootLevel { get; set; } public string RootLevel { get; set; }
public string ResourceCollection { get; set; } public string ResourceCollection { get; set; }
[NotMapped] [NotMapped]
[XmlElement("resource")] [XmlElement("resource")]
public string[] Resources { public string[] Resources {
get => this.ResourceCollection.Split(","); get => this.ResourceCollection.Split(",");
set => this.ResourceCollection = string.Join(',', value); set => this.ResourceCollection = string.Join(',', value);
} }
[XmlIgnore] [XmlIgnore]
public int LocationId { get; set; } public int LocationId { get; set; }
[XmlIgnore] [XmlIgnore]
public int CreatorId { get; set; } public int CreatorId { get; set; }
[ForeignKey(nameof(CreatorId))] [ForeignKey(nameof(CreatorId))]
public User Creator { get; set; } public User Creator { get; set; }
/// <summary> /// <summary>
/// The location of the level on the creator's earth /// The location of the level on the creator's earth
/// </summary> /// </summary>
[XmlElement("location")] [XmlElement("location")]
[ForeignKey(nameof(LocationId))] [ForeignKey(nameof(LocationId))]
public Location Location { get; set; } public Location Location { get; set; }
[XmlElement("initiallyLocked")] [XmlElement("initiallyLocked")]
public bool InitiallyLocked { get; set; } public bool InitiallyLocked { get; set; }
[XmlElement("isSubLevel")] [XmlElement("isSubLevel")]
public bool SubLevel { get; set; } public bool SubLevel { get; set; }
[XmlElement("isLBP1Only")] [XmlElement("isLBP1Only")]
public bool Lbp1Only { get; set; } public bool Lbp1Only { get; set; }
[XmlElement("shareable")] [XmlElement("shareable")]
public int Shareable { get; set; } public int Shareable { get; set; }
[XmlElement("authorLabels")] [XmlElement("authorLabels")]
public string AuthorLabels { get; set; } public string AuthorLabels { get; set; }
[XmlElement("background")] [XmlElement("background")]
public string BackgroundHash { get; set; } = ""; public string BackgroundHash { get; set; } = "";
[XmlElement("minPlayers")] [XmlElement("minPlayers")]
public int MinimumPlayers { get; set; } public int MinimumPlayers { get; set; }
[XmlElement("maxPlayers")] [XmlElement("maxPlayers")]
public int MaximumPlayers { get; set; } public int MaximumPlayers { get; set; }
[XmlElement("moveRequired")] [XmlElement("moveRequired")]
public bool MoveRequired { get; set; } public bool MoveRequired { get; set; }
public string SerializeResources() { [XmlIgnore]
return this.Resources public long FirstUploaded { get; set; }
.Aggregate("", (current, resource) =>
current + LbpSerializer.StringElement("resource", resource)); [XmlIgnore]
public long LastUpdated { get; set; }
[XmlIgnore]
public bool MMPick { get; set; }
public string SerializeResources()
{
return this.Resources.Aggregate("", (current, resource) => current + LbpSerializer.StringElement("resource", resource));
} }
public string Serialize() { public string Serialize()
{
string slotData = LbpSerializer.StringElement("name", this.Name) + string slotData = LbpSerializer.StringElement("name", this.Name) +
LbpSerializer.StringElement("id", this.SlotId) + LbpSerializer.StringElement("id", this.SlotId) +
LbpSerializer.StringElement("game", 1) + LbpSerializer.StringElement("game", 1) +
@ -107,8 +118,11 @@ namespace LBPUnion.ProjectLighthouse.Types.Levels {
LbpSerializer.StringElement("background", this.BackgroundHash) + LbpSerializer.StringElement("background", this.BackgroundHash) +
LbpSerializer.StringElement("minPlayers", this.MinimumPlayers) + LbpSerializer.StringElement("minPlayers", this.MinimumPlayers) +
LbpSerializer.StringElement("maxPlayers", this.MaximumPlayers) + LbpSerializer.StringElement("maxPlayers", this.MaximumPlayers) +
LbpSerializer.StringElement("moveRequired", this.MoveRequired); LbpSerializer.StringElement("moveRequired", this.MoveRequired) +
LbpSerializer.StringElement("firstPublished", this.FirstUploaded) +
LbpSerializer.StringElement("lastUpdated", this.LastUpdated) +
LbpSerializer.StringElement("mmpick", this.MMPick);
return LbpSerializer.TaggedStringElement("slot", slotData, "type", "user"); return LbpSerializer.TaggedStringElement("slot", slotData, "type", "user");
} }
} }

View file

@ -1,39 +1,43 @@
#nullable enable
using System;
using System.IO; using System.IO;
using System.Text; using System.Text;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
namespace LBPUnion.ProjectLighthouse.Types { namespace LBPUnion.ProjectLighthouse.Types
// This is all the information I can understand for now. More testing is required. {
// Example data:
// - LBP2 digital, with the RPCN username `literally1984`
// POST /LITTLEBIGPLANETPS3_XML/login?applicationID=21414&languageID=1&lbp2=1&beta=0&titleID=NPUA80662&country=us
// !<21>0256333||x||<7C><>Y literally198bruUP9000-NPUA80662_008D
// - LBP2 digital, with the RPCN username `jvyden`
// POST /LITTLEBIGPLANETPS3_XML/login?applicationID=21414&languageID=1&lbp2=1&beta=0&titleID=NPUA80662&country=us
// !<21>0220333||/u||=0<> jvydebruUP9000-NPUA80662_008D
/// <summary> /// <summary>
/// The data sent from POST /LOGIN. /// The data sent from POST /LOGIN.
/// </summary> /// </summary>
public class LoginData { public class LoginData
public string Username { get; set; } {
// public string GameVersion { get; set; }
// public int UnknownNumber { get; set; } // Seems to increment by 1000 every login attempt
public static LoginData CreateFromString(string str) { public static readonly string UsernamePrefix = Encoding.ASCII.GetString
do { (
str = str.Replace("\b", string.Empty); // Trim backspace characters new byte[]
} while(str.Contains('\b')); {
0x04, 0x00, 0x20,
}
);
public string Username { get; set; } = null!;
/// <summary>
/// Converts a X-I-5 Ticket into `LoginData`.
/// https://www.psdevwiki.com/ps3/X-I-5-Ticket
/// </summary>
public static LoginData? CreateFromString(string str)
{
str = str.Replace("\b", ""); // Remove backspace characters
using MemoryStream ms = new(Encoding.ASCII.GetBytes(str)); using MemoryStream ms = new(Encoding.ASCII.GetBytes(str));
using BinaryReader reader = new(ms); using BinaryReader reader = new(ms);
if (!str.Contains(UsernamePrefix)) return null;
LoginData loginData = new(); LoginData loginData = new();
BinaryHelper.ReadUntilByte(reader, 0x20); // Skips to relevant part reader.BaseStream.Position = str.IndexOf(UsernamePrefix, StringComparison.Ordinal) + UsernamePrefix.Length;
// byte[] endBytes = reader.ReadBytes((int)(ms.Length - reader.BaseStream.Position));
// string end = Encoding.ASCII.GetString(endBytes);
loginData.Username = BinaryHelper.ReadString(reader).Replace("\0", string.Empty); loginData.Username = BinaryHelper.ReadString(reader).Replace("\0", string.Empty);
return loginData; return loginData;

View file

@ -2,23 +2,23 @@ using System.Collections.Generic;
using System.Xml.Serialization; using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
namespace LBPUnion.ProjectLighthouse.Types { namespace LBPUnion.ProjectLighthouse.Types
{
/// <summary> /// <summary>
/// Response to POST /login /// Response to POST /login
/// </summary> /// </summary>
[XmlRoot("loginResult"), XmlType("loginResult")] [XmlRoot("loginResult")]
public class LoginResult { [XmlType("loginResult")]
public class LoginResult
{
[XmlElement("authTicket")] [XmlElement("authTicket")]
public string AuthTicket { get; set; } public string AuthTicket { get; set; }
[XmlElement("lbpEnvVer")] [XmlElement("lbpEnvVer")]
public string LbpEnvVer { get; set; } public string LbpEnvVer { get; set; }
public string Serialize() { public string Serialize()
return LbpSerializer.Elements( => LbpSerializer.Elements
new KeyValuePair<string, object>("authTicket", this.AuthTicket), (new KeyValuePair<string, object>("authTicket", this.AuthTicket), new KeyValuePair<string, object>("lbpEnvVer", this.LbpEnvVer));
new KeyValuePair<string, object>("lbpEnvVer", this.LbpEnvVer)
);
}
} }
} }

View file

@ -1,5 +1,5 @@
namespace LBPUnion.ProjectLighthouse.Types.Match { namespace LBPUnion.ProjectLighthouse.Types.Match
public interface IMatchData { {
public interface IMatchData
} {}
} }

View file

@ -1,5 +1,7 @@
namespace LBPUnion.ProjectLighthouse.Types.Match { namespace LBPUnion.ProjectLighthouse.Types.Match
public enum RoomState { {
public enum RoomState
{
Idle = 0, Idle = 0,
LookingForPlayersForLevel = 1, LookingForPlayersForLevel = 1,
Unknown = 2, Unknown = 2,

View file

@ -1,5 +1,7 @@
namespace LBPUnion.ProjectLighthouse.Types.Match { namespace LBPUnion.ProjectLighthouse.Types.Match
public class UpdateMyPlayerData : IMatchData { {
public class UpdateMyPlayerData : IMatchData
{
public string Player; public string Player;
} }
} }

View file

@ -1,7 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace LBPUnion.ProjectLighthouse.Types.Match { namespace LBPUnion.ProjectLighthouse.Types.Match
public class UpdatePlayersInRoom : IMatchData { {
public class UpdatePlayersInRoom : IMatchData
{
public List<string> Players; public List<string> Players;
public List<string> Reservations; public List<string> Reservations;
} }

View file

@ -1,10 +1,12 @@
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
namespace LBPUnion.ProjectLighthouse.Types.News { namespace LBPUnion.ProjectLighthouse.Types.News
{
/// <summary> /// <summary>
/// Used on the info moon on LBP1. Broken for unknown reasons /// Used on the info moon on LBP1. Broken for unknown reasons
/// </summary> /// </summary>
public class NewsEntry { public class NewsEntry
{
public int Id { get; set; } public int Id { get; set; }
public string Title { get; set; } public string Title { get; set; }
public string Summary { get; set; } public string Summary { get; set; }
@ -13,14 +15,13 @@ namespace LBPUnion.ProjectLighthouse.Types.News {
public string Category { get; set; } public string Category { get; set; }
public long Date { get; set; } public long Date { get; set; }
public string Serialize() { public string Serialize()
return LbpSerializer.StringElement("id", this.Id) + => LbpSerializer.StringElement("id", this.Id) +
LbpSerializer.StringElement("title", this.Title) + LbpSerializer.StringElement("title", this.Title) +
LbpSerializer.StringElement("summary", this.Summary) + LbpSerializer.StringElement("summary", this.Summary) +
LbpSerializer.StringElement("text", this.Text) + LbpSerializer.StringElement("text", this.Text) +
LbpSerializer.StringElement("date", this.Date) + LbpSerializer.StringElement("date", this.Date) +
this.Image.Serialize() + this.Image.Serialize() +
LbpSerializer.StringElement("category", this.Category); LbpSerializer.StringElement("category", this.Category);
}
} }
} }

View file

@ -1,14 +1,13 @@
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
namespace LBPUnion.ProjectLighthouse.Types.News { namespace LBPUnion.ProjectLighthouse.Types.News
public class NewsImage { {
public class NewsImage
{
public string Hash { get; set; } public string Hash { get; set; }
public string Alignment { get; set; } public string Alignment { get; set; }
public string Serialize() { public string Serialize()
return LbpSerializer.StringElement("image", => LbpSerializer.StringElement("image", LbpSerializer.StringElement("hash", this.Hash) + LbpSerializer.StringElement("alignment", this.Alignment));
LbpSerializer.StringElement("hash", this.Hash) +
LbpSerializer.StringElement("alignment", this.Alignment));
}
} }
} }

View file

@ -1,22 +1,26 @@
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Types.Profiles { namespace LBPUnion.ProjectLighthouse.Types.Profiles
{
[Keyless] [Keyless]
public class ClientsConnected { public class ClientsConnected
{
public bool Lbp1 { get; set; } public bool Lbp1 { get; set; }
public bool Lbp2 { get; set; } public bool Lbp2 { get; set; }
public bool LbpMe { get; set; } public bool LbpMe { get; set; }
public bool Lbp3Ps3 { get; set; } public bool Lbp3Ps3 { get; set; }
public bool Lbp3Ps4 { get; set; } public bool Lbp3Ps4 { get; set; }
public string Serialize() { public string Serialize()
return LbpSerializer.StringElement("clientsConnected", => LbpSerializer.StringElement
(
"clientsConnected",
LbpSerializer.StringElement("lbp1", this.Lbp1) + LbpSerializer.StringElement("lbp1", this.Lbp1) +
LbpSerializer.StringElement("lbp2", this.Lbp2) + LbpSerializer.StringElement("lbp2", this.Lbp2) +
LbpSerializer.StringElement("lbpme", this.LbpMe) + LbpSerializer.StringElement("lbpme", this.LbpMe) +
LbpSerializer.StringElement("lbp3ps3", this.Lbp3Ps3) + LbpSerializer.StringElement("lbp3ps3", this.Lbp3Ps3) +
LbpSerializer.StringElement("lbp3ps4", this.Lbp3Ps4)); LbpSerializer.StringElement("lbp3ps4", this.Lbp3Ps4)
} );
} }
} }

View file

@ -3,20 +3,23 @@ using System.ComponentModel.DataAnnotations.Schema;
using System.Xml.Serialization; using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
namespace LBPUnion.ProjectLighthouse.Types.Profiles { namespace LBPUnion.ProjectLighthouse.Types.Profiles
[XmlRoot("comment"), XmlType("comment")] {
public class Comment { [XmlRoot("comment")]
[XmlType("comment")]
public class Comment
{
[Key] [Key]
[XmlAttribute("id")] [XmlAttribute("id")]
public int CommentId { get; set; } public int CommentId { get; set; }
public int PosterUserId { get; set; } public int PosterUserId { get; set; }
public int TargetUserId { get; set; } public int TargetUserId { get; set; }
[ForeignKey(nameof(PosterUserId))] [ForeignKey(nameof(PosterUserId))]
public User Poster { get; set; } public User Poster { get; set; }
[ForeignKey(nameof(TargetUserId))] [ForeignKey(nameof(TargetUserId))]
public User Target { get; set; } public User Target { get; set; }
@ -24,24 +27,22 @@ namespace LBPUnion.ProjectLighthouse.Types.Profiles {
[XmlElement("message")] [XmlElement("message")]
public string Message { get; set; } public string Message { get; set; }
public int ThumbsUp { get; set; } public int ThumbsUp { get; set; }
public int ThumbsDown { get; set; } public int ThumbsDown { get; set; }
private string serialize() { private string serialize()
return LbpSerializer.StringElement("id", this.CommentId) + => LbpSerializer.StringElement("id", this.CommentId) +
LbpSerializer.StringElement("npHandle", this.Poster.Username) + LbpSerializer.StringElement("npHandle", this.Poster.Username) +
LbpSerializer.StringElement("timestamp", this.Timestamp) + LbpSerializer.StringElement("timestamp", this.Timestamp) +
LbpSerializer.StringElement("message", this.Message) + LbpSerializer.StringElement("message", this.Message) +
LbpSerializer.StringElement("thumbsup", this.ThumbsUp) + LbpSerializer.StringElement("thumbsup", this.ThumbsUp) +
LbpSerializer.StringElement("thumbsdown", this.ThumbsDown); LbpSerializer.StringElement("thumbsdown", this.ThumbsDown);
}
public string Serialize(int yourThumb) { public string Serialize
return LbpSerializer.StringElement("comment", this.serialize() + LbpSerializer.StringElement("yourthumb", yourThumb)); (int yourThumb)
} => LbpSerializer.StringElement("comment", this.serialize() + LbpSerializer.StringElement("yourthumb", yourThumb));
public string Serialize() { public string Serialize() => LbpSerializer.StringElement("comment", this.serialize());
return LbpSerializer.StringElement("comment", this.serialize());
}
} }
} }

View file

@ -1,8 +1,12 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace LBPUnion.ProjectLighthouse.Types.Profiles { namespace LBPUnion.ProjectLighthouse.Types.Profiles
public class LastMatch { {
[Key] public int UserId { get; set; } public class LastMatch
{
[Key]
public int UserId { get; set; }
public long Timestamp { get; set; } public long Timestamp { get; set; }
} }
} }

View file

@ -1,17 +1,24 @@
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
namespace LBPUnion.ProjectLighthouse.Types.Profiles { namespace LBPUnion.ProjectLighthouse.Types.Profiles
{
/// <summary> /// <summary>
/// The location of a slot on a planet. /// The location of a slot on a planet.
/// </summary> /// </summary>
public class Location { [XmlRoot("location")]
[XmlType("location")]
public class Location
{
[XmlIgnore]
public int Id { get; set; } public int Id { get; set; }
[XmlElement("x")]
public int X { get; set; } public int X { get; set; }
[XmlElement("y")]
public int Y { get; set; } public int Y { get; set; }
public string Serialize() { public string Serialize() => LbpSerializer.StringElement("x", this.X) + LbpSerializer.StringElement("y", this.Y);
return LbpSerializer.StringElement("x", this.X) +
LbpSerializer.StringElement("y", this.Y);
}
} }
} }

View file

@ -1,9 +1,12 @@
using System.Xml.Serialization; using System.Xml.Serialization;
namespace LBPUnion.ProjectLighthouse.Types { namespace LBPUnion.ProjectLighthouse.Types
[XmlRoot("resources"), XmlType("resources")] {
public class ResourceList { [XmlRoot("resources")]
[XmlElement("resource")] [XmlType("resources")]
public class ResourceList
{
[XmlElement("resource")]
public string[] Resources; public string[] Resources;
} }
} }

View file

@ -1,15 +1,17 @@
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
namespace LBPUnion.ProjectLighthouse.Types.Settings { namespace LBPUnion.ProjectLighthouse.Types.Settings
public class PrivacySettings { {
public class PrivacySettings
{
public string LevelVisibility { get; set; } public string LevelVisibility { get; set; }
public string ProfileVisibility { get; set; } public string ProfileVisibility { get; set; }
public string Serialize() { public string Serialize()
return LbpSerializer.StringElement("privacySettings", => LbpSerializer.StringElement
LbpSerializer.StringElement("levelVisibility", this.LevelVisibility) + (
LbpSerializer.StringElement("profileVisibility", this.ProfileVisibility) "privacySettings",
LbpSerializer.StringElement("levelVisibility", this.LevelVisibility) + LbpSerializer.StringElement("profileVisibility", this.ProfileVisibility)
); );
}
} }
} }

View file

@ -3,23 +3,25 @@ using System;
using Kettu; using Kettu;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
namespace LBPUnion.ProjectLighthouse.Types.Settings { namespace LBPUnion.ProjectLighthouse.Types.Settings
public static class ServerSettings { {
public static class ServerSettings
{
/// <summary> /// <summary>
/// The maximum amount of slots allowed on users' earth /// The maximum amount of slots allowed on users' earth
/// </summary> /// </summary>
public const int EntitledSlots = int.MaxValue; public const int EntitledSlots = 50;
public const int ListsQuota = 20; public const int ListsQuota = 50;
public const string ServerName = "ProjectLighthouse"; public const string ServerName = "ProjectLighthouse";
private static string? dbConnectionString; private static string? dbConnectionString;
public static string DbConnectionString { public static string DbConnectionString {
get { get {
if(dbConnectionString == null) { if (dbConnectionString == null) return dbConnectionString = Environment.GetEnvironmentVariable("LIGHTHOUSE_DB_CONNECTION_STRING") ?? "";
return dbConnectionString = Environment.GetEnvironmentVariable("LIGHTHOUSE_DB_CONNECTION_STRING") ?? "";
}
return dbConnectionString; return dbConnectionString;
} }
set => dbConnectionString = value; set => dbConnectionString = value;
@ -27,10 +29,12 @@ namespace LBPUnion.ProjectLighthouse.Types.Settings {
public static bool DbConnected { public static bool DbConnected {
get { get {
try { try
{
return new Database().Database.CanConnect(); return new Database().Database.CanConnect();
} }
catch(Exception e) { catch(Exception e)
{
Logger.Log(e.ToString(), LoggerLevelDatabase.Instance); Logger.Log(e.ToString(), LoggerLevelDatabase.Instance);
return false; return false;
} }

View file

@ -1,9 +1,13 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace LBPUnion.ProjectLighthouse.Types { namespace LBPUnion.ProjectLighthouse.Types
public class Token { {
public class Token
{
// ReSharper disable once UnusedMember.Global // ReSharper disable once UnusedMember.Global
[Key] public int TokenId { get; set; } [Key]
public int TokenId { get; set; }
public int UserId { get; set; } public int UserId { get; set; }
public string UserToken { get; set; } public string UserToken { get; set; }
} }

View file

@ -1,10 +1,16 @@
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types.Profiles; using LBPUnion.ProjectLighthouse.Types.Profiles;
using LBPUnion.ProjectLighthouse.Types.Settings; using LBPUnion.ProjectLighthouse.Types.Settings;
namespace LBPUnion.ProjectLighthouse.Types { namespace LBPUnion.ProjectLighthouse.Types
public class User { {
public class User
{
// [NotMapped]
public readonly ClientsConnected ClientsConnected = new();
public int UserId { get; set; } public int UserId { get; set; }
public string Username { get; set; } public string Username { get; set; }
public string IconHash { get; set; } public string IconHash { get; set; }
@ -13,21 +19,22 @@ namespace LBPUnion.ProjectLighthouse.Types {
public int HeartCount { get; set; } public int HeartCount { get; set; }
public string YayHash { get; set; } public string YayHash { get; set; }
public string BooHash { get; set; } public string BooHash { get; set; }
/// <summary> /// <summary>
/// A user-customizable biography shown on the profile card /// A user-customizable biography shown on the profile card
/// </summary> /// </summary>
public string Biography { get; set; } public string Biography { get; set; }
public int ReviewCount { get; set; } public int ReviewCount { get; set; }
public int CommentCount { get; set; } public int CommentCount { get; set; }
public int PhotosByMeCount { get; set; } public int PhotosByMeCount { get; set; }
public int PhotosWithMeCount { get; set; } public int PhotosWithMeCount { get; set; }
public bool CommentsEnabled { get; set; } public bool CommentsEnabled { get; set; }
public int LocationId { get; set; } public int LocationId { get; set; }
/// <summary> /// <summary>
/// The location of the profile card on the user's earth /// The location of the profile card on the user's earth
/// </summary> /// </summary>
[ForeignKey("LocationId")] [ForeignKey("LocationId")]
public Location Location { get; set; } public Location Location { get; set; }
@ -41,50 +48,9 @@ namespace LBPUnion.ProjectLighthouse.Types {
public int StaffChallengeBronzeCount { get; set; } public int StaffChallengeBronzeCount { get; set; }
public string PlanetHash { get; set; } = ""; public string PlanetHash { get; set; } = "";
// [NotMapped]
public readonly ClientsConnected ClientsConnected = new();
#region Slots
/// <summary> public string Serialize()
/// The number of used slots on the earth {
/// </summary>
public int UsedSlots { get; set; }
/// <summary>
/// The number of slots remaining on the earth
/// </summary>
public int FreeSlots => ServerSettings.EntitledSlots - this.UsedSlots;
private static readonly string[] slotTypes = {
// "lbp1",
"lbp2",
"lbp3",
"crossControl",
};
private string SerializeSlots() {
string slots = string.Empty;
slots += LbpSerializer.StringElement("lbp1UsedSlots", this.UsedSlots);
slots += LbpSerializer.StringElement("entitledSlots", ServerSettings.EntitledSlots);
slots += LbpSerializer.StringElement("freeSlots", this.FreeSlots);
foreach(string slotType in slotTypes) {
slots += LbpSerializer.StringElement(slotType + "UsedSlots", this.UsedSlots);
slots += LbpSerializer.StringElement(slotType + "EntitledSlots", ServerSettings.EntitledSlots);
// ReSharper disable once StringLiteralTypo
slots += LbpSerializer.StringElement(slotType + slotType == "crossControl" ? "PurchsedSlots" : "PurchasedSlots", 0);
slots += LbpSerializer.StringElement(slotType + "FreeSlots", this.FreeSlots);
}
return slots;
}
#endregion Slots
public string Serialize() {
string user = LbpSerializer.TaggedStringElement("npHandle", this.Username, "icon", this.IconHash) + string user = LbpSerializer.TaggedStringElement("npHandle", this.Username, "icon", this.IconHash) +
LbpSerializer.StringElement("game", this.Game) + LbpSerializer.StringElement("game", this.Game) +
this.SerializeSlots() + this.SerializeSlots() +
@ -110,8 +76,55 @@ namespace LBPUnion.ProjectLighthouse.Types {
LbpSerializer.StringElement("planets", this.PlanetHash) + LbpSerializer.StringElement("planets", this.PlanetHash) +
LbpSerializer.BlankElement("photos") + LbpSerializer.BlankElement("photos") +
this.ClientsConnected.Serialize(); this.ClientsConnected.Serialize();
return LbpSerializer.TaggedStringElement("user", user, "type", "user"); return LbpSerializer.TaggedStringElement("user", user, "type", "user");
} }
#region Slots
/// <summary>
/// The number of used slots on the earth
/// </summary>
[NotMapped]
public int UsedSlots {
get {
using Database database = new();
return database.Slots.Count(s => s.CreatorId == this.UserId);
}
}
/// <summary>
/// The number of slots remaining on the earth
/// </summary>
public int FreeSlots => ServerSettings.EntitledSlots - this.UsedSlots;
private static readonly string[] slotTypes =
{
// "lbp1",
"lbp2", "lbp3", "crossControl",
};
private string SerializeSlots()
{
string slots = string.Empty;
slots += LbpSerializer.StringElement("lbp1UsedSlots", this.UsedSlots);
slots += LbpSerializer.StringElement("entitledSlots", ServerSettings.EntitledSlots);
slots += LbpSerializer.StringElement("freeSlots", this.FreeSlots);
foreach (string slotType in slotTypes)
{
slots += LbpSerializer.StringElement(slotType + "UsedSlots", this.UsedSlots);
slots += LbpSerializer.StringElement(slotType + "EntitledSlots", ServerSettings.EntitledSlots);
// ReSharper disable once StringLiteralTypo
slots += LbpSerializer.StringElement(slotType + slotType == "crossControl" ? "PurchsedSlots" : "PurchasedSlots", 0);
slots += LbpSerializer.StringElement(slotType + "FreeSlots", this.FreeSlots);
}
return slots;
}
#endregion Slots
} }
} }

View file

@ -1,58 +1,84 @@
# Project Lighthouse # Project Lighthouse
Project Lighthouse is an umbrella project for all work to investigate and develop private servers for LittleBigPlanet. Project Lighthouse is an umbrella project for all work to investigate and develop private servers for LittleBigPlanet.
This project is the main server component that LittleBigPlanet games connect to. This project is the main server component that LittleBigPlanet games connect to.
## WARNING! ## WARNING!
This is beta software, and thus is not ready for public use yet.
We're not responsible if someone connects and hacks your entire machine and deletes all your files. This is beta software, and thus is not ready for public use yet. We're not responsible if someone connects and hacks
your entire machine and deletes all your files.
That said, feel free to develop privately! That said, feel free to develop privately!
## Building ## Building
This will be written when we're out of beta. Consider this your barrier to entry ;). This will be written when we're out of beta. Consider this your barrier to entry ;).
## Running ## Running
Lighthouse requires a MySQL database at this time.
For Linux users running docker, one can be set up using the `docker-compose.yml` file in the root of the project folder.
Next, make sure the `LIGHTHOUSE_DB_CONNECTION_STRING` environment variable is set correctly. Lighthouse requires a MySQL database at this time. For Linux users running docker, one can be set up using
By default, it is `server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse`. If you are running the database via the `docker-compose.yml` file in the root of the project folder.
the above `docker-compose.yml` you shouldn't need to change this. For other development/especially production environments
Next, make sure the `LIGHTHOUSE_DB_CONNECTION_STRING` environment variable is set correctly. By default, it
is `server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse`. If you are running the database via the
above `docker-compose.yml` you shouldn't need to change this. For other development/especially production environments
you will need to change this. you will need to change this.
Once you've gotten MySQL running you can run Lighthouse. It will take care of the rest. Once you've gotten MySQL running you can run Lighthouse. It will take care of the rest.
## Connecting ## Connecting
PS3 is difficult to set up, so I will be going over how to set up RPCS3 instead. A guide will be coming for PS3 closer to release.
You can also follow this guide if you want to learn how to modify your EBOOT.
*Note: This requires a modified copy of RPCS3. You can find a working patch [here](https://gist.github.com/jvyden/0d9619f7dd3dbc49f7583486bdacad75).* PS3 is difficult to set up, so I will be going over how to set up RPCS3 instead. A guide will be coming for PS3 closer
to release. You can also follow this guide if you want to learn how to modify your EBOOT.
Start by getting a copy of LittleBigPlanet 2 installed. It can be digital (NPUA80662) or disc (BCUS98245). *Note: This requires a modified copy of RPCS3. You can find a working
I won't get into how because if you got this far you should already know what you're doing. For those that don't, the [RPCS3 Quickstart Guide](https://rpcs3.net/quickstart) should cover it. patch [here](https://gist.github.com/jvyden/0d9619f7dd3dbc49f7583486bdacad75).*
Next, download [UnionPatcher](https://github.com/LBPUnion/UnionPatcher/). Binaries can be found by reading the README.md file. Start by getting a copy of LittleBigPlanet 2 installed. It can be digital (NPUA80662) or disc (BCUS98245). I won't get
into how because if you got this far you should already know what you're doing. For those that don't,
the [RPCS3 Quickstart Guide](https://rpcs3.net/quickstart) should cover it.
You should have everything you need now, so open up RPCS3 and go to Utilities -> Decrypt PS3 Binaries. Point this to `rpcs3/dev_hdd0/game/(title id)/USRDIR/EBOOT.BIN`. Next, download [UnionPatcher](https://github.com/LBPUnion/UnionPatcher/). Binaries can be found by reading the README.md
file.
This should give you a file named `EBOOT.elf` in the same folder. Next, fire up UnionPatcher (making sure to select the correct project to start, e.g. on Mac launch `UnionPatcher.Gui.MacOS`.) You should have everything you need now, so open up RPCS3 and go to Utilities -> Decrypt PS3 Binaries. Point this
to `rpcs3/dev_hdd0/game/(title id)/USRDIR/EBOOT.BIN`.
Now that you have your decrypted eboot, open UnionPatcher and select the `EBOOT.elf` you got earlier in the top box, enter `http://localhost:10060/LITTLEBIGPLANETPS3_XML` in the second, and the output filename in the third. This should give you a file named `EBOOT.elf` in the same folder. Next, fire up UnionPatcher (making sure to select the
For this guide I'll use `EBOOTlocalhost.elf`. correct project to start, e.g. on Mac launch `UnionPatcher.Gui.MacOS`.)
Now that you have your decrypted eboot, open UnionPatcher and select the `EBOOT.elf` you got earlier in the top box,
enter `http://localhost:10060/LITTLEBIGPLANETPS3_XML` in the second, and the output filename in the third. For this
guide I'll use `EBOOTlocalhost.elf`.
Now, copy the `EBOOTlocalhost.elf` file to where you got your `EBOOT.elf` file from, and you're now good to go. Now, copy the `EBOOTlocalhost.elf` file to where you got your `EBOOT.elf` file from, and you're now good to go.
To launch the game with the patched EBOOT, open up RPCS3, go to File, Boot SELF/ELF, and open up `EBOOTlocalhost.elf`. To launch the game with the patched EBOOT, open up RPCS3, go to File, Boot SELF/ELF, and open up `EBOOTlocalhost.elf`.
Assuming you are running the patched version of RPCS3, you patched the file correctly, the database is migrated, and Lighthouse is running, the game should now connect. Assuming you are running the patched version of RPCS3, you patched the file correctly, the database is migrated, and
Lighthouse is running, the game should now connect.
Finally, take a break. Chances are that took a while. Finally, take a break. Chances are that took a while.
## Contributing Tips ## Contributing Tips
### Database ### Database
Some modifications may require updates to the database schema. You can automatically create a migration file by: Some modifications may require updates to the database schema. You can automatically create a migration file by:
1. Making sure the tools are installed. You can do this by running `dotnet tool restore`. 1. Making sure the tools are installed. You can do this by running `dotnet tool restore`.
2. Making sure `LIGHTHOUSE_DB_CONNECTION_STRING` is set correctly. See the `Running` section for more details. 2. Making sure `LIGHTHOUSE_DB_CONNECTION_STRING` is set correctly. See the `Running` section for more details.
3. Making your changes to the database. I won't cover this since if you're making database changes you should know what you're doing. 3. Making your changes to the database. I won't cover this since if you're making database changes you should know what
you're doing.
4. Running `dotnet ef migrations add <NameOfMigrationInPascalCase> --project ProjectLighthouse`. 4. Running `dotnet ef migrations add <NameOfMigrationInPascalCase> --project ProjectLighthouse`.
## Compatibility across games and platforms
| Game | Console (PS3/Vita) | Emulator (RPCS3) | Next-Gen (PS4/PS5) |
|----------|------------------------|------------------------------------------------|--------------------|
| LBP1 | Somewhat compatible | Incompatible, crashes on entering pod computer | N/A |
| LBP2 | Compatible | Compatible with patched RPCS3 | N/A |
| LBP3 | Somewhat compatible | Somewhat compatible with workaround | Incompatible |
| LBP Vita | Potentially compatible | N/A | N/A |
Project Lighthouse is still a heavy work in progress, so this is subject to change at any point.

View file

@ -14,6 +14,6 @@ services:
- '3306' # Expose port to localhost:3306 - '3306' # Expose port to localhost:3306
volumes: volumes:
- lighthouse-db:/var/lib/mysql - lighthouse-db:/var/lib/mysql
volumes: volumes:
lighthouse-db: lighthouse-db: