diff --git a/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OHS/User.cs b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OHS/User.cs index e11ce4a3f..525715327 100644 --- a/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OHS/User.cs +++ b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OHS/User.cs @@ -541,6 +541,11 @@ public static string Get(byte[] PostData, string ContentType, string directorypa if (directorypath.Contains("casino")) output = "nil"; break; + case "CONTROLS": + if (directorypath.Contains("EmergencyState")) + output = "{ [\"VIBRATION\"] = true, [\"AIM_INVERT\"] = false, " + + "[\"AIM_SENSITIVITY\"] = 0.5, [\"TURN_SENSITIVITY\"] = 0.5 }"; + break; case "GameState": if (directorypath.Contains("shooter_game")) output = "{ [\"currentLevel\"] = 1, [\"currentMaxLevel\"] = 50, [\"items\"] = {\t{ type = \"guns\" \t\t , name=\"repeater\"\t\t\t, level=1 , inUse = false }\r\n" + diff --git a/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/Announce.cs b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/Announce.cs new file mode 100644 index 000000000..9ef8180d1 --- /dev/null +++ b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/Announce.cs @@ -0,0 +1,55 @@ +using System; +using System.IO; +using System.Text; +using CustomLogger; + +namespace WebAPIService.GameServices.PSHOME.OSHABERI +{ + public class Announce + { + private const string HardcodedDefault = + "{" + + "\"ver_\":1," + + "\"updateTime_\":300," + + "\"infoLifeTime_\":10," + + "\"announces_\":[]" + + "}"; + + public static string loadEntrygate(string workPath, string fulluripath) + { + return Serve(workPath, "Announce_entrygate.json", "entrygate"); + } + + public static string loadFarm(string workPath, string fulluripath) + { + return Serve(workPath, "Announce_farm.json", "farm"); + } + + public static string loadGarden(string workPath, string fulluripath) + { + return Serve(workPath, "Announce_garden.json", "garden"); + } + + private static string Serve(string workPath, string fileName, string sceneName) + { + string overridePath = Path.Combine(workPath, "oshaberi", "config", fileName); + + if (File.Exists(overridePath)) + { + try + { + string json = File.ReadAllText(overridePath, Encoding.UTF8); + LoggerAccessor.LogInfo($"[OSHABERI] - Announce ({sceneName}): serving override ({json.Length} bytes)"); + return json; + } + catch (Exception ex) + { + LoggerAccessor.LogWarn($"[OSHABERI] - Announce ({sceneName}): override read failed, using default: {ex}"); + } + } + + LoggerAccessor.LogInfo($"[OSHABERI] - Announce ({sceneName}): serving empty feed"); + return HardcodedDefault; + } + } +} \ No newline at end of file diff --git a/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/Campaign.cs b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/Campaign.cs new file mode 100644 index 000000000..ff91ce0a6 --- /dev/null +++ b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/Campaign.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; +using System.Text; +using CustomLogger; + +namespace WebAPIService.GameServices.PSHOME.OSHABERI +{ + public class Campaign + { + private const string HardcodedDefault = + "{" + + "\"ver_\":1," + + "\"updateTime_\":300," + + "\"conditionCheckTime_\":60," + + "\"infoLifeTime_\":10," + + "\"campaigns_\":[]," + + "\"events_\":[]" + + "}"; + + public static string loadCampaign(string workPath, string fulluripath) + { + string overridePath = Path.Combine(workPath, "oshaberi", "config", "Campaign.json"); + + if (File.Exists(overridePath)) + { + try + { + string json = File.ReadAllText(overridePath, Encoding.UTF8); + LoggerAccessor.LogInfo($"[OSHABERI] - Campaign: serving override ({json.Length} bytes)"); + return json; + } + catch (Exception ex) + { + LoggerAccessor.LogWarn($"[OSHABERI] - Campaign: override read failed, using defaults: {ex}"); + } + } + + LoggerAccessor.LogInfo("[OSHABERI] - Campaign: serving hardcoded default (no active campaigns)"); + return HardcodedDefault; + } + } +} \ No newline at end of file diff --git a/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/EventRecords.cs b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/EventRecords.cs new file mode 100644 index 000000000..ffcd45733 --- /dev/null +++ b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/EventRecords.cs @@ -0,0 +1,207 @@ +using System; +using System.IO; +using System.Text; +using System.Text.Json; +using MultiServerLibrary.HTTP; +using CustomLogger; +using HttpMultipartParser; + + + + +#if !NETFRAMEWORK +using System.Web; +#endif + +namespace WebAPIService.GameServices.PSHOME.OSHABERI +{ + public class EventRecords + { + private const string DefaultEventJson = + "{\"ver_\":1,\"records_\":{}}"; + + public static string loadUserEvent(byte[] PostData, string ContentType, string workPath, string fulluripath, string method) + { + string userId = string.Empty; + + try + { + if (method == "GET" || ContentType == null) + { +#if NETFRAMEWORK + userId = HTTPProcessor.GetQueryParameters(fulluripath)["u"]; +#else + userId = HttpUtility.ParseQueryString(new Uri("http://x" + fulluripath).Query).Get("u"); +#endif + } + else + { + string boundary = HTTPProcessor.ExtractBoundary(ContentType); + using (MemoryStream ms = new MemoryStream(PostData)) + { + userId = MultipartFormDataParser.Parse(ms, boundary).GetParameterValue("u") ?? string.Empty; + ms.Flush(); + } + } + } + catch (Exception ex) + { + LoggerAccessor.LogError($"[OSHABERI] - loadUserEvent: parse error: {ex}"); + return DefaultEventJson; + } + + if (string.IsNullOrEmpty(userId)) + { + LoggerAccessor.LogWarn("[OSHABERI] - loadUserEvent: no userId, returning default"); + return DefaultEventJson; + } + + string safeId = SanitiseUserId(userId); + string evDir = Path.Combine(workPath, "oshaberi", "events"); + Directory.CreateDirectory(evDir); + string filePath = Path.Combine(evDir, safeId + ".json"); + + if (File.Exists(filePath)) + { + string json = File.ReadAllText(filePath, Encoding.UTF8); + LoggerAccessor.LogInfo($"[OSHABERI] - loadUserEvent: loaded for '{userId}' ({json.Length} bytes)"); + return json; + } + + File.WriteAllText(filePath, DefaultEventJson, Encoding.UTF8); + LoggerAccessor.LogInfo($"[OSHABERI] - loadUserEvent: created default for new user '{userId}'"); + return DefaultEventJson; + } + + public static string saveUserEvent(byte[] PostData, string ContentType, string workPath) + { + if (PostData == null || PostData.Length == 0) + { + LoggerAccessor.LogError("[OSHABERI] - saveUserEvent: empty POST body"); + return ErrorJson(-1); + } + + string userId = string.Empty; + string recordsJson = string.Empty; + + try + { + string boundary = HTTPProcessor.ExtractBoundary(ContentType); + using (MemoryStream ms = new MemoryStream(PostData)) + { + var form = MultipartFormDataParser.Parse(ms, boundary); + userId = form.GetParameterValue("u") ?? string.Empty; + recordsJson = form.GetParameterValue("records") ?? string.Empty; + ms.Flush(); + } + } + catch (Exception ex) + { + LoggerAccessor.LogError($"[OSHABERI] - saveUserEvent: parse error: {ex}"); + return ErrorJson(-1); + } + + if (string.IsNullOrEmpty(userId)) + { + LoggerAccessor.LogError("[OSHABERI] - saveUserEvent: missing userId"); + return ErrorJson(-2); + } + + if (string.IsNullOrEmpty(recordsJson) || recordsJson == "{}") + { + LoggerAccessor.LogInfo($"[OSHABERI] - saveUserEvent: empty records for '{userId}', no-op"); + return SuccessJson(); + } + + string safeId = SanitiseUserId(userId); + string evDir = Path.Combine(workPath, "oshaberi", "events"); + Directory.CreateDirectory(evDir); + string filePath = Path.Combine(evDir, safeId + ".json"); + + try + { + string existing = File.Exists(filePath) + ? File.ReadAllText(filePath, Encoding.UTF8) + : DefaultEventJson; + string merged = MergeEventRecords(existing, recordsJson); + File.WriteAllText(filePath, merged, Encoding.UTF8); + LoggerAccessor.LogInfo($"[OSHABERI] - saveUserEvent: saved for '{userId}' ({merged.Length} bytes)"); + } + catch (Exception ex) + { + LoggerAccessor.LogError($"[OSHABERI] - saveUserEvent: write error: {ex}"); + return ErrorJson(-3); + } + + return SuccessJson(); + } + + private static string MergeEventRecords(string existing, string incoming) + { + try + { + using var incomingDoc = JsonDocument.Parse(incoming); + if (incomingDoc.RootElement.TryGetProperty("ver_", out _)) + return incoming; + + using var existingDoc = JsonDocument.Parse(existing); + + using var stream = new MemoryStream(); + using (var writer = new Utf8JsonWriter(stream)) + { + writer.WriteStartObject(); + + foreach (var prop in existingDoc.RootElement.EnumerateObject()) + { + if (prop.Name == "records_") + continue; + prop.WriteTo(writer); + } + + writer.WritePropertyName("records_"); + writer.WriteStartObject(); + + JsonElement existingRecords; + bool hasExisting = existingDoc.RootElement.TryGetProperty("records_", out existingRecords); + + if (hasExisting) + { + foreach (var rec in existingRecords.EnumerateObject()) + { + if (incomingDoc.RootElement.TryGetProperty(rec.Name, out _)) + continue; + rec.WriteTo(writer); + } + } + + foreach (var rec in incomingDoc.RootElement.EnumerateObject()) + rec.WriteTo(writer); + + writer.WriteEndObject(); + writer.WriteEndObject(); + } + + return Encoding.UTF8.GetString(stream.ToArray()); + } + catch (Exception ex) + { + LoggerAccessor.LogWarn($"[OSHABERI] - MergeEventRecords failed, using incoming: {ex.Message}"); + return incoming; + } + } + + private static string SanitiseUserId(string userId) + { + var sb = new StringBuilder(userId.Length); + foreach (char c in userId) + sb.Append(char.IsLetterOrDigit(c) || c == '_' || c == '-' || c == '.' ? c : '_'); + return sb.Length > 0 ? sb.ToString() : "unknown"; + } + + private static string SuccessJson() => + "{\"result\":1,\"error_no\":0}"; + + private static string ErrorJson(int code) => + $"{{\"result\":0,\"error_no\":{code}}}"; + } +} \ No newline at end of file diff --git a/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/GlobalSetting.cs b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/GlobalSetting.cs new file mode 100644 index 000000000..27c00a81c --- /dev/null +++ b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/GlobalSetting.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; +using System.Text; +using CustomLogger; + +namespace WebAPIService.GameServices.PSHOME.OSHABERI +{ + public class GlobalSetting + { + private const string HardcodedDefault = + "{" + + "\"releaseVersion_\":0," + + "\"gcStep_\":2," + + "\"asignFarmReqTime_\":1," + + "\"asignFarmAnsTime_\":1," + + "\"asignFarmKeepTime_\":10," + + "\"uniqueFarmInstance_\":true" + + "}"; + + public static string loadOption(string workPath) + { + string overridePath = Path.Combine(workPath, "oshaberi", "config", "GlobalSetting.json"); + + if (File.Exists(overridePath)) + { + try + { + string json = File.ReadAllText(overridePath, Encoding.UTF8); + LoggerAccessor.LogInfo($"[OSHABERI] - GlobalSetting: serving override ({json.Length} bytes)"); + return json; + } + catch (Exception ex) + { + LoggerAccessor.LogWarn($"[OSHABERI] - GlobalSetting: override read failed, using defaults: {ex}"); + } + } + + LoggerAccessor.LogInfo("[OSHABERI] - GlobalSetting: serving hardcoded defaults"); + return HardcodedDefault; + } + } +} \ No newline at end of file diff --git a/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/OSHABERIClass.cs b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/OSHABERIClass.cs new file mode 100644 index 000000000..cf2dc8098 --- /dev/null +++ b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/OSHABERIClass.cs @@ -0,0 +1,83 @@ +using CustomLogger; +using System; + +namespace WebAPIService.GameServices.PSHOME.OSHABERI +{ + public class OSHABERIClass + { + private readonly string workpath; + private readonly string absolutepath; + private readonly string fulluripath; + private readonly string method; + + public OSHABERIClass(string method, string absolutepath, string workpath, string fulluripath) + { + this.method = method; + this.absolutepath = absolutepath; + this.workpath = workpath; + this.fulluripath = fulluripath; + } + + public string ProcessRequest(byte[] PostData, string ContentType) + { + if (string.IsNullOrEmpty(absolutepath)) + return null; + + PostData ??= Array.Empty(); + + switch (method) + { + case "GET": + switch (absolutepath) + { + case "/game/app/announce/load_entrygate.php": + return Announce.loadEntrygate(workpath, fulluripath); + + case "/game/app/announce/load_farm.php": + return Announce.loadFarm(workpath, fulluripath); + + case "/game/app/announce/load_garden.php": + return Announce.loadGarden(workpath, fulluripath); + + case "/game/app/userdata/load_userdata.php": + return UserData.loadUserData(PostData, ContentType, workpath, fulluripath, method); + + case "/game/app/option/load_option.php": + return GlobalSetting.loadOption(workpath); + + case "/game/app/campaign/load_campaign.php": + return Campaign.loadCampaign(workpath, fulluripath); + + case "/game/app/event/load_user_event.php": + return EventRecords.loadUserEvent(PostData, ContentType, workpath, fulluripath, method); + + default: + LoggerAccessor.LogWarn($"[OSHABERI] - Unknown GET request: {absolutepath}"); + break; + } + break; + + case "POST": + switch (absolutepath) + { + case "/game/app/userdata/save_userdata.php": + return UserData.saveUserData(PostData, ContentType, workpath); + + case "/game/app/event/save_user_event.php": + return EventRecords.saveUserEvent(PostData, ContentType, workpath); + + default: + LoggerAccessor.LogWarn($"[OSHABERI] - Unknown POST request: {absolutepath}"); + break; + } + break; + + default: + LoggerAccessor.LogWarn($"[OSHABERI] - Unsupported HTTP method: {method}"); + break; + } + + return null; + } + } +} \ No newline at end of file diff --git a/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/UserData.cs b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/UserData.cs new file mode 100644 index 000000000..9b6761ec8 --- /dev/null +++ b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/OSHABERI/UserData.cs @@ -0,0 +1,256 @@ +using System; +using System.IO; +using System.Text; +using System.Text.Json; +using MultiServerLibrary.HTTP; +using CustomLogger; +using HttpMultipartParser; + + + +#if !NETFRAMEWORK +using System.Web; +#endif + +namespace WebAPIService.GameServices.PSHOME.OSHABERI +{ + public class UserData + { + private const string DefaultUserDataJson = + "{" + + "\"HeaderData\":{\"version_\":8}," + + "\"PersonalData\":{" + + "\"deliverFarmPoint_\":0," + + "\"farmPoint_\":0," + + "\"wtPotValue_\":0," + + "\"wtPotLevel_\":1," + + "\"helpPoint_\":0," + + "\"lastLoginYMD_\":0," + + "\"lastLoginHMS_\":0," + + "\"lastBonusYMD_\":0," + + "\"lastBonusHMS_\":0," + + "\"lastCoopFarmYMD_\":0," + + "\"lastCoopFarmHMS_\":0," + + "\"commonFlag_\":\"00000000\"," + + "\"tutorialFlag_\":\"00000000\"" + + "}," + + "\"OperateCropData\":{" + + "\"managedID_\":0," + + "\"exp_\":0," + + "\"level_\":1," + + "\"rankDivide_\":0," + + "\"mutatiln_\":0" + + "}," + + "\"CropStockData\":{\"crops_\":{}}," + + "\"CropPlaceData\":{\"sceneSets_\":{}}," + + "\"CropBagData\":{\"expantion_\":0,\"sortKind_\":1}," + + "\"ItemData\":{\"items_\":{}}," + + "\"CollectionData\":{\"resultBits_\":[\"0000000000000000\",\"0000000000000000\"]}" + + "}"; + + public static string loadUserData(byte[] PostData, string ContentType, string workPath, string fulluripath, string method) + { + string userId = string.Empty; + + try + { + if (method == "GET" || ContentType == null) + { +#if NETFRAMEWORK + userId = HTTPProcessor.GetQueryParameters(fulluripath)["u"]; +#else + userId = HttpUtility.ParseQueryString(new Uri("http://x" + fulluripath).Query).Get("u"); +#endif + } + else + { + string boundary = HTTPProcessor.ExtractBoundary(ContentType); + using (MemoryStream ms = new MemoryStream(PostData)) + { + userId = MultipartFormDataParser.Parse(ms, boundary).GetParameterValue("u") ?? string.Empty; + ms.Flush(); + } + } + } + catch (Exception ex) + { + LoggerAccessor.LogError($"[OSHABERI] - loadUserData: failed to parse userId: {ex}"); + return ErrorJson(-1); + } + + if (string.IsNullOrEmpty(userId)) + { + LoggerAccessor.LogError("[OSHABERI] - loadUserData: userId is empty"); + return ErrorJson(-2); + } + + string safeId = SanitiseUserId(userId); + string userDir = Path.Combine(workPath, "oshaberi", "userdata"); + Directory.CreateDirectory(userDir); + string filePath = Path.Combine(userDir, safeId + ".json"); + + string json; + if (File.Exists(filePath)) + { + json = File.ReadAllText(filePath, Encoding.UTF8); + LoggerAccessor.LogInfo($"[OSHABERI] - loadUserData: loaded for '{userId}' ({json.Length} bytes)"); + } + else + { + json = DefaultUserDataJson; + File.WriteAllText(filePath, json, Encoding.UTF8); + LoggerAccessor.LogInfo($"[OSHABERI] - loadUserData: created default data for new user '{userId}'"); + } + + return json; + } + public static string saveUserData(byte[] PostData, string ContentType, string workPath) + { + if (PostData == null || PostData.Length == 0) + { + LoggerAccessor.LogError("[OSHABERI] - saveUserData: empty POST body"); + return ErrorJson(-1); + } + + string userId = string.Empty; + string dataJson = string.Empty; + string histJson = string.Empty; + string evJson = string.Empty; + + try + { + string boundary = HTTPProcessor.ExtractBoundary(ContentType); + using (MemoryStream ms = new MemoryStream(PostData)) + { + var form = MultipartFormDataParser.Parse(ms, boundary); + userId = form.GetParameterValue("u") ?? string.Empty; + dataJson = form.GetParameterValue("data") ?? string.Empty; + histJson = form.GetParameterValue("history") ?? string.Empty; + evJson = form.GetParameterValue("event") ?? string.Empty; + ms.Flush(); + } + } + catch (Exception ex) + { + LoggerAccessor.LogError($"[OSHABERI] - saveUserData: failed to parse multipart: {ex}"); + return ErrorJson(-1); + } + + if (string.IsNullOrEmpty(userId)) + { + LoggerAccessor.LogError("[OSHABERI] - saveUserData: userId field missing"); + return ErrorJson(-2); + } + + if (string.IsNullOrEmpty(dataJson) || dataJson == "{}" || dataJson == "[]") + { + LoggerAccessor.LogInfo($"[OSHABERI] - saveUserData: empty delta for '{userId}', no-op"); + return SuccessJson(); + } + + string safeId = SanitiseUserId(userId); + + string userDir = Path.Combine(workPath, "oshaberi", "userdata"); + Directory.CreateDirectory(userDir); + string filePath = Path.Combine(userDir, safeId + ".json"); + + try + { + string existing = File.Exists(filePath) + ? File.ReadAllText(filePath, Encoding.UTF8) + : DefaultUserDataJson; + + string merged = MergeJsonObjects(existing, dataJson); + File.WriteAllText(filePath, merged, Encoding.UTF8); + LoggerAccessor.LogInfo($"[OSHABERI] - saveUserData: saved for '{userId}' ({merged.Length} bytes)"); + } + catch (Exception ex) + { + LoggerAccessor.LogError($"[OSHABERI] - saveUserData: write error: {ex}"); + return ErrorJson(-3); + } + + if (!string.IsNullOrEmpty(histJson) && histJson != "{}" && histJson != "[]") + { + try + { + string histDir = Path.Combine(workPath, "oshaberi", "history"); + Directory.CreateDirectory(histDir); + string histFile = Path.Combine(histDir, safeId + ".jsonl"); + File.AppendAllText(histFile, $"{{\"ts\":\"{DateTime.UtcNow:o}\",\"data\":{histJson}}}\n", Encoding.UTF8); + } + catch (Exception ex) + { + LoggerAccessor.LogWarn($"[OSHABERI] - saveUserData: history write failed for '{userId}': {ex}"); + } + } + + if (!string.IsNullOrEmpty(evJson) && evJson != "{}" && evJson != "[]") + { + try + { + string evDir = Path.Combine(workPath, "oshaberi", "eventdiff"); + Directory.CreateDirectory(evDir); + string evFile = Path.Combine(evDir, safeId + ".json"); + string existingEv = File.Exists(evFile) + ? File.ReadAllText(evFile, Encoding.UTF8) + : "{}"; + File.WriteAllText(evFile, MergeJsonObjects(existingEv, evJson), Encoding.UTF8); + } + catch (Exception ex) + { + LoggerAccessor.LogWarn($"[OSHABERI] - saveUserData: event diff write failed for '{userId}': {ex}"); + } + } + + return SuccessJson(); + } + private static string MergeJsonObjects(string baseJson, string patchJson) + { + try + { + using var baseDoc = JsonDocument.Parse(baseJson); + using var patchDoc = JsonDocument.Parse(patchJson); + + using var stream = new MemoryStream(); + using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { SkipValidation = false })) + { + writer.WriteStartObject(); + + foreach (var prop in baseDoc.RootElement.EnumerateObject()) + { + if (patchDoc.RootElement.TryGetProperty(prop.Name, out _)) + continue; + prop.WriteTo(writer); + } + + foreach (var prop in patchDoc.RootElement.EnumerateObject()) + prop.WriteTo(writer); + + writer.WriteEndObject(); + } + + return Encoding.UTF8.GetString(stream.ToArray()); + } + catch (Exception ex) + { + LoggerAccessor.LogWarn($"[OSHABERI] - MergeJsonObjects parse failed, using patch directly: {ex.Message}"); + return patchJson; + } + } + + private static string SanitiseUserId(string userId) + { + var sb = new StringBuilder(userId.Length); + foreach (char c in userId) + sb.Append(char.IsLetterOrDigit(c) || c == '_' || c == '-' || c == '.' ? c : '_'); + return sb.Length > 0 ? sb.ToString() : "unknown"; + } + + private static string SuccessJson() => + "{\"result\":1,\"error_no\":0}"; + + private static string ErrorJson(int code) => + $"{{\"result\":0,\"error_no\":{code}}}"; + } +} \ No newline at end of file diff --git a/AuxiliaryServices/WebAPIService/GameServices/PSHOME/VEEMEE/VEEMEEClass.cs b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/VEEMEE/VEEMEEClass.cs index 4f02f698a..5cff399b1 100644 --- a/AuxiliaryServices/WebAPIService/GameServices/PSHOME/VEEMEE/VEEMEEClass.cs +++ b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/VEEMEE/VEEMEEClass.cs @@ -1,5 +1,6 @@ using MultiServerLibrary.HTTP; using System.Text; +using System.IO; using WebAPIService.GameServices.PSHOME.VEEMEE.accorn; using WebAPIService.GameServices.PSHOME.VEEMEE.audi_sled; using WebAPIService.GameServices.PSHOME.VEEMEE.audi_vrun; @@ -93,11 +94,11 @@ public VEEMEEClass(string method, string absolutepath) resultContentType = "text/xml"; break; case "/MetaScores/setScore.php": - result = MetaScores.SetUserDataPOST(postData, contentType, apiPath); + result = MetaScores.SetUserDataPOST(postData, HTTPProcessor.ExtractBoundary(contentType), apiPath); resultContentType = "text/xml"; break; case "/MetaScores/getScore.php": - result = MetaScores.GetUserDataPOST(postData, contentType, apiPath); + result = MetaScores.GetUserDataPOST(postData, HTTPProcessor.ExtractBoundary(contentType), apiPath); resultContentType = "text/xml"; break; case "/MetaScores/getTrophies.php": @@ -109,11 +110,19 @@ public VEEMEEClass(string method, string absolutepath) resultContentType = "text/xml"; break; case "/MetaScores/getHighScores.php": + result = MetaScores.GetHighScoresPOST(postData, HTTPProcessor.ExtractBoundary(contentType), apiPath, filter: 0, friends: false); + resultContentType = "text/xml"; + break; case "/MetaScores/getHighScoresToday.php": + result = MetaScores.GetHighScoresPOST(postData, HTTPProcessor.ExtractBoundary(contentType), apiPath, filter: 1, friends: false); + resultContentType = "text/xml"; + break; case "/MetaScores/getHighScoresYesterday.php": + result = MetaScores.GetHighScoresPOST(postData, HTTPProcessor.ExtractBoundary(contentType), apiPath, filter: 2, friends: false); + resultContentType = "text/xml"; + break; case "/MetaScores/getHighScoresFriends.php": - // TODO: Implements MetaScores dynamic leaderboards! - result = ""; + result = MetaScores.GetHighScoresPOST(postData, HTTPProcessor.ExtractBoundary(contentType), apiPath, filter: 0, friends: true); resultContentType = "text/xml"; break; case "/player_profiles/setPlayerProfile.php": @@ -305,6 +314,14 @@ public VEEMEEClass(string method, string absolutepath) case "/stats/getconfig.php": result = Stats.GetConfig(true, postData, contentType, apiPath); break; + case "/screens.php": + string screensPath = $"{apiPath}/VEEMEE/screens/screens.xml"; // Just a Big Screenlinks File + if (File.Exists(screensPath)) + result = File.ReadAllText(screensPath); + else + result = ""; + resultContentType = "text/xml"; + break; default: break; } diff --git a/AuxiliaryServices/WebAPIService/GameServices/PSHOME/VEEMEE/meta/MetaScores.cs b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/VEEMEE/meta/MetaScores.cs index 0c5cf3919..45382103f 100644 --- a/AuxiliaryServices/WebAPIService/GameServices/PSHOME/VEEMEE/meta/MetaScores.cs +++ b/AuxiliaryServices/WebAPIService/GameServices/PSHOME/VEEMEE/meta/MetaScores.cs @@ -1,125 +1,238 @@ +using HttpMultipartParser; +using System; +using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; -using System.Xml; -using MultiServerLibrary.HTTP; +using System.Text; namespace WebAPIService.GameServices.PSHOME.VEEMEE.meta { public class MetaScores { - public static string SetUserDataPOST(byte[] PostData, string ContentType, string apiPath) - { - string key = string.Empty; - string psnid = string.Empty; - string game_id = string.Empty; - string sort_1 = string.Empty; - string sort_2 = string.Empty; - string score_1 = string.Empty; - string score_2 = string.Empty; - - if (ContentType == "application/x-www-form-urlencoded" && PostData != null) - { - var data = HTTPProcessor.ExtractAndSortUrlEncodedPOSTData(PostData); - key = data["key"].First(); - if (key != "JPDFC10A9MXS8HHOMOUKYAR3") - { - CustomLogger.LoggerAccessor.LogError("[VEEMEE] - meta_scores - Client tried to push invalid key! Invalidating request."); - return null; - } - psnid = data["psnid"].First(); - game_id = data["game_id"].First(); - sort_1 = data["sort_1"].First(); - sort_2 = data["sort_2"].First(); - score_1 = data["score_1"].First(); - score_2 = data["score_2"].First(); + private const string ValidKey = "JPDFC10A9MXS8HHOMOUKYAR3"; - string directoryPath = $"{apiPath}/VEEMEE/meta_scores/{game_id}/{sort_1}/User_Data"; - string directoryPath_2 = $"{apiPath}/VEEMEE/meta_scores/{game_id}/{sort_2}/User_Data"; - - string filePath = $"{directoryPath}/{psnid}.xml"; - string filePath_2 = $"{directoryPath}/{psnid}_2.xml"; - - Directory.CreateDirectory(directoryPath); - Directory.CreateDirectory(directoryPath_2); + public static string SetUserDataPOST(byte[] PostData, string boundary, string apiPath) + { + if (string.IsNullOrEmpty(boundary) || PostData == null) + return null; - if (File.Exists(filePath) && File.Exists(filePath_2)) + try + { + using (MemoryStream ms = new MemoryStream(PostData)) { - // Load the XML string into an XmlDocument - XmlDocument xmlDoc = new XmlDocument(); - xmlDoc.LoadXml($"{File.ReadAllText(filePath)}"); + var data = MultipartFormDataParser.Parse(ms, boundary); + + string key = data.GetParameterValue("key"); + if (key != ValidKey) + { + CustomLogger.LoggerAccessor.LogError("[VEEMEE] - meta_scores - SetUserData: invalid key."); + return null; + } + + string psnid = data.GetParameterValue("psnid"); + string game_id = data.GetParameterValue("game_id"); + string sort_1 = data.GetParameterValue("sort_1"); + string sort_2 = data.GetParameterValue("sort_2"); + string score_1 = data.GetParameterValue("score_1"); + string score_2 = data.GetParameterValue("score_2"); + + string directoryPath = $"{apiPath}/VEEMEE/meta_scores/{game_id}/{sort_1}/User_Data"; + string directoryPath_2 = $"{apiPath}/VEEMEE/meta_scores/{game_id}/{sort_2}/User_Data"; + string filePath = $"{directoryPath}/{psnid}.xml"; + string filePath_2 = $"{directoryPath_2}/{psnid}_2.xml"; + + Directory.CreateDirectory(directoryPath); + Directory.CreateDirectory(directoryPath_2); - // Find the element - XmlElement scoreElement = xmlDoc.SelectSingleNode("/xml/score") as XmlElement; - - if (scoreElement != null) - // Replace the value of with a new value - scoreElement.InnerText = score_1; - - File.WriteAllText(filePath, xmlDoc.OuterXml.Replace("", string.Empty).Replace("", string.Empty)); - - xmlDoc = new XmlDocument(); - xmlDoc.LoadXml($"{File.ReadAllText(filePath_2)}"); - - // Find the element - scoreElement = xmlDoc.SelectSingleNode("/xml/score") as XmlElement; - - if (scoreElement != null) - // Replace the value of with a new value - scoreElement.InnerText = score_2; - - File.WriteAllText(filePath, xmlDoc.OuterXml.Replace("", string.Empty).Replace("", string.Empty)); - - return $"{psnid}{score_1}{score_2}"; - } - else - { File.WriteAllText(filePath, $"{score_1}"); File.WriteAllText(filePath_2, $"{score_2}"); return $"{psnid}{score_1}{score_2}"; } } + catch (Exception ex) + { + CustomLogger.LoggerAccessor.LogError($"[VEEMEE] - meta_scores - SetUserDataPOST exception: {ex}"); + } return null; } - public static string GetUserDataPOST(byte[] PostData, string ContentType, string apiPath) + public static string GetUserDataPOST(byte[] PostData, string boundary, string apiPath) { - string key = string.Empty; - string psnid = string.Empty; - string game_id = string.Empty; - string sort_1 = string.Empty; - string sort_2 = string.Empty; + if (string.IsNullOrEmpty(boundary) || PostData == null) + return null; - if (ContentType == "application/x-www-form-urlencoded" && PostData != null) + try { - var data = HTTPProcessor.ExtractAndSortUrlEncodedPOSTData(PostData); - key = data["key"].First(); - if (key != "JPDFC10A9MXS8HHOMOUKYAR3") + using (MemoryStream ms = new MemoryStream(PostData)) { - CustomLogger.LoggerAccessor.LogError("[VEEMEE] - meta_scores - Client tried to push invalid key! Invalidating request."); - return null; + var data = MultipartFormDataParser.Parse(ms, boundary); + + string key = data.GetParameterValue("key"); + if (key != ValidKey) + { + CustomLogger.LoggerAccessor.LogError("[VEEMEE] - meta_scores - GetUserData: invalid key."); + return null; + } + + string psnid = data.GetParameterValue("psnid"); + string game_id = data.GetParameterValue("game_id"); + string sort_1 = data.GetParameterValue("sort_1"); + string sort_2 = data.GetParameterValue("sort_2"); + + string filePath = $"{apiPath}/VEEMEE/meta_scores/{game_id}/{sort_1}/User_Data/{psnid}.xml"; + string filePath_2 = $"{apiPath}/VEEMEE/meta_scores/{game_id}/{sort_2}/User_Data/{psnid}_2.xml"; + + if (File.Exists(filePath) && File.Exists(filePath_2)) + { + string score_1 = File.ReadAllText(filePath).Replace("", string.Empty).Replace("", string.Empty).Trim(); + string score_2 = File.ReadAllText(filePath_2).Replace("", string.Empty).Replace("", string.Empty).Trim(); + return $"{psnid}{score_1}{score_2}"; + } + + return $"{psnid}00"; } - psnid = data["psnid"].First(); - game_id = data["game_id"].First(); - sort_1 = data["sort_1"].First(); - sort_2 = data["sort_2"].First(); - - string directoryPath = $"{apiPath}/VEEMEE/meta_scores/{game_id}/{sort_1}/User_Data"; - string directoryPath_2 = $"{apiPath}/VEEMEE/meta_scores/{game_id}/{sort_2}/User_Data"; + } + catch (Exception ex) + { + CustomLogger.LoggerAccessor.LogError($"[VEEMEE] - meta_scores - GetUserDataPOST exception: {ex}"); + } - string filePath = $"{directoryPath}/{psnid}.xml"; - string filePath_2 = $"{directoryPath}/{psnid}_2.xml"; + return null; + } + public static string GetHighScoresPOST(byte[] PostData, string boundary, string apiPath, int filter = 0, bool friends = false) + { + if (string.IsNullOrEmpty(boundary) || PostData == null) + return ""; - if (File.Exists(filePath) && File.Exists(filePath_2)) + try + { + using (MemoryStream ms = new MemoryStream(PostData)) { - string score_1 = File.ReadAllText(filePath).Replace("", string.Empty).Replace("", string.Empty); - string score_2 = File.ReadAllText(filePath_2).Replace("", string.Empty).Replace("", string.Empty); - return $"{psnid}{score_1}{score_2}"; + var data = MultipartFormDataParser.Parse(ms, boundary); + + string key = data.GetParameterValue("key"); + if (key != ValidKey) + { + CustomLogger.LoggerAccessor.LogError("[VEEMEE] - meta_scores - GetHighScores: invalid key."); + return ""; + } + + string game_id = data.GetParameterValue("game_id"); + string sort_1 = data.GetParameterValue("sort_1"); + + if (string.IsNullOrEmpty(game_id)) + { + CustomLogger.LoggerAccessor.LogError("[VEEMEE] - meta_scores - GetHighScores: missing game_id."); + return ""; + } + + string userDataPath = $"{apiPath}/VEEMEE/meta_scores/{game_id}/{sort_1}/User_Data"; + + if (!Directory.Exists(userDataPath)) + return ""; + + HashSet friendsFilter = null; + if (friends) + { + friendsFilter = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var param in data.Parameters.Where(p => p.Name == "friends[]")) + friendsFilter.Add(param.Data); + } + + DateTime? dateFrom = null; + DateTime? dateTo = null; + if (filter == 1) // Today + { + dateFrom = DateTime.UtcNow.Date; + dateTo = dateFrom.Value.AddDays(1); + } + else if (filter == 2) + { + dateFrom = DateTime.UtcNow.Date.AddDays(-1); + dateTo = DateTime.UtcNow.Date; + } + + var entries = new List<(string psnid, long score1, long score2)>(); + + foreach (string filePath in Directory.EnumerateFiles(userDataPath, "*.xml")) + { + string fileName = Path.GetFileNameWithoutExtension(filePath); + + if (fileName.EndsWith("_2")) + continue; + + if (dateFrom.HasValue) + { + DateTime lastWrite = File.GetLastWriteTimeUtc(filePath); + if (lastWrite < dateFrom.Value || lastWrite >= dateTo.Value) + continue; + } + + if (friendsFilter != null && !friendsFilter.Contains(fileName)) + continue; + + long score1 = 0; + try + { + string raw = File.ReadAllText(filePath) + .Replace("", string.Empty) + .Replace("", string.Empty) + .Trim(); + if (double.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out double d1)) + score1 = (long)d1; + } + catch { continue; } + + long score2 = 0; + string filePath_2 = Path.Combine(userDataPath, fileName + "_2.xml"); + if (File.Exists(filePath_2)) + { + try + { + string raw2 = File.ReadAllText(filePath_2) + .Replace("", string.Empty) + .Replace("", string.Empty) + .Trim(); + if (double.TryParse(raw2, NumberStyles.Float, CultureInfo.InvariantCulture, out double d2)) + score2 = (long)d2; + } + catch { } + } + + entries.Add((fileName, score1, score2)); + } + + if (sort_1.Equals("DESC", StringComparison.OrdinalIgnoreCase)) + entries = entries.OrderByDescending(e => e.score1).ToList(); + else + entries = entries.OrderBy(e => e.score1).ToList(); + + var sb = new StringBuilder(); + sb.Append(""); + int limit = Math.Min(10, entries.Count); + for (int i = 0; i < limit; i++) + { + var e = entries[i]; + sb.Append(""); + sb.Append($"{e.psnid}"); + sb.Append("0"); + sb.Append($"{e.score1}"); + sb.Append($"{e.score2}"); + sb.Append(""); + } + sb.Append(""); + return sb.ToString(); } } + catch (Exception ex) + { + CustomLogger.LoggerAccessor.LogError($"[VEEMEE] - meta_scores - GetHighScoresPOST exception: {ex}"); + } - return $"{psnid}00"; + return ""; } } -} +} \ No newline at end of file diff --git a/Servers/ApacheNet/BuildIn/RouteHandlers/GameRoutes/WebAPIRoutes.cs b/Servers/ApacheNet/BuildIn/RouteHandlers/GameRoutes/WebAPIRoutes.cs index 88bbd18d7..955d424c7 100644 --- a/Servers/ApacheNet/BuildIn/RouteHandlers/GameRoutes/WebAPIRoutes.cs +++ b/Servers/ApacheNet/BuildIn/RouteHandlers/GameRoutes/WebAPIRoutes.cs @@ -29,6 +29,7 @@ using WebAPIService.GameServices.PSHOME.LOOT; using WebAPIService.GameServices.PSHOME.NDREAMS; using WebAPIService.GameServices.PSHOME.OHS; +using WebAPIService.GameServices.PSHOME.OSHABERI; using WebAPIService.GameServices.PSHOME.OUWF; using WebAPIService.GameServices.PSHOME.PREMIUMAGENCY; using WebAPIService.GameServices.PSHOME.RCHOME; @@ -876,6 +877,41 @@ internal class WebAPIRoutes return ctx.Response.Send(res).Result; } }, + new() { + Name = "OSHABERI Farm API", + UrlRegex = @".*/game/app/.*", + Hosts = new[] { + "homect-scej.jp", + "qa-homect-scej.jp", + "oc.homect-scej.jp" + }, + Callable = (ctx) => { + HttpStatusCode statusCode; + ctx.Response.ChunkedTransfer = ctx.AcceptChunked; + + string? res = new OSHABERIClass(ctx.Request.Method.ToString(), ctx.AbsolutePath, ApacheNetServerConfiguration.APIStaticFolder, ctx.FullUrl).ProcessRequest(ctx.Request.DataAsBytes, ctx.Request.ContentType); + if (string.IsNullOrEmpty(res)) + { + ctx.Response.ContentType = "text/plain"; + statusCode = HttpStatusCode.InternalServerError; + } + else + { + ctx.Response.Headers.Add("Date", DateTime.Now.ToString("r")); + ctx.Response.ContentType = "application/json"; + statusCode = HttpStatusCode.OK; + } + ctx.Response.StatusCode = (int)statusCode; + byte[]? responseBytes = !string.IsNullOrEmpty(res) + ? new UTF8Encoding(false).GetBytes(res) + : null; + + if (ctx.Response.ChunkedTransfer) + return ctx.Response.SendChunk(responseBytes, true).Result; + else + return ctx.Response.Send(responseBytes).Result; + } + }, new() { Name = "FROMSOFTWARE API", Hosts = new[] { "acvd-ps3ww-cdn.fromsoftware.jp" }, diff --git a/Servers/ApacheNet/Program.cs b/Servers/ApacheNet/Program.cs index 3fb2afee2..2506be1d5 100644 --- a/Servers/ApacheNet/Program.cs +++ b/Servers/ApacheNet/Program.cs @@ -126,7 +126,8 @@ public static class ApacheNetServerConfiguration "secure.cpreprod.homeps3.online.scee.com", "secure.heavyh2o.net", "game.hellfiregames.com", - "www.ndreamsgateway.com" + "www.ndreamsgateway.com", + "oc.homect-scej.jp" }; public static List? Ports { get; set; } = new() { NetworkPorts.Http.Tcp, NetworkPorts.Http.Ssl, 3074, 3658, 9090, 10010, 26004, 33000 }; public static List? RedirectRules { get; set; }