using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Xml; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using ProjectLighthouse.Types; namespace ProjectLighthouse.Controllers { [ApiController] [Route("LITTLEBIGPLANETPS3_XML/")] [Produces("text/xml")] public class UserController : ControllerBase { private readonly Database database; public UserController(Database database) { this.database = database; } [HttpGet("user/{username}")] public async Task GetUser(string username) { User user = await database.Users .Include(u => u.Location) .FirstOrDefaultAsync(u => u.Username == username); if(user == null) return this.NotFound(); return this.Ok(user.Serialize()); } // [HttpPost("user/{username}")] // public async Task CreateUser(string username) { // await new Database().CreateUser(username); // return await GetUser(username); // } [HttpPost("updateUser")] public async Task UpdateUser() { User user = await database.UserFromRequest(Request); if(user == null) return this.StatusCode(403, ""); XmlReaderSettings settings = new() { Async = true // this is apparently not default }; bool locationChanged = false; // this is an absolute mess, but necessary because LBP only sends what changed // // example for changing profile card location: // // // 1234 // 1234 // // // // example for changing biography: // // biography stuff // // // if you find a way to make it not stupid feel free to replace this using(XmlReader reader = XmlReader.Create(Request.Body, settings)) { List path = new(); // you can think of this as a file path in the XML, like -> -> while(await reader.ReadAsync()) { // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault switch(reader.NodeType) { case XmlNodeType.Element: path.Add(reader.Name); break; case XmlNodeType.Text: switch(path[1]) { case "biography": { user.Biography = await reader.GetValueAsync(); break; } case "location": { locationChanged = true; // if we're here then we're probably about to change the location. // ReSharper disable once ConvertIfStatementToSwitchStatement if(path[2] == "x") { user.Location.X = Convert.ToInt32(await reader.GetValueAsync()); // GetValue only returns a string, i guess we just hope its a number lol } else if(path[2] == "y") { user.Location.Y = Convert.ToInt32(await reader.GetValueAsync()); } break; } case "icon": { user.IconHash = await reader.GetValueAsync(); break; } case "planets": { user.PlanetHash = await reader.GetValueAsync(); break; } } break; case XmlNodeType.EndElement: path.RemoveAt(path.Count - 1); break; } } } // the way location on a user card works is stupid and will not save with the way below as-is, so we do the following: if(locationChanged) { // only modify the database if we modify here Location l = await 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 l.X = user.Location.X; l.Y = user.Location.Y; // now both are in sync, and will update in the database. } if(database.ChangeTracker.HasChanges()) await database.SaveChangesAsync(); // save the user to the database if we changed anything return this.Ok(); } [HttpPost("match")] [Produces("text/json")] public IActionResult Match() { return this.Ok("[{\"StatusCode\":200}]"); } } }