diff --git a/.idea/.idea.ProjectLighthouse/.idea/.name b/.idea/.idea.ProjectLighthouse/.idea/.name new file mode 100644 index 00000000..cbe8482b --- /dev/null +++ b/.idea/.idea.ProjectLighthouse/.idea/.name @@ -0,0 +1 @@ +ProjectLighthouse \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/ClientConfigurationController.cs b/ProjectLighthouse/Controllers/ClientConfigurationController.cs index acf08f0f..fc12c2e7 100644 --- a/ProjectLighthouse/Controllers/ClientConfigurationController.cs +++ b/ProjectLighthouse/Controllers/ClientConfigurationController.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using LBPUnion.ProjectLighthouse.Types.Settings; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace LBPUnion.ProjectLighthouse.Controllers @@ -12,10 +13,14 @@ namespace LBPUnion.ProjectLighthouse.Controllers [HttpGet("network_settings.nws")] [SuppressMessage("ReSharper", "StringLiteralTypo")] public IActionResult NetworkSettings() - => this.Ok + { + HostString hostname = this.Request.Host; + return this.Ok ( - "ProbabilityOfPacketDelay 0.0\nMinPacketDelayFrames 0\nMaxPacketDelayFrames 3\nProbabilityOfPacketDrop 0.0\nEnableFakeConditionsForLoopback true\nNumberOfFramesPredictionAllowedForNonLocalPlayer 1000\nEnablePrediction true\nMinPredictedFrames 0\nMaxPredictedFrames 10\nAllowGameRendCameraSplit true\nFramesBeforeAgressiveCatchup 30\nPredictionPadSides 200\nPredictionPadTop 200\nPredictionPadBottom 200\nShowErrorNumbers true\nAllowModeratedLevels true\nAllowModeratedPoppetItems true\nShowLevelBoos true\nTIMEOUT_WAIT_FOR_JOIN_RESPONSE_FROM_PREV_PARTY_HOST 50.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_HOST 30.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_MEMBER 45.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN_FRIEND 15.0\nTIMEOUT_WAIT_FOR_CONNECTION_FROM_HOST 30.0\nTIMEOUT_WAIT_FOR_ROOM_ID_TO_JOIN 60.0\nTIMEOUT_WAIT_FOR_GET_NUM_PLAYERS_ONLINE 60.0\nTIMEOUT_WAIT_FOR_SIGNALLING_CONNECTIONS 120.0\nTIMEOUT_WAIT_FOR_PARTY_DATA 60.0\nTIME_TO_WAIT_FOR_LEAVE_MESSAGE_TO_COME_BACK 20.0\nTIME_TO_WAIT_FOR_FOLLOWING_REQUESTS_TO_ARRIVE 30.0\nTIMEOUT_WAIT_FOR_FINISHED_MIGRATING_HOST 30.0\nTIMEOUT_WAIT_FOR_PARTY_LEADER_FINISH_JOINING 45.0\nTIMEOUT_WAIT_FOR_QUICKPLAY_LEVEL 60.0\nTIMEOUT_WAIT_FOR_PLAYERS_TO_JOIN 30.0\nTIMEOUT_WAIT_FOR_DIVE_IN_PLAYERS 120.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 30.0\nTIMEOUT_DIVE_IN_TOTAL 1000000.0\nTIMEOUT_WAIT_FOR_SOCKET_CONNECTION 120.0\nTIMEOUT_WAIT_FOR_REQUEST_RESOURCE_MESSAGE 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_GET_RESOURCE_LIST 120.0\nTIMEOUT_WAIT_FOR_CLIENT_TO_LOAD_RESOURCES 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_SAVE_GAME_STATE 30.0\nTIMEOUT_WAIT_FOR_ADD_PLAYERS_TO_TAKE 30.0\nTIMEOUT_WAIT_FOR_UPDATE_FROM_CLIENT 90.0\nTIMEOUT_WAIT_FOR_HOST_TO_GET_RESOURCE_LIST 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_SAVE_GAME_STATE 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_ADD_US 30.0\nTIMEOUT_WAIT_FOR_UPDATE 60.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN 50.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_PRESENCE 60.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_CONNECTION 120.0\nSECONDS_BETWEEN_PINS_AWARDED_UPLOADS 300.0\nEnableKeepAlive true\nAllowVoIPRecordingPlayback true\nCDNHostName localhost\nTelemetryServer localhost\nOverheatingThresholdDisallowMidgameJoin 0.95\nMaxCatchupFrames 3\nMaxLagBeforeShowLoading 23\nMinLagBeforeHideLoading 30\nLagImprovementInflectionPoint -1.0\nFlickerThreshold 2.0\nClosedDemo2014Version 1\nClosedDemo2014Expired false\nEnablePlayedFilter true\nEnableCommunityDecorations true\nGameStateUpdateRate 10.0\nGameStateUpdateRateWithConsumers 1.0\nDisableDLCPublishCheck false\nEnableDiveIn true\nEnableHackChecks false" + "ProbabilityOfPacketDelay 0.0\nMinPacketDelayFrames 0\nMaxPacketDelayFrames 3\nProbabilityOfPacketDrop 0.0\nEnableFakeConditionsForLoopback true\nNumberOfFramesPredictionAllowedForNonLocalPlayer 1000\nEnablePrediction true\nMinPredictedFrames 0\nMaxPredictedFrames 10\nAllowGameRendCameraSplit true\nFramesBeforeAgressiveCatchup 30\nPredictionPadSides 200\nPredictionPadTop 200\nPredictionPadBottom 200\nShowErrorNumbers true\nAllowModeratedLevels true\nAllowModeratedPoppetItems true\nShowLevelBoos true\nTIMEOUT_WAIT_FOR_JOIN_RESPONSE_FROM_PREV_PARTY_HOST 50.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_HOST 30.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_MEMBER 45.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN_FRIEND 15.0\nTIMEOUT_WAIT_FOR_CONNECTION_FROM_HOST 30.0\nTIMEOUT_WAIT_FOR_ROOM_ID_TO_JOIN 60.0\nTIMEOUT_WAIT_FOR_GET_NUM_PLAYERS_ONLINE 60.0\nTIMEOUT_WAIT_FOR_SIGNALLING_CONNECTIONS 120.0\nTIMEOUT_WAIT_FOR_PARTY_DATA 60.0\nTIME_TO_WAIT_FOR_LEAVE_MESSAGE_TO_COME_BACK 20.0\nTIME_TO_WAIT_FOR_FOLLOWING_REQUESTS_TO_ARRIVE 30.0\nTIMEOUT_WAIT_FOR_FINISHED_MIGRATING_HOST 30.0\nTIMEOUT_WAIT_FOR_PARTY_LEADER_FINISH_JOINING 45.0\nTIMEOUT_WAIT_FOR_QUICKPLAY_LEVEL 60.0\nTIMEOUT_WAIT_FOR_PLAYERS_TO_JOIN 30.0\nTIMEOUT_WAIT_FOR_DIVE_IN_PLAYERS 120.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 30.0\nTIMEOUT_DIVE_IN_TOTAL 1000000.0\nTIMEOUT_WAIT_FOR_SOCKET_CONNECTION 120.0\nTIMEOUT_WAIT_FOR_REQUEST_RESOURCE_MESSAGE 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_GET_RESOURCE_LIST 120.0\nTIMEOUT_WAIT_FOR_CLIENT_TO_LOAD_RESOURCES 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_SAVE_GAME_STATE 30.0\nTIMEOUT_WAIT_FOR_ADD_PLAYERS_TO_TAKE 30.0\nTIMEOUT_WAIT_FOR_UPDATE_FROM_CLIENT 90.0\nTIMEOUT_WAIT_FOR_HOST_TO_GET_RESOURCE_LIST 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_SAVE_GAME_STATE 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_ADD_US 30.0\nTIMEOUT_WAIT_FOR_UPDATE 60.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN 50.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_PRESENCE 60.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_CONNECTION 120.0\nSECONDS_BETWEEN_PINS_AWARDED_UPLOADS 300.0\nEnableKeepAlive true\nAllowVoIPRecordingPlayback true\nCDNHostName localhost\nTelemetryServer localhost\nOverheatingThresholdDisallowMidgameJoin 0.95\nMaxCatchupFrames 3\nMaxLagBeforeShowLoading 23\nMinLagBeforeHideLoading 30\nLagImprovementInflectionPoint -1.0\nFlickerThreshold 2.0\nClosedDemo2014Version 1\nClosedDemo2014Expired false\nEnablePlayedFilter true\nEnableCommunityDecorations true\nGameStateUpdateRate 10.0\nGameStateUpdateRateWithConsumers 1.0\nDisableDLCPublishCheck false\nEnableDiveIn true\nEnableHackChecks false\n" + + $"TelemetryServer {hostname}\nCDNHostName {hostname}" ); + } [HttpGet("t_conf")] [Produces("text/json")] diff --git a/ProjectLighthouse/Controllers/DeveloperController.cs b/ProjectLighthouse/Controllers/DeveloperController.cs new file mode 100644 index 00000000..0ec94875 --- /dev/null +++ b/ProjectLighthouse/Controllers/DeveloperController.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; + +namespace LBPUnion.ProjectLighthouse.Controllers +{ + [ApiController] + [Route("LITTLEBIGPLANETPS3_XML/")] + public class DeveloperController : Controller + { + [HttpGet("/developer_videos")] + public IActionResult DeveloperVideos() => this.Ok(); + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/MatchController.cs b/ProjectLighthouse/Controllers/MatchController.cs index a4af9f5d..81fe97e0 100644 --- a/ProjectLighthouse/Controllers/MatchController.cs +++ b/ProjectLighthouse/Controllers/MatchController.cs @@ -19,6 +19,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers public class MatchController : ControllerBase { private readonly Database database; + public MatchController(Database database) { this.database = database; @@ -38,14 +39,13 @@ namespace LBPUnion.ProjectLighthouse.Controllers // Example POST /match: [UpdateMyPlayerData,["Player":"FireGamer9872"]] string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); - if (bodyString.Contains - ("FindBestRoom")) + if (bodyString.Contains("FindBestRoom")) return this.Ok ( "[{\"StatusCode\":200},{\"Players\":[{\"PlayerId\":\"literally1984\",\"matching_res\":0},{\"PlayerId\":\"jvyden\",\"matching_res\":1}],\"Slots\":[[5,0]],\"RoomState\":\"E_ROOM_IN_POD\",\"HostMood\":\"E_MOOD_EVERYONE\",\"LevelCompletionEstimate\":0,\"PassedNoJoinPoint\":0,\"MoveConnected\":false,\"Location\":[\"127.0.0.1\"],\"BuildVersion\":289,\"Language\":1,\"FirstSeenTimestamp\":1427331263756,\"LastSeenTimestamp\":1635112546000,\"GameId\":1,\"NatType\":2,\"Friends\":[],\"Blocked\":[],\"RecentlyLeft\":[],\"FailedJoin\":[]}]" ); - if (string.IsNullOrEmpty(bodyString) || bodyString[0] != '[') return this.BadRequest(); + if (bodyString[0] != '[') return this.BadRequest(); IMatchData? matchData; try diff --git a/ProjectLighthouse/Controllers/MessageController.cs b/ProjectLighthouse/Controllers/MessageController.cs index a96317eb..193443c5 100644 --- a/ProjectLighthouse/Controllers/MessageController.cs +++ b/ProjectLighthouse/Controllers/MessageController.cs @@ -14,6 +14,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers public class MessageController : ControllerBase { private readonly Database database; + public MessageController(Database database) { this.database = database; diff --git a/ProjectLighthouse/Controllers/StoreController.cs b/ProjectLighthouse/Controllers/StoreController.cs new file mode 100644 index 00000000..7c68be02 --- /dev/null +++ b/ProjectLighthouse/Controllers/StoreController.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; + +namespace LBPUnion.ProjectLighthouse.Controllers +{ + [ApiController] + [Route("LITTLEBIGPLANETPS3_XML/")] + [Produces("text/xml")] + public class StoreController : Controller + { + [HttpGet("promotions")] + public IActionResult Promotions() => this.Ok(); + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/UserController.cs b/ProjectLighthouse/Controllers/UserController.cs index 1938f6cd..265d9126 100644 --- a/ProjectLighthouse/Controllers/UserController.cs +++ b/ProjectLighthouse/Controllers/UserController.cs @@ -16,6 +16,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers public class UserController : ControllerBase { private readonly Database database; + public UserController(Database database) { this.database = database; @@ -34,6 +35,9 @@ namespace LBPUnion.ProjectLighthouse.Controllers [HttpGet("users")] public async Task GetUserAlt([FromQuery] string u) => await this.GetUser(u); + [HttpGet("user/{username}/playlists")] + public IActionResult GetUserPlaylists(string username) => this.Ok(); + [HttpPost("updateUser")] public async Task UpdateUser() { @@ -102,6 +106,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers break; } } + break; case XmlNodeType.EndElement: path.RemoveAt(path.Count - 1); @@ -110,8 +115,8 @@ namespace LBPUnion.ProjectLighthouse.Controllers } // the way location on a user card works is stupid and will not save with the way below as-is, so we do the following: - if (locationChanged) - { // only modify the database if we modify here + if (locationChanged) // only modify the database if we modify here + { Location l = await this.database.Locations.Where(l => l.Id == user.LocationId).FirstOrDefaultAsync(); // find the location in the database again // set the location in the database to the one we modified above diff --git a/ProjectLighthouse/Helpers/HashHelper.cs b/ProjectLighthouse/Helpers/HashHelper.cs index 6cf17969..ffda6e17 100644 --- a/ProjectLighthouse/Helpers/HashHelper.cs +++ b/ProjectLighthouse/Helpers/HashHelper.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Security.Cryptography; using System.Text; +using System.Threading.Tasks; namespace LBPUnion.ProjectLighthouse.Helpers { @@ -37,6 +39,30 @@ namespace LBPUnion.ProjectLighthouse.Helpers return BCryptHash(Sha256Hash(bytes)); } + public static async Task ComputeDigest(string path, string authCookie, Stream body, string digestKey) + { + MemoryStream memoryStream = new(); + + byte[] pathBytes = Encoding.UTF8.GetBytes(path); + byte[] cookieBytes = string.IsNullOrEmpty(authCookie) ? Array.Empty() : Encoding.UTF8.GetBytes(authCookie); + byte[] keyBytes = Encoding.UTF8.GetBytes(digestKey); + + await body.CopyToAsync(memoryStream); + + byte[] bodyBytes = memoryStream.ToArray(); + + using IncrementalHash sha1 = IncrementalHash.CreateHash(HashAlgorithmName.SHA1); + sha1.AppendData(bodyBytes); + if (cookieBytes.Length > 0) sha1.AppendData(cookieBytes); + sha1.AppendData(pathBytes); + sha1.AppendData(keyBytes); + + byte[] digestBytes = sha1.GetHashAndReset(); + string digestString = Convert.ToHexString(digestBytes).ToLower(); + + return digestString; + } + #region Hash Functions public static string Sha256Hash(string str) => Sha256Hash(Encoding.UTF8.GetBytes(str)); diff --git a/ProjectLighthouse/Startup.cs b/ProjectLighthouse/Startup.cs index bd3b2640..6b6775df 100644 --- a/ProjectLighthouse/Startup.cs +++ b/ProjectLighthouse/Startup.cs @@ -1,6 +1,9 @@ +using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using Kettu; +using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Serialization; using Microsoft.AspNetCore.Builder; @@ -9,6 +12,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Primitives; namespace LBPUnion.ProjectLighthouse { @@ -25,6 +29,7 @@ namespace LBPUnion.ProjectLighthouse public void ConfigureServices(IServiceCollection services) { services.AddControllers(); + services.AddMvc(options => options.OutputFormatters.Add(new XmlOutputFormatter())); services.AddDbContext(); @@ -33,6 +38,18 @@ namespace LBPUnion.ProjectLighthouse // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + bool computeDigests = true; + string serverDigestKey = Environment.GetEnvironmentVariable("SERVER_DIGEST_KEY"); + if (string.IsNullOrWhiteSpace(serverDigestKey)) + { + Logger.Log + ( + "The SERVER_DIGEST_KEY environment variable wasn't set, so digest headers won't be set or verified. This will prevent LBP 1 and LBP 3 from working. " + + "To increase security, it is recommended that you find and set this variable." + ); + computeDigests = false; + } + if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); // Logs every request and the response to it @@ -45,9 +62,61 @@ namespace LBPUnion.ProjectLighthouse Stopwatch requestStopwatch = new(); requestStopwatch.Start(); + // Log all headers. + foreach (KeyValuePair header in context.Request.Headers) Logger.Log($"{header.Key}: {header.Value}"); + context.Request.EnableBuffering(); // Allows us to reset the position of Request.Body for later logging + + // Client digest check. + string authCookie; + if (!context.Request.Cookies.TryGetValue("MM_AUTH", out authCookie)) authCookie = string.Empty; + string digestPath = context.Request.Path; + Stream body = context.Request.Body; + + if (computeDigests) + { + string clientRequestDigest = await HashHelper.ComputeDigest(digestPath, authCookie, body, serverDigestKey); + + // Check the digest we've just calculated against the X-Digest-A header if the game set the header. They should match. + if (context.Request.Headers.TryGetValue("X-Digest-A", out StringValues sentDigest)) + if (clientRequestDigest != sentDigest) + { + context.Response.StatusCode = 403; + context.Abort(); + return; + } + + context.Response.Headers.Add("X-Digest-B", clientRequestDigest); + context.Request.Body.Position = 0; + } + + // This does the same as above, but for the response stream. + using MemoryStream responseBuffer = new(); + Stream oldResponseStream = context.Response.Body; + context.Response.Body = responseBuffer; + await next(); // Handle the request so we can get the status code from it + // Compute the server digest hash. + if (computeDigests) + { + responseBuffer.Position = 0; + + // Compute the digest for the response. + string serverDigest = await HashHelper.ComputeDigest(context.Request.Path, authCookie, responseBuffer, serverDigestKey); + context.Response.Headers.Add("X-Digest-A", serverDigest); + } + + // Set the X-Original-Content-Length header to the length of the response buffer. + context.Response.Headers.Add("X-Original-Content-Length", responseBuffer.Length.ToString()); + + // Copy the buffered response to the actual respose stream. + responseBuffer.Position = 0; + + await responseBuffer.CopyToAsync(oldResponseStream); + + context.Response.Body = oldResponseStream; + requestStopwatch.Stop(); Logger.Log @@ -66,13 +135,7 @@ namespace LBPUnion.ProjectLighthouse app.UseRouting(); - app.UseEndpoints - ( - endpoints => - { - endpoints.MapControllers(); - } - ); + app.UseEndpoints(endpoints => endpoints.MapControllers()); } } } \ No newline at end of file