From 9c180657b435b01fcbecabe8fa5ded4291a26b09 Mon Sep 17 00:00:00 2001 From: akanyan Date: Tue, 21 May 2024 02:25:56 +0900 Subject: [PATCH] feat: PB exports --- .gitignore | 3 +- Inohara.csproj.template | 6 ++++ Inohara/DataTypes.cs | 22 +++++++------- Inohara/Exporter.cs | 33 +++++++++++++++++++-- Inohara/Util.cs | 24 +++++++++++++-- MU3.App/patch_ApplicationMU3.cs | 6 ++-- MU3/patch_Scene_25_Login.cs | 6 ++++ README.md | 52 ++++++++++++++++++++++++--------- inohara.cfg.example | 15 ++++++++-- 9 files changed, 131 insertions(+), 36 deletions(-) diff --git a/.gitignore b/.gitignore index e4d0d0f..56449a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.dll *.csproj bin/ -obj/ \ No newline at end of file +obj/ +dist/ \ No newline at end of file diff --git a/Inohara.csproj.template b/Inohara.csproj.template index c0345fb..19c67bc 100644 --- a/Inohara.csproj.template +++ b/Inohara.csproj.template @@ -16,4 +16,10 @@ + + + + + + diff --git a/Inohara/DataTypes.cs b/Inohara/DataTypes.cs index aecbc51..d47cfa9 100644 --- a/Inohara/DataTypes.cs +++ b/Inohara/DataTypes.cs @@ -23,7 +23,7 @@ public struct BatchMeta { public struct BatchScore { public int score; public string difficulty; - public UInt64 timeAchieved; + public UInt64? timeAchieved; public string noteLamp; public string bellLamp; public string matchType; @@ -34,20 +34,20 @@ public struct BatchScore { [Serializable] public struct BatchJudgements { - public int cbreak; - public int breakMyBonesIWill; - public int hit; - public int miss; + public int? cbreak; + public int? breakMyBonesIWill; + public int? hit; + public int? miss; } [Serializable] public struct BatchOptional { - public int fast; - public int slow; - public int bellCount; - public int totalBellCount; - public int damage; - public int platScore; + public int? fast; + public int? slow; + public int? bellCount; + public int? totalBellCount; + public int? damage; + public int? platScore; } /** diff --git a/Inohara/Exporter.cs b/Inohara/Exporter.cs index 78b0901..794dc76 100644 --- a/Inohara/Exporter.cs +++ b/Inohara/Exporter.cs @@ -22,6 +22,7 @@ public class Exporter: SingletonMonoBehaviour { public string StatusPoint; public string ImportPoint; public bool EnableText; + public bool ExportPBs; } static readonly float REQ2_DELAY = 4f; static readonly float DRAW_DURATION = 4f; @@ -32,9 +33,9 @@ public class Exporter: SingletonMonoBehaviour { private readonly Font _arial = Resources.GetBuiltinResource("Arial.ttf"); private List _scores = new(); - private void Log(object o, bool text) { + private void Log(object o, bool drawText) { Debug.Log("[Inohara] " + o.ToString()); - if(_cfg.EnableText && text) { + if(_cfg.EnableText && drawText) { StartCoroutine(DrawMessage(o.ToString(), new Color(1f, 1f, 1f, 1.0f))); } } @@ -54,6 +55,7 @@ public class Exporter: SingletonMonoBehaviour { } public void LoadCfg() { + _cfg.Enable = false; try { using StreamReader reader = new(Path.Combine(Application.dataPath, "../inohara.cfg")); Dictionary options = new(); @@ -81,6 +83,9 @@ public class Exporter: SingletonMonoBehaviour { if(!options.ContainsKey("enableosd") || !bool.TryParse(options["enableosd"], out _cfg.EnableText)) { _cfg.EnableText = false; } + if(!options.ContainsKey("exportpbs") || !bool.TryParse(options["exportpbs"], out _cfg.ExportPBs)) { + _cfg.ExportPBs = false; + } if(!options.ContainsKey("timeout") || !int.TryParse(options["timeout"], out _cfg.Timeout)) { _cfg.Timeout = 3; } @@ -136,6 +141,7 @@ public class Exporter: SingletonMonoBehaviour { using var req = new UnityWebRequest(_cfg.BaseUrl + _cfg.StatusPoint, "GET"); req.SetRequestHeader("Authorization", "Bearer " + tmpBearer); req.downloadHandler = new DownloadHandlerBuffer(); + req.timeout = _cfg.Timeout; yield return req.Send(); @@ -174,7 +180,7 @@ public class Exporter: SingletonMonoBehaviour { req.SetRequestHeader("Content-Type", "application/json"); _scores.Add(Util.CreateScore(result, info)); - var batch = Util.CreateBatch(_scores); + var batch = Util.CreateBatch(_scores, "inohara"); byte[] jsonToSend = new UTF8Encoding().GetBytes(batch); req.uploadHandler = new UploadHandlerRaw(jsonToSend); @@ -221,6 +227,27 @@ public class Exporter: SingletonMonoBehaviour { } } + public void ExportPBs() { + if(!_cfg.ExportPBs) { + return; + } + + var userMusic = Singleton.instance.userMusic; + var scores = new List(); + foreach(var x in userMusic.Values) { + foreach(var y in x.UserFumen) { + if(y != null) { + scores.Add(Util.CreatePB(y)); + } + } + } + var batch = Util.CreateBatch(scores, "inohara-pb"); + using StreamWriter writer = new(Path.Combine(Application.dataPath, "../batch-manual.json")); + writer.Write(batch); + + Log("Exported PBs to batch-manual.json", false); + } + // This is just for fun private IEnumerator DrawMessage(string message, Color color) { GameObject canvasGO = new() { diff --git a/Inohara/Util.cs b/Inohara/Util.cs index 5087baa..9d7325d 100644 --- a/Inohara/Util.cs +++ b/Inohara/Util.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using MU3.Battle; using MU3.DataStudio; using MU3.Game; +using MU3.User; using UnityEngine; namespace Inohara; @@ -62,12 +63,31 @@ class Util { }; } - public static string CreateBatch(List scores) { + public static BatchScore CreatePB(UserFumen fumen) { + return new BatchScore { + score = fumen.TechScoreMax, + difficulty = GetStringDiff(fumen.Level), + matchType = "inGameID", + identifier = fumen.MusicId.ToString(), + bellLamp = fumen.IsFullBell ? "FULL BELL" : "NONE", + noteLamp = + fumen.IsAllBreak ? "ALL BREAK" : + fumen.IsFullCombo ? "FULL COMBO" : + // fumen.isClear is a bool that seems to flag battle victory. Useless + // basing this on score is ass but for the most part it will be accurate. + fumen.TechScoreMax >= 940000 ? "CLEAR" : + "LOSS", + optional = new BatchOptional() {}, + judgements = new BatchJudgements() {} + }; + } + + public static string CreateBatch(List scores, string name) { var bm = new BatchManual { meta = new BatchMeta { game = "ongeki", playtype = "Single", - service = "inohara" + service = name }, scores = scores.ToArray() }; diff --git a/MU3.App/patch_ApplicationMU3.cs b/MU3.App/patch_ApplicationMU3.cs index 13d4cc7..09f4a8f 100644 --- a/MU3.App/patch_ApplicationMU3.cs +++ b/MU3.App/patch_ApplicationMU3.cs @@ -9,16 +9,16 @@ using MU3.Util; namespace MU3.App; public class patch_ApplicationMU3 : ApplicationMU3 { - private extern void orig_Execute_WaitAMDaemonReady(); + private extern void orig_initializeAfterAMDaemonReady(); - private void Execute_WaitAMDaemonReady() { + private void initializeAfterAMDaemonReady() { GameObject go = new() { name = "Inohara" }; go.AddComponent(); DontDestroyOnLoad(go); - orig_Execute_WaitAMDaemonReady(); + orig_initializeAfterAMDaemonReady(); SingletonMonoBehaviour.instance.LoadCfg(); } } diff --git a/MU3/patch_Scene_25_Login.cs b/MU3/patch_Scene_25_Login.cs index 4a00639..d07be4a 100644 --- a/MU3/patch_Scene_25_Login.cs +++ b/MU3/patch_Scene_25_Login.cs @@ -10,9 +10,15 @@ namespace MU3; public class patch_Scene_25_Login : Scene_25_Login { private extern void orig_finishAime(); + private extern void orig_GetUserRival_Init(); private void finishAime() { orig_finishAime(); SingletonMonoBehaviour.instance.Authorize(); } + + private void GetUserRival_Init() { + orig_GetUserRival_Init(); + SingletonMonoBehaviour.instance.ExportPBs(); + } } diff --git a/README.md b/README.md index 3695d74..5c7f5cd 100644 --- a/README.md +++ b/README.md @@ -8,31 +8,55 @@ A µ3 score exporter for [Tachi](https://github.com/zkldi/Tachi). - 1.45 ### Installation -Get the config file [here](https://kamai.tachi.ac/client-file-flow/CIa914320cd344a8db712cf0c99254c205ca940463), download the DLL [here](https://gitea.tendokyu.moe/akanyan/inohara/releases) and follow one of the methods below. +First, get the config file [here](https://kamai.tachi.ac/client-file-flow/CIa914320cd344a8db712cf0c99254c205ca940463) and put it in the base game directory (next to `mu3.exe`), then follow one of the methods below. -#### The BepInEx method (recommended) -- Download [BepInEx](https://github.com/BepInEx/BepInEx/releases/) -- Copy the `BepInEx` directory into the base game directory (where `mu3.exe` is); omit `winhttp.dll` +#### The simple method +- Update [segatools](https://gitea.tendokyu.moe/Dniel97/segatools). +- Download `inohara.zip` from [releases](https://gitea.tendokyu.moe/akanyan/inohara/releases) and extract it into the base game directory. - Modify this entry in `segatools.ini`: ```ini [unity] targetAssembly=BepInEx\core\BepInEx.Preloader.dll ``` + - The game directory should look like this (abridged): +``` +├── BepInEx +├── mu3_Data +├── inohara.cfg +├── mu3.exe +└── segatools.ini +``` + +#### The manual BepInEx method +- Download [BepInEx 5](https://github.com/BepInEx/BepInEx/releases/) and [BepInEx.MonoMod.Loader](https://github.com/BepInEx/BepInEx.MonoMod.Loader). +- Copy both `BepInEx` directories into the base game directory; omit `winhttp.dll`. +- Modify this entry in `segatools.ini`: + ```ini + [unity] + targetAssembly=BepInEx\core\BepInEx.Preloader.dll + ``` - If you don't have this entry, update segatools. - If you insist on not updating segatools, instead copy `winhttp.dll` and rename it to `version.dll`. -- Move `Assembly-CSharp.Inohara.mm.dll` to `BepInEx\monomod` -- Put `inohara.cfg` in the base game directory (next to `mu3.exe`) +- Download `Assembly-CSharp.Inohara.mm.dll` from [releases](https://gitea.tendokyu.moe/akanyan/inohara/releases) and put it in `BepInEx\monomod`. -#### The MonoMod method -- Download [MonoMod](https://github.com/MonoMod/MonoMod/releases) -- Copy `Assembly-CSharp.Inohara.mm.dll` into `mu3_Data\Managed` -- Run `MonoMod.exe mu3_Data\Managed\Assembly-CSharp.dll` -- Backup `Assembly-CSharp.dll` -- Rename `MONOMODDED_Assembly-CSharp.dll` to `Assembly-CSharp.dll` -- Move `inohara.cfg` to the base game directory (next to `mu3.exe`) + +#### The hardpatch method +- Download [MonoMod](https://github.com/MonoMod/MonoMod/releases). +- Download `Assembly-CSharp.Inohara.mm.dll` from [releases](https://gitea.tendokyu.moe/akanyan/inohara/releases) and put it in `mu3_Data\Managed`. +- Run: + ``` + MonoMod.exe mu3_Data\Managed\Assembly-CSharp.dll + ``` +- Backup `Assembly-CSharp.dll`. +- Rename `MONOMODDED_Assembly-CSharp.dll` to `Assembly-CSharp.dll`. ### Usage -Scores are sent after each play and that's it. You can nonetheless make sure it's running by checking the console or toggling `EnableOSD`. +Scores are sent after each play and that's it. You can nonetheless make sure it's running by enabling the console in `BepInEx\config\BepInEx.cfg` or the OSD in `inohara.cfg`. + +#### Uploading older scores +Use [this script](https://gist.github.com/nyairobi/ffdf9e674f31987b1ffbd38d31b55f6c). You only have to do this once as Inohara will handle all future scores. If you are on a remote server, contact the admin. + +**As a last resort**, you can toggle `ExportPBs` and upload the generated `batch-manual.json` [here](https://kamai.tachi.ac/import/batch-manual). Those PBs are industrial grade dogshit. Should the server admin provide you access to the playlog down the line, delete the `inohara-pb` import. ### Building Provide your own `Assembly-CSharp.dll` (or `_unpacked`) and `UnityEngine.UI.dll`, then `dotnet restore`, `dotnet build`. \ No newline at end of file diff --git a/inohara.cfg.example b/inohara.cfg.example index df66a50..8903242 100644 --- a/inohara.cfg.example +++ b/inohara.cfg.example @@ -1,18 +1,29 @@ [Options] + # Whether to enable score submissions Enable = true + # Timeout for web requests, in seconds Timeout = 3 + # Tachi instance base URL -BaseUrl = +BaseUrl = https://kamai.tachi.ac/ + # Tachi status endpoint Status = /api/v1/status + # Tachi score import endpoint Import = /ir/direct-manual/import -# Display status on-screen (rudimentarily) + +# Whether to display status on-screen (rudimentarily) EnableOSD = false +# Whether to export your PBs for batch-manual (saved to batch-manual.json) +# This is very, very bad and should only be used as last resort +ExportPBs = false + [Keys] + * = %%TACHI_KEY%% # If you have a multi-user setup, you can configure