mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-08-01 09:48:37 +00:00
Merge remote-tracking branch 'upstream/main' into Pullrequests
This commit is contained in:
commit
d638357f34
76 changed files with 1102 additions and 417 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -28,6 +28,7 @@ gitVersion.txt
|
|||
gitRemotes.txt
|
||||
gitUnpushed.txt
|
||||
logs/*
|
||||
npTicket*
|
||||
|
||||
# MSBuild stuff
|
||||
bin/
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<Path>.github</Path>
|
||||
<Path>.gitignore</Path>
|
||||
<Path>.idea</Path>
|
||||
<Path>CONTRIBUTING.md</Path>
|
||||
<Path>DatabaseMigrations</Path>
|
||||
<Path>ProjectLighthouse.sln.DotSettings</Path>
|
||||
<Path>ProjectLighthouse.sln.DotSettings.user</Path>
|
||||
|
|
100
CONTRIBUTING.md
Normal file
100
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,100 @@
|
|||
# Contributing
|
||||
|
||||
## Setting up MySQL
|
||||
|
||||
Project Lighthouse requires a MySQL database. 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. 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.
|
||||
|
||||
Once you've gotten MySQL running you can run Lighthouse. It will take care of the rest.
|
||||
|
||||
## 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 patched copy of RPCS3. You can find a working
|
||||
version [on our GitHub](https://github.com/LBPUnion/rpcs3).*
|
||||
|
||||
Start by getting a copy of LittleBigPlanet 1/2 installed. (Check the LittleBigPlanet 1 section, since you'll need to do
|
||||
extra steps for your game to not crash upon entering pod computer).
|
||||
|
||||
The game can be a digital copy (NPUA80472/NPUA80662) or a disc copy (
|
||||
BCUS98148/BCUS98245).
|
||||
|
||||
Next, download [UnionPatcher](https://github.com/LBPUnion/UnionPatcher/). Binaries can be found by reading the `README.md`
|
||||
file.
|
||||
|
||||
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`. You can grab your title id by right clicking the game in RPCS3 and
|
||||
clicking Copy Info -> Copy Serial.
|
||||
|
||||
|
||||
This should give you a file named `EBOOT.elf` in the same folder. This is your decrypted eboot.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
Project Lighthouse is running, the game should now connect and you may begin contributing!
|
||||
|
||||
### LittleBigPlanet 1
|
||||
|
||||
For LittleBigPlanet 1 to work with RPCS3, follow the steps above normally.
|
||||
|
||||
First, open your favourite hex editor. We recommend [HxD](https://mh-nexus.de/en/hxd/).
|
||||
|
||||
Once you have a hex editor open, open your `EBOOTlocalhost.elf` file and search for the hex
|
||||
values `73 63 65 4E 70 43 6F 6D 6D 65 72 63 65 32`. In HxD, this would be done by clicking on Search -> Replace,
|
||||
clicking on the `Hex-values` tab, and entering the hex there.
|
||||
|
||||
Then, you can zero it out by replacing it with `00 00 00 00 00 00 00 00 00 00 00 00 00 00`.
|
||||
|
||||
What this does is remove all the references to the `sceNpCommerce2` function. The function is used for purchasing DLC,
|
||||
which at this moment in time crashes RPCS3.
|
||||
|
||||
After saving the file your LBP1 EBOOT can be used with RPCS3.
|
||||
|
||||
## Contributing Tips
|
||||
|
||||
### Database migrations
|
||||
|
||||
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`.
|
||||
2. Making sure `LIGHTHOUSE_DB_CONNECTION_STRING` is set correctly. See the `Running` section for more details.
|
||||
3. Modifying the database schema via the C# portion of the code. Do not modify the actual SQL database.
|
||||
4. Running `dotnet ef migrations add <NameOfMigrationInPascalCase> --project ProjectLighthouse`.
|
||||
|
||||
This process will create a migration file from the changes made in the C# code.
|
||||
|
||||
The new migrations will automatically be applied upon starting Lighthouse.
|
||||
|
||||
### Running tests
|
||||
|
||||
You can run tests either through your IDE or by running `dotnet tests`.
|
||||
|
||||
Keep in mind while running database tests (which most tests are) you need to have `LIGHTHOUSE_DB_CONNECTION_STRING` set.
|
||||
|
||||
### Continuous Integration (CI) Tips
|
||||
|
||||
- You can skip CI runs for a commit if you specify `[skip ci]` at the beginning of the commit name. This is useful for
|
||||
formatting changes, etc.
|
||||
- When creating your first pull request, CI will not run initially. A team member will have to approve you for use of
|
||||
running CI on a pull request. This is because of GitHub policy.
|
||||
|
||||
### API Documentation
|
||||
|
||||
You can access API documentation by looking at the XMLDoc in the controllers under `ProjectLighthouse.Controllers.Api`
|
||||
|
||||
You can also access an interactive version by starting Lighthouse and accessing Swagger
|
||||
at `http://localhost:10060/swagger/index.html`.
|
|
@ -37,7 +37,8 @@ public class LighthouseServerTest
|
|||
await database.CreateUser($"{username}{number}", HashHelper.BCryptHash($"unitTestPassword{number}"));
|
||||
}
|
||||
|
||||
string stringContent = $"{LoginData.UsernamePrefix}{username}{number}{(char)0x00}";
|
||||
//TODO: generate actual tickets
|
||||
string stringContent = $"unitTestTicket{username}{number}";
|
||||
|
||||
HttpResponseMessage response = await this.Client.PostAsync
|
||||
($"/LITTLEBIGPLANETPS3_XML/login?titleID={GameVersionHelper.LittleBigPlanet2TitleIds[0]}", new StringContent(stringContent));
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<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:Boolean x:Key="/Default/CodeEditing/TypingAssist/Asp/FormatOnClosingTag/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeEditing/TypingAssist/CSharpAnnotationTypingAssist/IsEnabledAfterTypeName/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeEditing/TypingAssist/CSharpAnnotationTypingAssist/IsEnabledAtOtherPositions/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeEditing/TypingAssist/CSharpAnnotationTypingAssist/IsInsertNullCheckWhenAlreadyAnnotated/@EntryValue">False</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeEditing/TypingAssist/FormatOnPaste/@EntryValue">FullFormat</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeEditing/TypingAssist/VirtualSpaceOnEnter/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeConstructorOrDestructorBody/@EntryIndexedValue">HINT</s:String>
|
||||
|
|
62
ProjectLighthouse/Controllers/Api/SlotEndpoints.cs
Normal file
62
ProjectLighthouse/Controllers/Api/SlotEndpoints.cs
Normal file
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.Api;
|
||||
|
||||
/// <summary>
|
||||
/// A collection of endpoints relating to slots.
|
||||
/// </summary>
|
||||
public class SlotEndpoints : ApiEndpointController
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public SlotEndpoints(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of (stripped down) slots from the database.
|
||||
/// </summary>
|
||||
/// <param name="limit">How many slots you want to retrieve.</param>
|
||||
/// <param name="skip">How many slots to skip.</param>
|
||||
/// <returns>The slot</returns>
|
||||
/// <response code="200">The slot list, if successful.</response>
|
||||
[HttpGet("slots")]
|
||||
[ProducesResponseType(typeof(List<MinimalSlot>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetSlots([FromQuery] int limit = 20, [FromQuery] int skip = 0)
|
||||
{
|
||||
limit = Math.Min(ServerStatics.PageSize, limit);
|
||||
|
||||
IEnumerable<MinimalSlot> minimalSlots = (await this.database.Slots.OrderByDescending(s => s.FirstUploaded).Skip(skip).Take(limit).ToListAsync()).Select
|
||||
(MinimalSlot.FromSlot);
|
||||
|
||||
return this.Ok(minimalSlots);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a slot (more commonly known as a level) and its information from the database.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the slot</param>
|
||||
/// <returns>The slot</returns>
|
||||
/// <response code="200">The slot, if successful.</response>
|
||||
/// <response code="404">The slot could not be found.</response>
|
||||
[HttpGet("slot/{id:int}")]
|
||||
[ProducesResponseType(typeof(Slot), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetSlot(int id)
|
||||
{
|
||||
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(u => u.SlotId == id);
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
return this.Ok(slot);
|
||||
}
|
||||
}
|
33
ProjectLighthouse/Controllers/Api/StatisticsEndpoints.cs
Normal file
33
ProjectLighthouse/Controllers/Api/StatisticsEndpoints.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using System.Threading.Tasks;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Api;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.Api;
|
||||
|
||||
/// <summary>
|
||||
/// A collection of endpoints relating to statistics.
|
||||
/// </summary>
|
||||
public class StatisticsEndpoints : ApiEndpointController
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets everything that StatisticsHelper provides.
|
||||
/// </summary>
|
||||
/// <returns>An instance of StatisticsResponse</returns>
|
||||
[HttpGet("statistics")]
|
||||
[ProducesResponseType(typeof(StatisticsResponse), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetStatistics()
|
||||
=> this.Ok
|
||||
(
|
||||
new StatisticsResponse
|
||||
{
|
||||
Photos = await StatisticsHelper.PhotoCount(),
|
||||
Slots = await StatisticsHelper.SlotCount(),
|
||||
Users = await StatisticsHelper.UserCount(),
|
||||
RecentMatches = await StatisticsHelper.RecentMatches(),
|
||||
TeamPicks = await StatisticsHelper.TeamPickCount(),
|
||||
}
|
||||
);
|
||||
}
|
39
ProjectLighthouse/Controllers/Api/UserEndpoints.cs
Normal file
39
ProjectLighthouse/Controllers/Api/UserEndpoints.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
#nullable enable
|
||||
using System.Threading.Tasks;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.Api;
|
||||
|
||||
/// <summary>
|
||||
/// A collection of endpoints relating to users.
|
||||
/// </summary>
|
||||
public class UserEndpoints : ApiEndpointController
|
||||
{
|
||||
private readonly Database database;
|
||||
|
||||
public UserEndpoints(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a user and their information from the database.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the user</param>
|
||||
/// <returns>The user</returns>
|
||||
/// <response code="200">The user, if successful.</response>
|
||||
/// <response code="404">The user could not be found.</response>
|
||||
[HttpGet("user/{id:int}")]
|
||||
[ProducesResponseType(typeof(User), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetUser(int id)
|
||||
{
|
||||
User? user = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id);
|
||||
if (user == null) return this.NotFound();
|
||||
|
||||
return this.Ok(user);
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ using LBPUnion.ProjectLighthouse.Types.Settings;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
|
@ -11,7 +11,7 @@ using LBPUnion.ProjectLighthouse.Types.Profiles;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
|
@ -11,7 +11,7 @@ using LBPUnion.ProjectLighthouse.Types.Profiles;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
|
@ -8,10 +8,12 @@ using LBPUnion.ProjectLighthouse.Helpers;
|
|||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
using LBPUnion.ProjectLighthouse.Types.Tickets;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using IOFile = System.IO.File;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/login")]
|
||||
|
@ -26,25 +28,29 @@ public class LoginController : ControllerBase
|
|||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Login([FromQuery] string? titleId)
|
||||
public async Task<IActionResult> Login()
|
||||
{
|
||||
titleId ??= "";
|
||||
MemoryStream ms = new();
|
||||
await this.Request.Body.CopyToAsync(ms);
|
||||
byte[] loginData = ms.ToArray();
|
||||
|
||||
string body = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
#if DEBUG
|
||||
await IOFile.WriteAllBytesAsync($"npTicket-{TimestampHelper.TimestampMillis}.txt", loginData);
|
||||
#endif
|
||||
|
||||
LoginData? loginData;
|
||||
NPTicket? npTicket;
|
||||
try
|
||||
{
|
||||
loginData = LoginData.CreateFromString(body);
|
||||
npTicket = NPTicket.CreateFromBytes(loginData);
|
||||
}
|
||||
catch
|
||||
{
|
||||
loginData = null;
|
||||
npTicket = null;
|
||||
}
|
||||
|
||||
if (loginData == null)
|
||||
if (npTicket == null)
|
||||
{
|
||||
Logger.Log("loginData was null, rejecting login", LoggerLevelLogin.Instance);
|
||||
Logger.Log("npTicket was null, rejecting login", LoggerLevelLogin.Instance);
|
||||
return this.BadRequest();
|
||||
}
|
||||
|
||||
|
@ -60,11 +66,11 @@ public class LoginController : ControllerBase
|
|||
// Get an existing token from the IP & username
|
||||
GameToken? token = await this.database.GameTokens.Include
|
||||
(t => t.User)
|
||||
.FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == loginData.Username && !t.Used);
|
||||
.FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == npTicket.Username && !t.Used);
|
||||
|
||||
if (token == null) // If we cant find an existing token, try to generate a new one
|
||||
{
|
||||
token = await this.database.AuthenticateUser(loginData, ipAddress, titleId);
|
||||
token = await this.database.AuthenticateUser(npTicket, ipAddress);
|
||||
if (token == null)
|
||||
{
|
||||
Logger.Log("unable to find/generate a token, rejecting login", LoggerLevelLogin.Instance);
|
||||
|
@ -110,7 +116,7 @@ public class LoginController : ControllerBase
|
|||
GameTokenId = token.TokenId,
|
||||
Timestamp = TimestampHelper.Timestamp,
|
||||
IPAddress = ipAddress,
|
||||
Platform = token.GameVersion == GameVersion.LittleBigPlanetVita ? Platform.Vita : Platform.PS3, // TODO: properly identify RPCS3
|
||||
Platform = npTicket.Platform,
|
||||
};
|
||||
|
||||
this.database.AuthenticationAttempts.Add(authAttempt);
|
||||
|
@ -129,7 +135,7 @@ public class LoginController : ControllerBase
|
|||
return this.StatusCode(403, "");
|
||||
}
|
||||
|
||||
Logger.Log($"Successfully logged in user {user.Username} as {token.GameVersion} client ({titleId})", LoggerLevelLogin.Instance);
|
||||
Logger.Log($"Successfully logged in user {user.Username} as {token.GameVersion} client", LoggerLevelLogin.Instance);
|
||||
// After this point we are now considering this session as logged in.
|
||||
|
||||
// We just logged in with the token. Mark it as used so someone else doesnt try to use it,
|
|
@ -7,7 +7,7 @@ using LBPUnion.ProjectLighthouse.Types.Levels;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi.Matching;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
|
@ -14,7 +14,7 @@ using LBPUnion.ProjectLighthouse.Types.Match;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi.Matching;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
|
@ -8,7 +8,7 @@ using LBPUnion.ProjectLighthouse.Types;
|
|||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
|
@ -38,13 +38,13 @@ public class MessageController : ControllerBase
|
|||
User? user = await this.database.UserFromGameRequest(this.Request);
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
#else
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
|
||||
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
if (userAndToken == null) return this.StatusCode(403, "");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
User user = userAndToken.Value.Item1;
|
||||
GameToken gameToken = userAndToken.Value.Item2;
|
||||
#endif
|
||||
|
||||
string announceText = ServerSettings.Instance.AnnounceText;
|
||||
|
@ -56,13 +56,13 @@ public class MessageController : ControllerBase
|
|||
(
|
||||
announceText +
|
||||
#if DEBUG
|
||||
"\n\n---DEBUG INFO---\n" +
|
||||
$"user.UserId: {user.UserId}\n" +
|
||||
$"token.Approved: {gameToken.Approved}\n" +
|
||||
$"token.Used: {gameToken.Used}\n" +
|
||||
$"token.UserLocation: {gameToken.UserLocation}\n" +
|
||||
$"token.GameVersion: {gameToken.GameVersion}\n" +
|
||||
"---DEBUG INFO---" +
|
||||
"\n\n---DEBUG INFO---\n" +
|
||||
$"user.UserId: {user.UserId}\n" +
|
||||
$"token.Approved: {gameToken.Approved}\n" +
|
||||
$"token.Used: {gameToken.Used}\n" +
|
||||
$"token.UserLocation: {gameToken.UserLocation}\n" +
|
||||
$"token.GameVersion: {gameToken.GameVersion}\n" +
|
||||
"---DEBUG INFO---" +
|
||||
#endif
|
||||
"\n"
|
||||
);
|
|
@ -15,7 +15,7 @@ using LBPUnion.ProjectLighthouse.Types.Settings;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi.Resources;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
|
@ -1,4 +1,5 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -12,7 +13,7 @@ using LBPUnion.ProjectLighthouse.Types.Files;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using IOFile = System.IO.File;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi.Resources;
|
||||
|
||||
[ApiController]
|
||||
[Produces("text/xml")]
|
|
@ -11,7 +11,7 @@ using LBPUnion.ProjectLighthouse.Types.Categories;
|
|||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
|
@ -2,7 +2,7 @@ using System;
|
|||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/tags")]
|
|
@ -9,7 +9,7 @@ using LBPUnion.ProjectLighthouse.Types.Levels;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
|
@ -13,7 +13,7 @@ using LBPUnion.ProjectLighthouse.Types.Settings;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
|
@ -38,7 +38,7 @@ public class PublishController : ControllerBase
|
|||
|
||||
if (user.UsedSlots >= ServerSettings.Instance.EntitledSlots) return this.BadRequest();
|
||||
|
||||
Slot? slot = await this.GetSlotFromBody();
|
||||
Slot? slot = await this.getSlotFromBody();
|
||||
if (slot == null) return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded
|
||||
|
||||
if (string.IsNullOrEmpty(slot.RootLevel)) return this.BadRequest();
|
||||
|
@ -79,7 +79,7 @@ public class PublishController : ControllerBase
|
|||
|
||||
if (user.UsedSlots >= ServerSettings.Instance.EntitledSlots) return this.BadRequest();
|
||||
|
||||
Slot? slot = await this.GetSlotFromBody();
|
||||
Slot? slot = await this.getSlotFromBody();
|
||||
if (slot?.Location == null) return this.BadRequest();
|
||||
|
||||
// Republish logic
|
||||
|
@ -186,7 +186,7 @@ public class PublishController : ControllerBase
|
|||
return this.Ok();
|
||||
}
|
||||
|
||||
public async Task<Slot?> GetSlotFromBody()
|
||||
private async Task<Slot?> getSlotFromBody()
|
||||
{
|
||||
this.Request.Body.Position = 0;
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
|
@ -13,7 +13,7 @@ using LBPUnion.ProjectLighthouse.Types.Reviews;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
|
@ -91,7 +91,7 @@ public class ReviewController : ControllerBase
|
|||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId);
|
||||
Review? newReview = await this.GetReviewFromBody();
|
||||
Review? newReview = await this.getReviewFromBody();
|
||||
if (newReview == null) return this.BadRequest();
|
||||
|
||||
if (review == null)
|
||||
|
@ -317,7 +317,7 @@ public class ReviewController : ControllerBase
|
|||
return this.Ok();
|
||||
}
|
||||
|
||||
public async Task<Review?> GetReviewFromBody()
|
||||
private async Task<Review?> getReviewFromBody()
|
||||
{
|
||||
this.Request.Body.Position = 0;
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
|
@ -11,7 +11,7 @@ using LBPUnion.ProjectLighthouse.Types;
|
|||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
|
@ -80,7 +80,7 @@ public class ScoreController : ControllerBase
|
|||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
string myRanking = this.GetScores(score.SlotId, score.Type, user);
|
||||
string myRanking = this.getScores(score.SlotId, score.Type, user);
|
||||
|
||||
return this.Ok(myRanking);
|
||||
}
|
||||
|
@ -99,11 +99,11 @@ public class ScoreController : ControllerBase
|
|||
|
||||
if (user == null) return this.StatusCode(403, "");
|
||||
|
||||
return this.Ok(this.GetScores(slotId, type, user, pageStart, pageSize));
|
||||
return this.Ok(this.getScores(slotId, type, user, pageStart, pageSize));
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
|
||||
public string GetScores(int slotId, int type, User user, int pageStart = -1, int pageSize = 5)
|
||||
private string getScores(int slotId, int type, User user, int pageStart = -1, int pageSize = 5)
|
||||
{
|
||||
// This is hella ugly but it technically assigns the proper rank to a score
|
||||
// var needed for Anonymous type returned from SELECT
|
|
@ -7,7 +7,7 @@ using LBPUnion.ProjectLighthouse.Types.Levels;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
|
@ -11,7 +11,7 @@ using LBPUnion.ProjectLighthouse.Types.Settings;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
|
@ -177,7 +177,7 @@ public class SlotsController : ControllerBase
|
|||
"hint_start", pageStart + Math.Min(pageSize, ServerSettings.Instance.EntitledSlots)
|
||||
},
|
||||
{
|
||||
"total", await StatisticsHelper.MMPicksCount()
|
||||
"total", await StatisticsHelper.TeamPickCount()
|
||||
},
|
||||
}
|
||||
)
|
||||
|
@ -235,7 +235,7 @@ public class SlotsController : ControllerBase
|
|||
|
||||
Random rand = new();
|
||||
|
||||
IEnumerable<Slot> slots = this.FilterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
.AsEnumerable()
|
||||
.OrderByDescending(s => s.Thumbsup)
|
||||
.ThenBy(_ => rand.Next())
|
||||
|
@ -279,14 +279,14 @@ public class SlotsController : ControllerBase
|
|||
|
||||
Random rand = new();
|
||||
|
||||
IEnumerable<Slot> slots = this.FilterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
.AsEnumerable()
|
||||
.OrderByDescending
|
||||
(
|
||||
// probably not the best way to do this?
|
||||
s =>
|
||||
{
|
||||
return this.GetGameFilter(gameFilterType, token.GameVersion) switch
|
||||
return this.getGameFilter(gameFilterType, token.GameVersion) switch
|
||||
{
|
||||
GameVersion.LittleBigPlanet1 => s.PlaysLBP1Unique,
|
||||
GameVersion.LittleBigPlanet2 => s.PlaysLBP2Unique,
|
||||
|
@ -337,7 +337,7 @@ public class SlotsController : ControllerBase
|
|||
|
||||
Random rand = new();
|
||||
|
||||
IEnumerable<Slot> slots = this.FilterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
IEnumerable<Slot> slots = this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
.AsEnumerable()
|
||||
.OrderByDescending(s => s.Hearts)
|
||||
.ThenBy(_ => rand.Next())
|
||||
|
@ -365,7 +365,7 @@ public class SlotsController : ControllerBase
|
|||
);
|
||||
}
|
||||
|
||||
public GameVersion GetGameFilter(string? gameFilterType, GameVersion version)
|
||||
private GameVersion getGameFilter(string? gameFilterType, GameVersion version)
|
||||
{
|
||||
if (version == GameVersion.LittleBigPlanetVita) return GameVersion.LittleBigPlanetVita;
|
||||
if (version == GameVersion.LittleBigPlanetPSP) return GameVersion.LittleBigPlanetPSP;
|
||||
|
@ -381,7 +381,7 @@ public class SlotsController : ControllerBase
|
|||
};
|
||||
}
|
||||
|
||||
public IQueryable<Slot> FilterByRequest(string? gameFilterType, string? dateFilterType, GameVersion version)
|
||||
private IQueryable<Slot> filterByRequest(string? gameFilterType, string? dateFilterType, GameVersion version)
|
||||
{
|
||||
string _dateFilterType = dateFilterType ?? "";
|
||||
|
||||
|
@ -392,7 +392,7 @@ public class SlotsController : ControllerBase
|
|||
_ => 0,
|
||||
};
|
||||
|
||||
GameVersion gameVersion = this.GetGameFilter(gameFilterType, version);
|
||||
GameVersion gameVersion = this.getGameFilter(gameFilterType, version);
|
||||
|
||||
IQueryable<Slot> whereSlots;
|
||||
|
|
@ -3,7 +3,7 @@ using LBPUnion.ProjectLighthouse.Helpers;
|
|||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
|
@ -24,7 +24,7 @@ public class StatisticsController : ControllerBase
|
|||
public async Task<IActionResult> PlanetStats()
|
||||
{
|
||||
int totalSlotCount = await StatisticsHelper.SlotCount();
|
||||
int mmPicksCount = await StatisticsHelper.MMPicksCount();
|
||||
int mmPicksCount = await StatisticsHelper.TeamPickCount();
|
||||
|
||||
return this.Ok
|
||||
(
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
|
@ -12,7 +12,7 @@ using LBPUnion.ProjectLighthouse.Types.Profiles;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.GameApi;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
|
@ -26,7 +26,7 @@ public class UserController : ControllerBase
|
|||
this.database = database;
|
||||
}
|
||||
|
||||
public async Task<string?> GetSerializedUser(string username, GameVersion gameVersion = GameVersion.LittleBigPlanet1)
|
||||
private async Task<string?> getSerializedUser(string username, GameVersion gameVersion = GameVersion.LittleBigPlanet1)
|
||||
{
|
||||
User? user = await this.database.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.Username == username);
|
||||
return user?.Serialize(gameVersion);
|
||||
|
@ -38,7 +38,7 @@ public class UserController : ControllerBase
|
|||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
string? user = await this.GetSerializedUser(username, token.GameVersion);
|
||||
string? user = await this.getSerializedUser(username, token.GameVersion);
|
||||
if (user == null) return this.NotFound();
|
||||
|
||||
return this.Ok(user);
|
||||
|
@ -51,7 +51,7 @@ public class UserController : ControllerBase
|
|||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
List<string?> serializedUsers = new();
|
||||
foreach (string userId in u) serializedUsers.Add(await this.GetSerializedUser(userId, token.GameVersion));
|
||||
foreach (string userId in u) serializedUsers.Add(await this.getSerializedUser(userId, token.GameVersion));
|
||||
|
||||
string serialized = serializedUsers.Aggregate(string.Empty, (current, user) => user == null ? current : current + user);
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.News;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/news")]
|
||||
[Produces("text/xml")]
|
||||
public class NewsController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public IActionResult Get()
|
||||
{
|
||||
string newsEntry = LbpSerializer.StringElement
|
||||
(
|
||||
"item",
|
||||
new NewsEntry
|
||||
{
|
||||
Category = "no_category",
|
||||
Summary = "test summary",
|
||||
Image = new NewsImage
|
||||
{
|
||||
Hash = "4947269c5f7061b27225611ee58a9a91a8031bbe",
|
||||
Alignment = "right",
|
||||
},
|
||||
Id = 1,
|
||||
Title = "Test Title",
|
||||
Text = "Test Text",
|
||||
Date = 1348755214000,
|
||||
}.Serialize()
|
||||
);
|
||||
|
||||
return this.Ok(LbpSerializer.StringElement("news", newsEntry));
|
||||
}
|
||||
}
|
|
@ -1,7 +1,4 @@
|
|||
#nullable enable
|
||||
using System.Threading.Tasks;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Controllers.Website.Admin;
|
||||
|
@ -16,14 +13,4 @@ public class AdminPanelController : ControllerBase
|
|||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpGet("testWebhook")]
|
||||
public async Task<IActionResult> TestWebhook()
|
||||
{
|
||||
User? user = this.database.UserFromWebRequest(this.Request);
|
||||
if (user == null || !user.IsAdmin) return this.NotFound();
|
||||
|
||||
await WebhookHelper.SendWebhook("Testing 123", "Someone is testing the Discord webhook from the admin panel.");
|
||||
return this.Redirect("/admin");
|
||||
}
|
||||
}
|
|
@ -1,15 +1,14 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Kettu;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Categories;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Types.Reviews;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
using LBPUnion.ProjectLighthouse.Types.Tickets;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
|
@ -67,9 +66,9 @@ public class Database : DbContext
|
|||
}
|
||||
|
||||
#nullable enable
|
||||
public async Task<GameToken?> AuthenticateUser(LoginData loginData, string userLocation, string titleId = "")
|
||||
public async Task<GameToken?> AuthenticateUser(NPTicket npTicket, string userLocation)
|
||||
{
|
||||
User? user = await this.Users.FirstOrDefaultAsync(u => u.Username == loginData.Username);
|
||||
User? user = await this.Users.FirstOrDefaultAsync(u => u.Username == npTicket.Username);
|
||||
if (user == null) return null;
|
||||
|
||||
GameToken gameToken = new()
|
||||
|
@ -78,15 +77,9 @@ public class Database : DbContext
|
|||
User = user,
|
||||
UserId = user.UserId,
|
||||
UserLocation = userLocation,
|
||||
GameVersion = GameVersionHelper.FromTitleId(titleId),
|
||||
GameVersion = npTicket.GameVersion,
|
||||
};
|
||||
|
||||
if (gameToken.GameVersion == GameVersion.Unknown)
|
||||
{
|
||||
Logger.Log($"Unknown GameVersion for TitleId {titleId}", LoggerLevelLogin.Instance);
|
||||
return null;
|
||||
}
|
||||
|
||||
this.GameTokens.Add(gameToken);
|
||||
await this.SaveChangesAsync();
|
||||
|
||||
|
|
|
@ -23,6 +23,10 @@ public static class BinaryReaderExtensions
|
|||
|
||||
public static int ReadInt32BE(this BinaryReader binRdr) => BitConverter.ToInt32(binRdr.ReadBytesRequired(sizeof(int)).Reverse(), 0);
|
||||
|
||||
public static ulong ReadUInt64BE(this BinaryReader binRdr) => BitConverter.ToUInt32(binRdr.ReadBytesRequired(sizeof(ulong)).Reverse(), 0);
|
||||
|
||||
public static long ReadInt64BE(this BinaryReader binRdr) => BitConverter.ToInt32(binRdr.ReadBytesRequired(sizeof(long)).Reverse(), 0);
|
||||
|
||||
public static byte[] ReadBytesRequired(this BinaryReader binRdr, int byteCount)
|
||||
{
|
||||
byte[] result = binRdr.ReadBytes(byteCount);
|
||||
|
|
|
@ -99,6 +99,6 @@ public class GameVersionHelper
|
|||
if (LittleBigPlanetVitaTitleIds.Contains(titleId)) return GameVersion.LittleBigPlanetVita;
|
||||
if (LittleBigPlanetPSPTitleIds.Contains(titleId)) return GameVersion.LittleBigPlanetPSP;
|
||||
|
||||
return GameVersion.LittleBigPlanet1;
|
||||
return GameVersion.Unknown;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ public static class StatisticsHelper
|
|||
|
||||
public static async Task<int> UserCount() => await database.Users.CountAsync(u => !u.Banned);
|
||||
|
||||
public static async Task<int> MMPicksCount() => await database.Slots.CountAsync(s => s.TeamPick);
|
||||
public static async Task<int> TeamPickCount() => await database.Slots.CountAsync(s => s.TeamPick);
|
||||
|
||||
public static async Task<int> PhotoCount() => await database.Photos.CountAsync();
|
||||
}
|
15
ProjectLighthouse/Helpers/SwaggerFilter.cs
Normal file
15
ProjectLighthouse/Helpers/SwaggerFilter.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Helpers;
|
||||
|
||||
public class SwaggerFilter : IDocumentFilter
|
||||
{
|
||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||
{
|
||||
List<KeyValuePair<string, OpenApiPathItem>> nonApiRoutes = swaggerDoc.Paths.Where(x => !x.Key.ToLower().StartsWith("/api/v1")).ToList();
|
||||
nonApiRoutes.ForEach(x => swaggerDoc.Paths.Remove(x.Key));
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ namespace LBPUnion.ProjectLighthouse.Helpers;
|
|||
|
||||
public static class WebhookHelper
|
||||
{
|
||||
private static readonly DiscordWebhookClient client = new(ServerSettings.Instance.DiscordWebhookUrl);
|
||||
private static readonly DiscordWebhookClient client = (ServerSettings.Instance.DiscordWebhookEnabled ? new DiscordWebhookClient(ServerSettings.Instance.DiscordWebhookUrl) : null);
|
||||
public static readonly Color UnionColor = new(0, 140, 255);
|
||||
|
||||
public static Task SendWebhook(EmbedBuilder builder) => SendWebhook(builder.Build());
|
||||
|
|
22
ProjectLighthouse/Maintenance/Commands/TestWebhookCommand.cs
Normal file
22
ProjectLighthouse/Maintenance/Commands/TestWebhookCommand.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Maintenance.Commands;
|
||||
|
||||
[UsedImplicitly]
|
||||
public class TestWebhookCommand : ICommand
|
||||
{
|
||||
public async Task Run(string[] args)
|
||||
{
|
||||
await WebhookHelper.SendWebhook("Testing 123", "Someone is testing the Discord webhook from the admin panel.");
|
||||
}
|
||||
public string Name() => "Test Discord Webhook";
|
||||
public string[] Aliases()
|
||||
=> new[]
|
||||
{
|
||||
"testWebhook", "testDiscordWebhook",
|
||||
};
|
||||
public string Arguments() => "";
|
||||
public int RequiredArgs() => 0;
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
@page "/admin"
|
||||
@using LBPUnion.ProjectLighthouse.Helpers
|
||||
@using LBPUnion.ProjectLighthouse.Helpers.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.Maintenance
|
||||
@using LBPUnion.ProjectLighthouse.Types.Settings
|
||||
@using LBPUnion.ProjectLighthouse.Types
|
||||
@model LBPUnion.ProjectLighthouse.Pages.Admin.AdminPanelPage
|
||||
|
||||
@{
|
||||
|
@ -9,61 +10,63 @@
|
|||
Model.Title = "Admin Panel";
|
||||
}
|
||||
|
||||
<a href="/admin/users">
|
||||
<div class="ui blue button">
|
||||
View Users
|
||||
</div>
|
||||
</a>
|
||||
@if (ServerSettings.Instance.DiscordWebhookEnabled)
|
||||
@if (!this.Request.IsMobile())
|
||||
{
|
||||
<a href="/admin/testWebhook">
|
||||
<div class="ui blue button">
|
||||
Test Discord Webhook
|
||||
</div>
|
||||
</a>
|
||||
<div class="ui center aligned grid">
|
||||
@foreach (AdminPanelStatistic statistic in Model.Statistics)
|
||||
{
|
||||
@await Html.PartialAsync("Partials/AdminPanelStatisticPartial", statistic)
|
||||
}
|
||||
</div>
|
||||
<br>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (AdminPanelStatistic statistic in Model.Statistics)
|
||||
{
|
||||
@await Html.PartialAsync("Partials/AdminPanelStatisticPartial", statistic)
|
||||
<br>
|
||||
}
|
||||
}
|
||||
|
||||
<h2>Commands</h2>
|
||||
<div class="ui grid">
|
||||
@foreach (ICommand command in MaintenanceHelper.Commands)
|
||||
{
|
||||
<div class="four wide column">
|
||||
<div class="ui blue segment">
|
||||
<h3>@command.Name()</h3>
|
||||
<form>
|
||||
<div class="ui input" style="width: 100%;">
|
||||
<input type="text" name="args" placeholder="@command.Arguments()">
|
||||
</div><br><br>
|
||||
<input type="text" name="command" style="display: none;" value="@command.FirstAlias">
|
||||
<button type="submit" class="ui green button" style="width: 100%;">
|
||||
<i class="play icon"></i>
|
||||
Execute
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@foreach (ICommand command in MaintenanceHelper.Commands)
|
||||
{
|
||||
<div class="ui blue segment">
|
||||
<h3>@command.Name()</h3>
|
||||
<form>
|
||||
@if (command.RequiredArgs() > 0)
|
||||
{
|
||||
<div class="ui input" style="width: @(Model.Request.IsMobile() ? 100 : 30)%;">
|
||||
<input type="text" name="args" placeholder="@command.Arguments()">
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
}
|
||||
<input type="text" name="command" style="display: none;" value="@command.FirstAlias">
|
||||
<button type="submit" class="ui green button">
|
||||
<i class="play icon"></i>
|
||||
Execute
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
<h2>Maintenance Jobs</h2>
|
||||
<p>
|
||||
<b>Warning: Interrupting Lighthouse during maintenance may leave the database in an unclean state.</b>
|
||||
</p>
|
||||
|
||||
<div class="ui grid">
|
||||
@foreach (IMaintenanceJob job in MaintenanceHelper.MaintenanceJobs)
|
||||
{
|
||||
<div class="four wide column">
|
||||
<div class="ui red segment">
|
||||
<h3>@job.Name()</h3>
|
||||
<p>@job.Description()</p>
|
||||
<form>
|
||||
<input type="text" name="maintenanceJob" style="display: none;" value="@job.GetType().Name">
|
||||
<button type="submit" class="ui green button" style="width: 100%;">
|
||||
<i class="play icon"></i>
|
||||
Execute
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@foreach (IMaintenanceJob job in MaintenanceHelper.MaintenanceJobs)
|
||||
{
|
||||
<div class="ui red segment">
|
||||
<h3>@job.Name()</h3>
|
||||
<p>@job.Description()</p>
|
||||
<form>
|
||||
<input type="text" name="maintenanceJob" style="display: none;" value="@job.GetType().Name">
|
||||
<button type="submit" class="ui green button">
|
||||
<i class="play icon"></i>
|
||||
Run Job
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
|
@ -15,12 +15,18 @@ public class AdminPanelPage : BaseLayout
|
|||
public AdminPanelPage(Database database) : base(database)
|
||||
{}
|
||||
|
||||
public List<AdminPanelStatistic> Statistics = new();
|
||||
|
||||
public async Task<IActionResult> OnGet([FromQuery] string? args, [FromQuery] string? command, [FromQuery] string? maintenanceJob)
|
||||
{
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user == null) return this.Redirect("~/login");
|
||||
if (!user.IsAdmin) return this.NotFound();
|
||||
|
||||
this.Statistics.Add(new AdminPanelStatistic("Users", await StatisticsHelper.UserCount(), "users"));
|
||||
this.Statistics.Add(new AdminPanelStatistic("Slots", await StatisticsHelper.SlotCount()));
|
||||
this.Statistics.Add(new AdminPanelStatistic("Photos", await StatisticsHelper.PhotoCount()));
|
||||
|
||||
if (!string.IsNullOrEmpty(command))
|
||||
{
|
||||
args ??= "";
|
||||
|
|
|
@ -11,9 +11,10 @@
|
|||
|
||||
<script>
|
||||
function onSubmit(form) {
|
||||
const password = form['password'];
|
||||
const passwordInput = document.getElementById("password");
|
||||
const passwordSubmit = document.getElementById("password-submit");
|
||||
|
||||
password.value = sha256(password.value);
|
||||
passwordSubmit.value = sha256(passwordInput.value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -43,7 +44,8 @@
|
|||
<div class="field">
|
||||
<label>Password</label>
|
||||
<div class="ui left icon input">
|
||||
<input type="password" name="password" id="password" placeholder="Password">
|
||||
<input type="password" id="password" placeholder="Password">
|
||||
<input type="hidden" id="password-submit" name="password">
|
||||
<i class="lock icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
@model LBPUnion.ProjectLighthouse.Types.AdminPanelStatistic
|
||||
|
||||
<div class="three wide column">
|
||||
<div class="ui center aligned blue segment">
|
||||
@if (Model.ViewAllEndpoint != null)
|
||||
{
|
||||
<h2>
|
||||
<a href="/admin/@Model.ViewAllEndpoint">@Model.StatisticNamePlural</a>
|
||||
</h2>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h2>@Model.StatisticNamePlural</h2>
|
||||
}
|
||||
<h3>@Model.Count</h3>
|
||||
</div>
|
||||
</div>
|
|
@ -10,6 +10,7 @@
|
|||
await using Database database = new();
|
||||
|
||||
string slotName = string.IsNullOrEmpty(Model.Name) ? "Unnamed Level" : Model.Name;
|
||||
bool isMobile = (bool?)ViewData["IsMobile"] ?? false;
|
||||
|
||||
bool isQueued = false;
|
||||
bool isHearted = false;
|
||||
|
@ -27,23 +28,26 @@
|
|||
string iconHash = Model.IconHash;
|
||||
if (string.IsNullOrWhiteSpace(iconHash) || iconHash.StartsWith('g')) iconHash = ServerSettings.Instance.MissingIconHash;
|
||||
}
|
||||
<div style="display: flex; width: 100%;">
|
||||
<div style="margin-right: 10px; background-image: url('/gameAssets/@iconHash'); height: 100px; width: 100px; background-size: cover; background-position: center; border-radius: 100%;">
|
||||
<div class="card">
|
||||
@{
|
||||
int size = isMobile ? 50 : 100;
|
||||
}
|
||||
<div class="cardIcon slotCardIcon" style="background-image: url('/gameAssets/@iconHash'); min-width: @(size)px; width: @(size)px; height: @(size)px">
|
||||
</div>
|
||||
<div style="height: fit-content; vertical-align: center; align-self: center">
|
||||
<div class="cardStats">
|
||||
@if (showLink)
|
||||
{
|
||||
<h2 style="margin-bottom: 2px;">
|
||||
<h2>
|
||||
<a href="~/slot/@Model.SlotId">@slotName</a>
|
||||
</h2>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h1 style="margin-bottom: 2px;">
|
||||
<h1>
|
||||
@slotName
|
||||
</h1>
|
||||
}
|
||||
<div class="statsUnderTitle" style="margin-bottom: 10px;">
|
||||
<div class="cardStatsUnderTitle">
|
||||
<i class="pink heart icon" title="Hearts"></i> <span>@Model.Hearts</span>
|
||||
<i class="blue play icon" title="Plays"></i> <span>@Model.Plays</span>
|
||||
<i class="green thumbs up icon" title="Yays"></i> <span>@Model.Thumbsup</span>
|
||||
|
@ -59,7 +63,7 @@
|
|||
<i>Created by <a href="/user/@Model.Creator?.UserId">@Model.Creator?.Username</a> for @Model.GameVersion.ToPrettyString()</i>
|
||||
</p>
|
||||
</div>
|
||||
<div style="height: 100px; margin-left: auto">
|
||||
<div class="cardButtons">
|
||||
<br>
|
||||
@if (user != null)
|
||||
{
|
||||
|
|
|
@ -3,12 +3,16 @@
|
|||
|
||||
@{
|
||||
bool showLink = (bool?)ViewData["ShowLink"] ?? false;
|
||||
bool isMobile = (bool?)ViewData["IsMobile"] ?? false;
|
||||
}
|
||||
|
||||
<div style="display: flex;">
|
||||
<div style="margin-right: 10px; background-image: url('/gameAssets/@Model.WebsiteAvatarHash'); height: 100px; width: 100px; background-size: cover; background-position: center; border-radius: .28571429rem;">
|
||||
<div class="card">
|
||||
@{
|
||||
int size = isMobile ? 50 : 100;
|
||||
}
|
||||
<div class="cardIcon userCardIcon" style="background-image: url('/gameAssets/@Model.WebsiteAvatarHash'); min-width: @(size)px; width: @(size)px; height: @(size)px">
|
||||
</div>
|
||||
<div style="height: fit-content; vertical-align: center; align-self: center">
|
||||
<div class="cardStats">
|
||||
@if (showLink)
|
||||
{
|
||||
<h2 style="margin-bottom: 2px;">
|
||||
|
@ -24,7 +28,7 @@
|
|||
<p>
|
||||
<i>@Model.Status</i>
|
||||
</p>
|
||||
<div class="statsUnderTitle">
|
||||
<div class="cardStatsUnderTitle">
|
||||
<i class="pink heart icon" title="Hearts"></i> <span>@Model.Hearts</span>
|
||||
<i class="blue comment icon" title="Comments"></i> <span>@Model.Comments</span>
|
||||
<i class="green upload icon" title="Uploaded Levels"></i><span>@Model.UsedSlots / @ServerSettings.Instance.EntitledSlots</span>
|
||||
|
|
|
@ -9,6 +9,14 @@
|
|||
|
||||
<p>There are @Model.PhotoCount total photos!</p>
|
||||
|
||||
<form action="/photos/0">
|
||||
<div class="ui icon input">
|
||||
<input type="text" name="name" placeholder="Search photos..." value="@Model.SearchValue">
|
||||
<i class="search icon"></i>
|
||||
</div>
|
||||
</form>
|
||||
<div class="ui divider"></div>
|
||||
|
||||
@foreach (Photo photo in Model.Photos)
|
||||
{
|
||||
<div class="ui segment">
|
||||
|
@ -18,10 +26,10 @@
|
|||
|
||||
@if (Model.PageNumber != 0)
|
||||
{
|
||||
<a href="/photos/@(Model.PageNumber - 1)">Previous Page</a>
|
||||
<a href="/photos/@(Model.PageNumber - 1)@(Model.SearchValue.Length == 0 ? "" : "?name=" + Model.SearchValue)">Previous Page</a>
|
||||
}
|
||||
@(Model.PageNumber + 1) / @(Model.PageAmount)
|
||||
@if (Model.PageNumber < Model.PageAmount - 1)
|
||||
{
|
||||
<a href="/photos/@(Model.PageNumber + 1)">Next Page</a>
|
||||
<a href="/photos/@(Model.PageNumber + 1)@(Model.SearchValue.Length == 0 ? "" : "?name=" + Model.SearchValue)">Next Page</a>
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
|
@ -22,20 +22,25 @@ public class PhotosPage : BaseLayout
|
|||
public int PhotoCount;
|
||||
|
||||
public List<Photo> Photos;
|
||||
|
||||
public string SearchValue;
|
||||
public PhotosPage([NotNull] Database database) : base(database)
|
||||
{}
|
||||
|
||||
public async Task<IActionResult> OnGet([FromRoute] int pageNumber)
|
||||
public async Task<IActionResult> OnGet([FromRoute] int pageNumber, [FromQuery] string? name)
|
||||
{
|
||||
this.PhotoCount = await StatisticsHelper.PhotoCount();
|
||||
if (string.IsNullOrWhiteSpace(name)) name = "";
|
||||
|
||||
this.PhotoCount = await this.Database.Photos.CountAsync(p => p.Creator.Username.Contains(name) || p.PhotoSubjectCollection.Contains(name));
|
||||
|
||||
this.SearchValue = name;
|
||||
this.PageNumber = pageNumber;
|
||||
this.PageAmount = (int)Math.Ceiling((double)this.PhotoCount / ServerStatics.PageSize);
|
||||
this.PageAmount = Math.Max(1, (int)Math.Ceiling((double)this.PhotoCount / ServerStatics.PageSize));
|
||||
|
||||
if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) return this.Redirect($"/photos/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}");
|
||||
|
||||
this.Photos = await this.Database.Photos.Include
|
||||
(p => p.Creator)
|
||||
this.Photos = await this.Database.Photos.Include(p => p.Creator)
|
||||
.Where(p => p.Creator.Username.Contains(name) || p.PhotoSubjectCollection.Contains(name))
|
||||
.OrderByDescending(p => p.Timestamp)
|
||||
.Skip(pageNumber * ServerStatics.PageSize)
|
||||
.Take(ServerStatics.PageSize)
|
||||
|
|
|
@ -10,11 +10,12 @@
|
|||
|
||||
<script>
|
||||
function onSubmit(form) {
|
||||
const password = form['password'];
|
||||
const confirmPassword = form['confirmPassword'];
|
||||
|
||||
password.value = sha256(password.value);
|
||||
confirmPassword.value = sha256(confirmPassword.value);
|
||||
const passwordInput = document.getElementById("password");
|
||||
const confirmPasswordInput = document.getElementById("confirmPassword");
|
||||
const passwordSubmit = document.getElementById("password-submit");
|
||||
const confirmPasswordSubmit = document.getElementById("confirm-submit");
|
||||
passwordSubmit.value = sha256(passwordInput.value);
|
||||
confirmPasswordSubmit.value = sha256(confirmPasswordInput.value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -44,7 +45,8 @@
|
|||
<div class="field">
|
||||
<label>Password</label>
|
||||
<div class="ui left icon input">
|
||||
<input type="password" name="password" id="password" placeholder="Password">
|
||||
<input type="password" id="password" placeholder="Password">
|
||||
<input type="hidden" name="password" id="password-submit">
|
||||
<i class="lock icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -52,7 +54,8 @@
|
|||
<div class="field">
|
||||
<label>Confirm Password</label>
|
||||
<div class="ui left icon input">
|
||||
<input type="password" name="confirmPassword" id="confirmPassword" placeholder="Confirm Password">
|
||||
<input type="password" id="confirmPassword" placeholder="Confirm Password">
|
||||
<input type="hidden" name="confirmPassword" id="confirm-submit">
|
||||
<i class="lock icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@page "/slot/{id:int}"
|
||||
@using LBPUnion.ProjectLighthouse.Helpers.Extensions
|
||||
@model LBPUnion.ProjectLighthouse.Pages.SlotPage
|
||||
|
||||
@{
|
||||
|
@ -20,6 +21,9 @@
|
|||
{
|
||||
"ShowLink", false
|
||||
},
|
||||
{
|
||||
"IsMobile", Model.Request.IsMobile()
|
||||
},
|
||||
})
|
||||
<br>
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@page "/slots/{pageNumber:int}"
|
||||
@using LBPUnion.ProjectLighthouse.Helpers.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.Types.Levels
|
||||
@model LBPUnion.ProjectLighthouse.Pages.SlotsPage
|
||||
|
||||
|
@ -9,8 +10,17 @@
|
|||
|
||||
<p>There are @Model.SlotCount total levels!</p>
|
||||
|
||||
<form action="/slots/0">
|
||||
<div class="ui icon input">
|
||||
<input type="text" name="name" placeholder="Search levels..." value="@Model.SearchValue">
|
||||
<i class="search icon"></i>
|
||||
</div>
|
||||
</form>
|
||||
<div class="ui divider"></div>
|
||||
|
||||
@foreach (Slot slot in Model.Slots)
|
||||
{
|
||||
bool isMobile = Model.Request.IsMobile();
|
||||
<div class="ui segment">
|
||||
@await Html.PartialAsync("Partials/SlotCardPartial", slot, new ViewDataDictionary(ViewData)
|
||||
{
|
||||
|
@ -23,16 +33,19 @@
|
|||
{
|
||||
"ShowLink", true
|
||||
},
|
||||
{
|
||||
"IsMobile", isMobile
|
||||
},
|
||||
})
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.PageNumber != 0)
|
||||
{
|
||||
<a href="/slots/@(Model.PageNumber - 1)">Previous Page</a>
|
||||
<a href="/slots/@(Model.PageNumber - 1)@(Model.SearchValue.Length == 0 ? "" : "?name=" + Model.SearchValue)">Previous Page</a>
|
||||
}
|
||||
@(Model.PageNumber + 1) / @(Model.PageAmount)
|
||||
@if (Model.PageNumber < Model.PageAmount - 1)
|
||||
{
|
||||
<a href="/slots/@(Model.PageNumber + 1)">Next Page</a>
|
||||
<a href="/slots/@(Model.PageNumber + 1)@(Model.SearchValue.Length == 0 ? "" : "?name=" + Model.SearchValue)">Next Page</a>
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
|
@ -22,20 +22,26 @@ public class SlotsPage : BaseLayout
|
|||
public int SlotCount;
|
||||
|
||||
public List<Slot> Slots;
|
||||
|
||||
public string SearchValue;
|
||||
|
||||
public SlotsPage([NotNull] Database database) : base(database)
|
||||
{}
|
||||
|
||||
public async Task<IActionResult> OnGet([FromRoute] int pageNumber)
|
||||
public async Task<IActionResult> OnGet([FromRoute] int pageNumber, [FromQuery] string? name)
|
||||
{
|
||||
this.SlotCount = await StatisticsHelper.SlotCount();
|
||||
if (string.IsNullOrWhiteSpace(name)) name = "";
|
||||
|
||||
this.SlotCount = await this.Database.Slots.CountAsync(p => p.Name.Contains(name));
|
||||
|
||||
this.SearchValue = name;
|
||||
this.PageNumber = pageNumber;
|
||||
this.PageAmount = (int)Math.Ceiling((double)this.SlotCount / ServerStatics.PageSize);
|
||||
this.PageAmount = Math.Max(1, (int)Math.Ceiling((double)this.SlotCount / ServerStatics.PageSize));
|
||||
|
||||
if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) return this.Redirect($"/slots/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}");
|
||||
|
||||
this.Slots = await this.Database.Slots.Include
|
||||
(p => p.Creator)
|
||||
this.Slots = await this.Database.Slots.Include(p => p.Creator)
|
||||
.Where(p => p.Name.Contains(name))
|
||||
.OrderByDescending(p => p.FirstUploaded)
|
||||
.Skip(pageNumber * ServerStatics.PageSize)
|
||||
.Take(ServerStatics.PageSize)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
@page "/user/{userId:int}"
|
||||
@using System.IO
|
||||
@using System.Web
|
||||
@using LBPUnion.ProjectLighthouse.Helpers.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.Types
|
||||
@using LBPUnion.ProjectLighthouse.Types.Profiles
|
||||
@model LBPUnion.ProjectLighthouse.Pages.UserPage
|
||||
|
@ -43,6 +44,9 @@
|
|||
{
|
||||
"ShowLink", false
|
||||
},
|
||||
{
|
||||
"IsMobile", Model.Request.IsMobile()
|
||||
},
|
||||
})
|
||||
</div>
|
||||
<div class="eight wide right aligned column">
|
||||
|
|
|
@ -27,7 +27,7 @@ public class UserPage : BaseLayout
|
|||
this.ProfileUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == userId);
|
||||
if (this.ProfileUser == null) return this.NotFound();
|
||||
|
||||
this.Photos = await this.Database.Photos.OrderByDescending(p => p.Timestamp).Where(p => p.CreatorId == userId).Take(5).ToListAsync();
|
||||
this.Photos = await this.Database.Photos.OrderByDescending(p => p.Timestamp).Where(p => p.CreatorId == userId).Take(6).ToListAsync();
|
||||
this.Comments = await this.Database.Comments.Include
|
||||
(p => p.Poster)
|
||||
.Include(p => p.Target)
|
||||
|
@ -43,4 +43,4 @@ public class UserPage : BaseLayout
|
|||
|
||||
return this.Page();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@page "/users/{pageNumber:int}"
|
||||
@using LBPUnion.ProjectLighthouse.Helpers.Extensions
|
||||
@using LBPUnion.ProjectLighthouse.Types
|
||||
@model LBPUnion.ProjectLighthouse.Pages.UsersPage
|
||||
|
||||
|
@ -8,25 +9,37 @@
|
|||
}
|
||||
|
||||
<p>There are @Model.UserCount total users.</p>
|
||||
|
||||
<form action="/users/0">
|
||||
<div class="ui icon input">
|
||||
<input type="text" name="name" placeholder="Search users..." value="@Model.SearchValue">
|
||||
<i class="search icon"></i>
|
||||
</div>
|
||||
</form>
|
||||
<div class="ui divider"></div>
|
||||
|
||||
@foreach (User user in Model.Users)
|
||||
{
|
||||
bool isMobile = Model.Request.IsMobile();
|
||||
<div class="ui segment">
|
||||
@await Html.PartialAsync("Partials/UserCardPartial", user, new ViewDataDictionary(ViewData)
|
||||
{
|
||||
{
|
||||
"ShowLink", true
|
||||
},
|
||||
{
|
||||
"IsMobile", isMobile
|
||||
},
|
||||
})
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.PageNumber != 0)
|
||||
{
|
||||
<a href="/users/@(Model.PageNumber - 1)">Previous Page</a>
|
||||
<a href="/users/@(Model.PageNumber - 1)@(Model.SearchValue.Length == 0 ? "" : "?name=" + Model.SearchValue)">Previous Page</a>
|
||||
}
|
||||
@(Model.PageNumber + 1) / @(Model.PageAmount)
|
||||
@if (Model.PageNumber < Model.PageAmount - 1)
|
||||
{
|
||||
<a href="/users/@(Model.PageNumber + 1)">Next Page</a>
|
||||
<a href="/users/@(Model.PageNumber + 1)@(Model.SearchValue.Length == 0 ? "" : "?name=" + Model.SearchValue)">Next Page</a>
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Pages.Layouts;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
|
@ -22,20 +22,24 @@ public class UsersPage : BaseLayout
|
|||
|
||||
public List<User> Users;
|
||||
|
||||
public string SearchValue;
|
||||
|
||||
public UsersPage([NotNull] Database database) : base(database)
|
||||
{}
|
||||
|
||||
public async Task<IActionResult> OnGet([FromRoute] int pageNumber)
|
||||
public async Task<IActionResult> OnGet([FromRoute] int pageNumber, [FromQuery] string? name)
|
||||
{
|
||||
this.UserCount = await StatisticsHelper.UserCount();
|
||||
if (string.IsNullOrWhiteSpace(name)) name = "";
|
||||
|
||||
this.UserCount = await this.Database.Users.CountAsync(u => !u.Banned && u.Username.Contains(name));
|
||||
|
||||
this.SearchValue = name;
|
||||
this.PageNumber = pageNumber;
|
||||
this.PageAmount = (int)Math.Ceiling((double)this.UserCount / ServerStatics.PageSize);
|
||||
this.PageAmount = Math.Max(1, (int)Math.Ceiling((double)this.UserCount / ServerStatics.PageSize));
|
||||
|
||||
if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) return this.Redirect($"/users/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}");
|
||||
|
||||
this.Users = await this.Database.Users.Where
|
||||
(u => !u.Banned)
|
||||
this.Users = await this.Database.Users.Where(u => !u.Banned && u.Username.Contains(name))
|
||||
.OrderByDescending(b => b.UserId)
|
||||
.Skip(pageNumber * ServerStatics.PageSize)
|
||||
.Take(ServerStatics.PageSize)
|
||||
|
|
|
@ -82,6 +82,7 @@ public static class Program
|
|||
return;
|
||||
}
|
||||
|
||||
FileHelper.EnsureDirectoryCreated(Path.Combine(Environment.CurrentDirectory, "png"));
|
||||
if (Directory.Exists("r"))
|
||||
{
|
||||
Logger.Log
|
||||
|
|
|
@ -7,6 +7,11 @@
|
|||
<RootNamespace>LBPUnion.ProjectLighthouse</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.2"/>
|
||||
<PackageReference Include="DDSReader" Version="1.0.8-pre"/>
|
||||
|
@ -23,6 +28,7 @@
|
|||
</PackageReference>
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.0"/>
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3"/>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Kettu;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
|
@ -13,8 +15,11 @@ using Microsoft.AspNetCore.HttpOverrides;
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Hosting.Internal;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.OpenApi.Models;
|
||||
#if RELEASE
|
||||
using Microsoft.Extensions.Hosting.Internal;
|
||||
#endif
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Startup;
|
||||
|
||||
|
@ -56,6 +61,30 @@ public class Startup
|
|||
}
|
||||
);
|
||||
|
||||
services.AddSwaggerGen
|
||||
(
|
||||
c =>
|
||||
{
|
||||
// Give swagger the name and version of our project
|
||||
c.SwaggerDoc
|
||||
(
|
||||
"v1",
|
||||
new OpenApiInfo
|
||||
{
|
||||
Title = "Project Lighthouse API",
|
||||
Version = "v1",
|
||||
}
|
||||
);
|
||||
|
||||
// Filter out endpoints not in /api/v1
|
||||
c.DocumentFilter<SwaggerFilter>();
|
||||
|
||||
// Add XMLDoc to swagger
|
||||
string xmlDocs = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||
c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlDocs));
|
||||
}
|
||||
);
|
||||
|
||||
#if DEBUG
|
||||
services.AddSingleton<IHostLifetime, DebugWarmupLifetime>();
|
||||
#else
|
||||
|
@ -85,6 +114,15 @@ public class Startup
|
|||
|
||||
app.UseForwardedHeaders();
|
||||
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI
|
||||
(
|
||||
c =>
|
||||
{
|
||||
c.SwaggerEndpoint("v1/swagger.json", "Project Lighthouse API");
|
||||
}
|
||||
);
|
||||
|
||||
// Logs every request and the response to it
|
||||
// Example: "200, 13ms: GET /LITTLEBIGPLANETPS3_XML/news"
|
||||
// Example: "404, 127ms: GET /asdasd?query=osucookiezi727ppbluezenithtopplayhdhr"
|
||||
|
|
|
@ -8,14 +8,75 @@ div.main {
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
div.statsUnderTitle > i {
|
||||
#lighthouse-debug-info > p {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
/*#region Cards*/
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cardIcon {
|
||||
margin-right: 10px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.cardStats {
|
||||
height: fit-content;
|
||||
vertical-align: center;
|
||||
align-self: center
|
||||
}
|
||||
|
||||
.cardStats > h1,
|
||||
.cardStats > h2 {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.cardButtons {
|
||||
height: 100px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.cardButtons > a {
|
||||
margin-bottom: 5px !important;
|
||||
vertical-align: center;
|
||||
}
|
||||
|
||||
.cardStatsUnderTitle {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
div.cardStatsUnderTitle > i {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
div.statsUnderTitle > span {
|
||||
div.cardStatsUnderTitle > span {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#lighthouse-debug-info > p {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
/*#region Slot cards*/
|
||||
|
||||
.slotCardIcon {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
/*#endregion Slot cards*/
|
||||
|
||||
/*#region User cards*/
|
||||
|
||||
.userCardIcon {
|
||||
border-radius: .28571429rem;
|
||||
}
|
||||
|
||||
|
||||
/*#endregion User cards*/
|
||||
|
||||
/*#endregion Cards*/
|
18
ProjectLighthouse/Types/AdminPanelStatistic.cs
Normal file
18
ProjectLighthouse/Types/AdminPanelStatistic.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
#nullable enable
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types;
|
||||
|
||||
public struct AdminPanelStatistic
|
||||
{
|
||||
public AdminPanelStatistic(string statisticNamePlural, int count, string? viewAllEndpoint = null)
|
||||
{
|
||||
this.StatisticNamePlural = statisticNamePlural;
|
||||
this.Count = count;
|
||||
this.ViewAllEndpoint = viewAllEndpoint;
|
||||
}
|
||||
|
||||
public readonly string StatisticNamePlural;
|
||||
public readonly int Count;
|
||||
|
||||
public readonly string? ViewAllEndpoint;
|
||||
}
|
10
ProjectLighthouse/Types/Api/StatisticsResponse.cs
Normal file
10
ProjectLighthouse/Types/Api/StatisticsResponse.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace LBPUnion.ProjectLighthouse.Types.Api;
|
||||
|
||||
public class StatisticsResponse
|
||||
{
|
||||
public int RecentMatches { get; set; }
|
||||
public int Slots { get; set; }
|
||||
public int Users { get; set; }
|
||||
public int TeamPicks { get; set; }
|
||||
public int Photos { get; set; }
|
||||
}
|
9
ProjectLighthouse/Types/ApiEndpointController.cs
Normal file
9
ProjectLighthouse/Types/ApiEndpointController.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types;
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/v1/")]
|
||||
[Produces("application/json")]
|
||||
public class ApiEndpointController : ControllerBase
|
||||
{}
|
26
ProjectLighthouse/Types/Levels/MinimalSlot.cs
Normal file
26
ProjectLighthouse/Types/Levels/MinimalSlot.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
namespace LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
|
||||
public struct MinimalSlot
|
||||
{
|
||||
public int SlotId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string IconHash { get; set; }
|
||||
public bool TeamPick { get; set; }
|
||||
public GameVersion GameVersion { get; set; }
|
||||
#if DEBUG
|
||||
public long FirstUploaded { get; set; }
|
||||
#endif
|
||||
|
||||
public static MinimalSlot FromSlot(Slot slot)
|
||||
=> new()
|
||||
{
|
||||
SlotId = slot.SlotId,
|
||||
Name = slot.Name,
|
||||
IconHash = slot.IconHash,
|
||||
TeamPick = slot.TeamPick,
|
||||
GameVersion = slot.GameVersion,
|
||||
#if DEBUG
|
||||
FirstUploaded = slot.FirstUploaded,
|
||||
#endif
|
||||
};
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
|
@ -19,6 +20,7 @@ public class Slot
|
|||
{
|
||||
[XmlAttribute("type")]
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public string Type { get; set; } = "user";
|
||||
|
||||
[Key]
|
||||
|
@ -35,24 +37,29 @@ public class Slot
|
|||
public string IconHash { get; set; } = "";
|
||||
|
||||
[XmlElement("rootLevel")]
|
||||
[JsonIgnore]
|
||||
public string RootLevel { get; set; } = "";
|
||||
|
||||
[JsonIgnore]
|
||||
public string ResourceCollection { get; set; } = "";
|
||||
|
||||
[NotMapped]
|
||||
[XmlElement("resource")]
|
||||
[JsonIgnore]
|
||||
public string[] Resources {
|
||||
get => this.ResourceCollection.Split(",");
|
||||
set => this.ResourceCollection = string.Join(',', value);
|
||||
}
|
||||
|
||||
[XmlIgnore]
|
||||
[JsonIgnore]
|
||||
public int LocationId { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
public int CreatorId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(CreatorId))]
|
||||
[JsonIgnore]
|
||||
public User? Creator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -60,6 +67,7 @@ public class Slot
|
|||
/// </summary>
|
||||
[XmlElement("location")]
|
||||
[ForeignKey(nameof(LocationId))]
|
||||
[JsonIgnore]
|
||||
public Location? Location { get; set; }
|
||||
|
||||
[XmlElement("initiallyLocked")]
|
||||
|
@ -78,6 +86,7 @@ public class Slot
|
|||
public string AuthorLabels { get; set; } = "";
|
||||
|
||||
[XmlElement("background")]
|
||||
[JsonIgnore]
|
||||
public string BackgroundHash { get; set; } = "";
|
||||
|
||||
[XmlElement("minPlayers")]
|
||||
|
@ -103,6 +112,7 @@ public class Slot
|
|||
|
||||
[XmlIgnore]
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public int Hearts {
|
||||
get {
|
||||
using Database database = new();
|
||||
|
@ -124,42 +134,55 @@ public class Slot
|
|||
public int PlaysComplete => this.PlaysLBP1Complete + this.PlaysLBP2Complete + this.PlaysLBP3Complete + this.PlaysLBPVitaComplete;
|
||||
|
||||
[XmlIgnore]
|
||||
[JsonIgnore]
|
||||
public int PlaysLBP1 { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
[JsonIgnore]
|
||||
public int PlaysLBP1Complete { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
[JsonIgnore]
|
||||
public int PlaysLBP1Unique { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
[JsonIgnore]
|
||||
public int PlaysLBP2 { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
[JsonIgnore]
|
||||
public int PlaysLBP2Complete { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
[JsonIgnore]
|
||||
public int PlaysLBP2Unique { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
[JsonIgnore]
|
||||
public int PlaysLBP3 { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
[JsonIgnore]
|
||||
public int PlaysLBP3Complete { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
[JsonIgnore]
|
||||
public int PlaysLBP3Unique { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
[JsonIgnore]
|
||||
public int PlaysLBPVita { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
[JsonIgnore]
|
||||
public int PlaysLBPVitaComplete { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
[JsonIgnore]
|
||||
public int PlaysLBPVitaUnique { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
[XmlElement("thumbsup")]
|
||||
public int Thumbsup {
|
||||
get {
|
||||
|
@ -170,6 +193,7 @@ public class Slot
|
|||
}
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
[XmlElement("thumbsdown")]
|
||||
public int Thumbsdown {
|
||||
get {
|
||||
|
@ -180,6 +204,7 @@ public class Slot
|
|||
}
|
||||
|
||||
[NotMapped]
|
||||
[JsonPropertyName("averageRating")]
|
||||
[XmlElement("averageRating")]
|
||||
public double RatingLBP1 {
|
||||
get {
|
||||
|
@ -193,6 +218,7 @@ public class Slot
|
|||
}
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
[XmlElement("reviewCount")]
|
||||
public int ReviewCount {
|
||||
get {
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types;
|
||||
|
||||
/// <summary>
|
||||
/// The data sent from POST /LOGIN.
|
||||
/// </summary>
|
||||
public class LoginData
|
||||
{
|
||||
|
||||
public static readonly string UsernamePrefix = Encoding.ASCII.GetString
|
||||
(
|
||||
new byte[]
|
||||
{
|
||||
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 BinaryReader reader = new(ms);
|
||||
|
||||
if (!str.Contains(UsernamePrefix)) return null;
|
||||
|
||||
LoginData loginData = new();
|
||||
|
||||
reader.BaseStream.Position = str.IndexOf(UsernamePrefix, StringComparison.Ordinal) + UsernamePrefix.Length;
|
||||
loginData.Username = BinaryHelper.ReadString(reader).Replace("\0", string.Empty);
|
||||
|
||||
return loginData;
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.News;
|
||||
|
||||
/// <summary>
|
||||
/// Used on the info moon on LBP1. Broken for unknown reasons
|
||||
/// </summary>
|
||||
public class NewsEntry
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Summary { get; set; }
|
||||
public string Text { get; set; }
|
||||
public NewsImage Image { get; set; }
|
||||
public string Category { get; set; }
|
||||
public long Date { get; set; }
|
||||
|
||||
public string Serialize()
|
||||
=> LbpSerializer.StringElement("id", this.Id) +
|
||||
LbpSerializer.StringElement("title", this.Title) +
|
||||
LbpSerializer.StringElement("summary", this.Summary) +
|
||||
LbpSerializer.StringElement("text", this.Text) +
|
||||
LbpSerializer.StringElement("date", this.Date) +
|
||||
this.Image.Serialize() +
|
||||
LbpSerializer.StringElement("category", this.Category);
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.News;
|
||||
|
||||
public class NewsImage
|
||||
{
|
||||
public string Hash { get; set; }
|
||||
public string Alignment { get; set; }
|
||||
|
||||
public string Serialize()
|
||||
=> LbpSerializer.StringElement("image", LbpSerializer.StringElement("hash", this.Hash) + LbpSerializer.StringElement("alignment", this.Alignment));
|
||||
}
|
|
@ -5,4 +5,7 @@ public enum Platform
|
|||
PS3 = 0,
|
||||
RPCS3 = 1,
|
||||
Vita = 2,
|
||||
PSP = 3,
|
||||
UnitTest = 4,
|
||||
Unknown = -1,
|
||||
}
|
7
ProjectLighthouse/Types/Tickets/DataHeader.cs
Normal file
7
ProjectLighthouse/Types/Tickets/DataHeader.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace LBPUnion.ProjectLighthouse.Types.Tickets;
|
||||
|
||||
public struct DataHeader
|
||||
{
|
||||
public DataType Type;
|
||||
public ushort Length;
|
||||
}
|
11
ProjectLighthouse/Types/Tickets/DataType.cs
Normal file
11
ProjectLighthouse/Types/Tickets/DataType.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace LBPUnion.ProjectLighthouse.Types.Tickets;
|
||||
|
||||
public enum DataType : byte
|
||||
{
|
||||
Empty = 0x00,
|
||||
UInt32 = 0x01,
|
||||
UInt64 = 0x02,
|
||||
String = 0x04,
|
||||
Timestamp = 0x07,
|
||||
Binary = 0x08,
|
||||
}
|
188
ProjectLighthouse/Types/Tickets/NPTicket.cs
Normal file
188
ProjectLighthouse/Types/Tickets/NPTicket.cs
Normal file
|
@ -0,0 +1,188 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Kettu;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types.Settings;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Tickets;
|
||||
|
||||
/// <summary>
|
||||
/// A PSN ticket, typically sent from PS3/RPCN
|
||||
/// </summary>
|
||||
public class NPTicket
|
||||
{
|
||||
public string Username { get; set; }
|
||||
|
||||
private Version ticketVersion { get; set; }
|
||||
|
||||
public Platform Platform { get; set; }
|
||||
|
||||
public uint IssuerId { get; set; }
|
||||
public ulong IssuedDate { get; set; }
|
||||
public ulong ExpireDate { get; set; }
|
||||
|
||||
private string titleId { get; set; }
|
||||
|
||||
public GameVersion GameVersion { get; set; }
|
||||
|
||||
private static void Read21Ticket(NPTicket npTicket, TicketReader reader)
|
||||
{
|
||||
reader.ReadTicketString(); // "Serial id", but its apparently not what we're looking for
|
||||
|
||||
npTicket.IssuerId = reader.ReadTicketUInt32();
|
||||
npTicket.IssuedDate = reader.ReadTicketUInt64();
|
||||
npTicket.ExpireDate = reader.ReadTicketUInt64();
|
||||
|
||||
reader.ReadTicketUInt64(); // PSN User id, we don't care about this
|
||||
|
||||
npTicket.Username = reader.ReadTicketString();
|
||||
|
||||
reader.ReadTicketString(); // Country
|
||||
reader.ReadTicketString(); // Domain
|
||||
|
||||
npTicket.titleId = reader.ReadTicketString();
|
||||
}
|
||||
|
||||
// Function is here for future use incase we ever need to read more from the ticket
|
||||
private static void Read30Ticket(NPTicket npTicket, TicketReader reader)
|
||||
{
|
||||
Read21Ticket(npTicket, reader);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://www.psdevwiki.com/ps3/X-I-5-Ticket
|
||||
/// </summary>
|
||||
public static NPTicket? CreateFromBytes(byte[] data)
|
||||
{
|
||||
NPTicket npTicket = new();
|
||||
#if DEBUG
|
||||
if (data[0] == 'u' && ServerStatics.IsUnitTesting)
|
||||
{
|
||||
string dataStr = Encoding.UTF8.GetString(data);
|
||||
if (dataStr.StartsWith("unitTestTicket"))
|
||||
{
|
||||
npTicket = new NPTicket
|
||||
{
|
||||
IssuerId = 0,
|
||||
ticketVersion = new Version(0, 0),
|
||||
Platform = Platform.UnitTest,
|
||||
GameVersion = GameVersion.LittleBigPlanet2,
|
||||
ExpireDate = 0,
|
||||
IssuedDate = 0,
|
||||
};
|
||||
|
||||
npTicket.Username = dataStr.Substring(14);
|
||||
|
||||
return npTicket;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
try
|
||||
{
|
||||
using MemoryStream ms = new(data);
|
||||
using TicketReader reader = new(ms);
|
||||
|
||||
npTicket.ticketVersion = reader.ReadTicketVersion();
|
||||
|
||||
reader.ReadBytes(4); // Skip header
|
||||
|
||||
reader.ReadUInt16BE(); // Ticket length, we don't care about this
|
||||
|
||||
#if DEBUG
|
||||
SectionHeader bodyHeader = reader.ReadSectionHeader();
|
||||
Logger.Log($"bodyHeader.Type is {bodyHeader.Type}", LoggerLevelLogin.Instance);
|
||||
#else
|
||||
reader.ReadSectionHeader();
|
||||
#endif
|
||||
|
||||
switch (npTicket.ticketVersion)
|
||||
{
|
||||
case "2.1":
|
||||
Read21Ticket(npTicket, reader);
|
||||
break;
|
||||
case "3.0":
|
||||
Read30Ticket(npTicket, reader);
|
||||
break;
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// We already read the title id, however we need to do some post-processing to get what we want.
|
||||
// Current data: UP9000-BCUS98245_00
|
||||
// We need to chop this to get the titleId we're looking for
|
||||
npTicket.titleId = npTicket.titleId.Substring(7); // Trim UP9000-
|
||||
npTicket.titleId = npTicket.titleId.Substring(0, npTicket.titleId.Length - 3); // Trim _00 at the end
|
||||
// Data now (hopefully): BCUS98245
|
||||
|
||||
#if DEBUG
|
||||
Logger.Log($"titleId is {npTicket.titleId}", LoggerLevelLogin.Instance);
|
||||
#endif
|
||||
|
||||
npTicket.GameVersion = GameVersionHelper.FromTitleId(npTicket.titleId); // Finally, convert it to GameVersion
|
||||
|
||||
if (npTicket.GameVersion == GameVersion.Unknown)
|
||||
{
|
||||
Logger.Log($"Could not determine game version from title id {npTicket.titleId}", LoggerLevelLogin.Instance);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Production PSN Issuer ID: 0x100
|
||||
// RPCN Issuer ID: 0x33333333
|
||||
npTicket.Platform = npTicket.IssuerId switch
|
||||
{
|
||||
0x100 => Platform.PS3,
|
||||
0x33333333 => Platform.RPCS3,
|
||||
_ => Platform.Unknown,
|
||||
};
|
||||
|
||||
if (npTicket.Platform == Platform.PS3 && npTicket.GameVersion == GameVersion.LittleBigPlanetVita) npTicket.Platform = Platform.Vita;
|
||||
|
||||
if (npTicket.Platform == Platform.Unknown)
|
||||
{
|
||||
Logger.Log($"Could not determine platform from IssuerId {npTicket.IssuerId} decimal", LoggerLevelLogin.Instance);
|
||||
return null;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Logger.Log("npTicket data:", LoggerLevelLogin.Instance);
|
||||
foreach (string line in JsonSerializer.Serialize(npTicket).Split('\n'))
|
||||
{
|
||||
Logger.Log(line, LoggerLevelLogin.Instance);
|
||||
}
|
||||
#endif
|
||||
|
||||
return npTicket;
|
||||
}
|
||||
catch(NotImplementedException)
|
||||
{
|
||||
Logger.Log($"The ticket version {npTicket.ticketVersion} is not implemented yet.", LoggerLevelLogin.Instance);
|
||||
Logger.Log
|
||||
(
|
||||
"Please let us know that this is a ticket version that is actually used on our issue tracker at https://github.com/LBPUnion/project-lighthouse/issues !",
|
||||
LoggerLevelLogin.Instance
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Logger.Log("Failed to read npTicket!", LoggerLevelLogin.Instance);
|
||||
Logger.Log("Either this is spam data, or the more likely that this is a bug.", LoggerLevelLogin.Instance);
|
||||
Logger.Log
|
||||
(
|
||||
"Please report the following exception to our issue tracker at https://github.com/LBPUnion/project-lighthouse/issues!",
|
||||
LoggerLevelLogin.Instance
|
||||
);
|
||||
|
||||
foreach (string line in e.ToDetailedException().Split('\n'))
|
||||
{
|
||||
Logger.Log(line, LoggerLevelLogin.Instance);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
7
ProjectLighthouse/Types/Tickets/SectionHeader.cs
Normal file
7
ProjectLighthouse/Types/Tickets/SectionHeader.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace LBPUnion.ProjectLighthouse.Types.Tickets;
|
||||
|
||||
public struct SectionHeader
|
||||
{
|
||||
public SectionType Type;
|
||||
public ushort Length;
|
||||
}
|
7
ProjectLighthouse/Types/Tickets/SectionType.cs
Normal file
7
ProjectLighthouse/Types/Tickets/SectionType.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace LBPUnion.ProjectLighthouse.Types.Tickets;
|
||||
|
||||
public enum SectionType : byte
|
||||
{
|
||||
Body = 0x00,
|
||||
Footer = 0x02,
|
||||
}
|
61
ProjectLighthouse/Types/Tickets/TicketReader.cs
Normal file
61
ProjectLighthouse/Types/Tickets/TicketReader.cs
Normal file
|
@ -0,0 +1,61 @@
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using LBPUnion.ProjectLighthouse.Helpers.Extensions;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Types.Tickets;
|
||||
|
||||
public class TicketReader : BinaryReader
|
||||
{
|
||||
public TicketReader([NotNull] Stream input) : base(input)
|
||||
{}
|
||||
|
||||
public Version ReadTicketVersion() => new(this.ReadByte() >> 4, this.ReadByte());
|
||||
|
||||
public SectionHeader ReadSectionHeader()
|
||||
{
|
||||
this.ReadByte();
|
||||
|
||||
SectionHeader sectionHeader = new();
|
||||
sectionHeader.Type = (SectionType)this.ReadByte();
|
||||
sectionHeader.Length = this.ReadUInt16BE();
|
||||
|
||||
return sectionHeader;
|
||||
}
|
||||
|
||||
public DataHeader ReadDataHeader()
|
||||
{
|
||||
DataHeader dataHeader = new();
|
||||
dataHeader.Type = (DataType)this.ReadUInt16BE();
|
||||
dataHeader.Length = this.ReadUInt16BE();
|
||||
|
||||
return dataHeader;
|
||||
}
|
||||
|
||||
public byte[] ReadTicketBinary()
|
||||
{
|
||||
DataHeader dataHeader = this.ReadDataHeader();
|
||||
Debug.Assert(dataHeader.Type == DataType.Binary || dataHeader.Type == DataType.String);
|
||||
|
||||
return this.ReadBytes(dataHeader.Length);
|
||||
}
|
||||
|
||||
public string ReadTicketString() => Encoding.UTF8.GetString(this.ReadTicketBinary()).TrimEnd('\0');
|
||||
|
||||
public uint ReadTicketUInt32()
|
||||
{
|
||||
DataHeader dataHeader = this.ReadDataHeader();
|
||||
Debug.Assert(dataHeader.Type == DataType.UInt32);
|
||||
|
||||
return this.ReadUInt32BE();
|
||||
}
|
||||
|
||||
public ulong ReadTicketUInt64()
|
||||
{
|
||||
DataHeader dataHeader = this.ReadDataHeader();
|
||||
Debug.Assert(dataHeader.Type == DataType.UInt64 || dataHeader.Type == DataType.Timestamp);
|
||||
|
||||
return this.ReadUInt64BE();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Profiles;
|
||||
|
@ -13,11 +14,17 @@ public class User
|
|||
public readonly ClientsConnected ClientsConnected = new();
|
||||
public int UserId { get; set; }
|
||||
public string Username { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string Password { get; set; }
|
||||
|
||||
public string IconHash { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int Game { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public int Lists => 0;
|
||||
|
||||
/// <summary>
|
||||
|
@ -26,6 +33,7 @@ public class User
|
|||
public string Biography { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public string WebsiteAvatarHash {
|
||||
get {
|
||||
string avatarHash = this.IconHash;
|
||||
|
@ -40,6 +48,7 @@ public class User
|
|||
}
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public int Reviews {
|
||||
get {
|
||||
using Database database = new();
|
||||
|
@ -48,6 +57,7 @@ public class User
|
|||
}
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public int Comments {
|
||||
get {
|
||||
using Database database = new();
|
||||
|
@ -56,6 +66,7 @@ public class User
|
|||
}
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public int PhotosByMe {
|
||||
get {
|
||||
using Database database = new();
|
||||
|
@ -64,6 +75,7 @@ public class User
|
|||
}
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public int PhotosWithMe {
|
||||
get {
|
||||
using Database database = new();
|
||||
|
@ -71,15 +83,18 @@ public class User
|
|||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public int LocationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The location of the profile card on the user's earth
|
||||
/// </summary>
|
||||
[ForeignKey("LocationId")]
|
||||
[JsonIgnore]
|
||||
public Location Location { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public int HeartedLevels {
|
||||
get {
|
||||
using Database database = new();
|
||||
|
@ -88,6 +103,7 @@ public class User
|
|||
}
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public int HeartedUsers {
|
||||
get {
|
||||
using Database database = new();
|
||||
|
@ -96,6 +112,7 @@ public class User
|
|||
}
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public int QueuedLevels {
|
||||
get {
|
||||
using Database database = new();
|
||||
|
@ -103,10 +120,13 @@ public class User
|
|||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string Pins { get; set; } = "";
|
||||
|
||||
[JsonIgnore]
|
||||
public string PlanetHash { get; set; } = "";
|
||||
|
||||
[JsonIgnore]
|
||||
public int Hearts {
|
||||
get {
|
||||
using Database database = new();
|
||||
|
@ -115,8 +135,10 @@ public class User
|
|||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsAdmin { get; set; } = false;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool PasswordResetRequired { get; set; }
|
||||
|
||||
public string YayHash { get; set; } = "";
|
||||
|
@ -125,6 +147,7 @@ public class User
|
|||
|
||||
#nullable enable
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public string Status {
|
||||
get {
|
||||
using Database database = new();
|
||||
|
@ -139,8 +162,10 @@ public class User
|
|||
}
|
||||
#nullable disable
|
||||
|
||||
[JsonIgnore]
|
||||
public bool Banned { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string BannedReason { get; set; }
|
||||
|
||||
public string Serialize(GameVersion gameVersion = GameVersion.LittleBigPlanet1)
|
||||
|
@ -178,6 +203,7 @@ public class User
|
|||
/// The number of used slots on the earth
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public int UsedSlots {
|
||||
get {
|
||||
using Database database = new();
|
||||
|
@ -194,6 +220,7 @@ public class User
|
|||
/// <summary>
|
||||
/// The number of slots remaining on the earth
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public int FreeSlots => ServerSettings.Instance.EntitledSlots - this.UsedSlots;
|
||||
|
||||
private static readonly string[] slotTypes =
|
||||
|
|
17
ProjectLighthouse/Types/Version.cs
Normal file
17
ProjectLighthouse/Types/Version.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
namespace LBPUnion.ProjectLighthouse.Types;
|
||||
|
||||
public class Version
|
||||
{
|
||||
public int Major { get; set; }
|
||||
public int Minor { get; set; }
|
||||
|
||||
public Version(int major, int minor)
|
||||
{
|
||||
this.Major = major;
|
||||
this.Minor = minor;
|
||||
}
|
||||
|
||||
public override string ToString() => $"{this.Major}.{this.Minor}";
|
||||
|
||||
public static implicit operator string(Version v) => v.ToString();
|
||||
}
|
121
README.md
121
README.md
|
@ -1,121 +1,38 @@
|
|||
# Project Lighthouse
|
||||
|
||||
Project Lighthouse is a clean-room, open-source custom server for LittleBigPlanet. This is a project conducted by
|
||||
the [LBP Union Ministry of Technology Research and Development team.](https://www.lbpunion.com/technology) For concerns
|
||||
and inquiries about the project, please [contact us here.](https://www.lbpunion.com/contact) For general questions and
|
||||
discussion about Project Lighthouse, please see
|
||||
the [LBP Union Ministry of Technology Research and Development team](https://www.lbpunion.com/technology).
|
||||
|
||||
For concerns and inquiries about the project, please contact us [here](https://www.lbpunion.com/contact).
|
||||
|
||||
For general questions and discussion about Project Lighthouse, please see
|
||||
the [megathread](https://www.lbpunion.com/forum/union-hall/project-lighthouse-littlebigplanet-private-servers-megathread)
|
||||
on our forum.
|
||||
|
||||
## WARNING!
|
||||
## DISCLAIMER
|
||||
|
||||
This is **beta software**, and thus is **not stable**.
|
||||
This is **beta software**, and thus is **not stable nor is it secure**.
|
||||
|
||||
We're not responsible if someone hacks your machine and wipes your database.
|
||||
While Project Lighthouse is in a mostly working state, **we ask that our software not be used in a production
|
||||
environment until release**.
|
||||
|
||||
Make frequent backups, and be sure to report any vulnerabilities.
|
||||
This is because we have not entirely nailed security down yet, and **your instance WILL get attacked** as a result. It's
|
||||
happened before, and it'll happen again.
|
||||
|
||||
Simply put, **Project Lighthouse is not ready for the general public yet**.
|
||||
|
||||
In addition, we're not responsible if someone hacks your machine and wipes your database, so make frequent backups, and
|
||||
be sure to report any vulnerabilities. Thank you in advance.
|
||||
|
||||
## Building
|
||||
|
||||
This will be written when we're out of beta. Consider this your barrier to entry ;).
|
||||
|
||||
It is recommended to build with Release if you plan to use Lighthouse in a production environment.
|
||||
It is recommended to build with `Release` if you plan to use Lighthouse in a production environment.
|
||||
|
||||
## Running
|
||||
## Contributing
|
||||
|
||||
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. 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.
|
||||
|
||||
Once you've gotten MySQL running you can run Lighthouse. It will take care of the rest.
|
||||
|
||||
## 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.
|
||||
|
||||
There are also community-provided guides in [the official LBP Union Discord](https://www.lbpunion.com/discord), which
|
||||
you can follow at your own discretion.
|
||||
|
||||
*Note: This requires a modified copy of RPCS3. You can find a working
|
||||
version [on our GitHub](https://github.com/LBPUnion/rpcs3).*
|
||||
|
||||
Start by getting a copy of LittleBigPlanet 1/2 installed. (Check the LittleBigPlanet 1 section, since you'll need to do
|
||||
extra steps for your game to not crash upon entering pod computer). It can be digital (NPUA80472/NPUA80662) or disc (
|
||||
BCUS98148/BCUS98245). For those that don't, the [RPCS3 Quickstart Guide](https://rpcs3.net/quickstart) should cover it.
|
||||
|
||||
Next, download [UnionPatcher](https://github.com/LBPUnion/UnionPatcher/). Binaries can be found by reading the README.md
|
||||
file.
|
||||
|
||||
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`. You can grab your title id by right clicking the game in RPCS3 and
|
||||
clicking Copy Info -> Copy Serial.
|
||||
|
||||
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`.)
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
### LittleBigPlanet 1
|
||||
|
||||
For LittleBigPlanet 1 to work with RPCS3, follow the steps for LittleBigPlanet 2.
|
||||
|
||||
First, open your favourite hex editor. We recommend [HxD](https://mh-nexus.de/en/hxd/).
|
||||
|
||||
Once you have a hex editor open, open your `EBOOTlocalhost.elf` file and search for the hex
|
||||
values `73 63 65 4E 70 43 6F 6D 6D 65 72 63 65 32`. In HxD, this would be done by clicking on Search -> Replace,
|
||||
clicking on the `Hex-values` tab, and entering the hex there.
|
||||
|
||||
Then, you can zero it out by replacing it with `00 00 00 00 00 00 00 00 00 00 00 00 00 00`.
|
||||
|
||||
What this does is remove all the references to the sceNpCommerce2 function. The function is used for purchasing DLC,
|
||||
which is impossible on Lighthouse. The reason why it must be patched out is because RPCS3 doesn't support the function
|
||||
at this moment.
|
||||
|
||||
Then save the file, and your LBP1 EBOOT can now be used with RPCS3.
|
||||
|
||||
Finally, take a break. Chances are that took a while.
|
||||
|
||||
## Contributing Tips
|
||||
|
||||
### Database migrations
|
||||
|
||||
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`.
|
||||
2. Making sure `LIGHTHOUSE_DB_CONNECTION_STRING` is set correctly. See the `Running` section for more details.
|
||||
3. Modifying the database schema via the C# portion of the code. Do not modify the actual SQL database.
|
||||
4. Running `dotnet ef migrations add <NameOfMigrationInPascalCase> --project ProjectLighthouse`.
|
||||
|
||||
This process will create a migration file from the changes made in the C# code.
|
||||
|
||||
The new migrations will automatically be applied upon starting Lighthouse.
|
||||
|
||||
### Running tests
|
||||
|
||||
You can run tests either through your IDE or by running `dotnet tests`.
|
||||
|
||||
Keep in mind while running database tests (which most tests are) you need to have `LIGHTHOUSE_DB_CONNECTION_STRING` set.
|
||||
|
||||
### Continuous Integration (CI) Tips
|
||||
|
||||
- You can skip CI runs for a commit if you specify `[skip ci]` at the beginning of the commit name. This is useful for
|
||||
formatting changes, etc.
|
||||
- When creating your first pull request, CI will not run initially. A team member will have to approve you for use of
|
||||
running CI on a pull request. This is because of GitHub policy.
|
||||
Please see `CONTRIBUTING.md` for more information.
|
||||
|
||||
## Compatibility across games and platforms
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue