feat: PB exports

This commit is contained in:
あかニャン 2024-05-21 02:25:56 +09:00
parent e8b8caf378
commit 9c180657b4
9 changed files with 131 additions and 36 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
*.csproj *.csproj
bin/ bin/
obj/ obj/
dist/

View File

@ -16,4 +16,10 @@
<Reference Include="UnityEngine.UI"><HintPath></HintPath></Reference> <Reference Include="UnityEngine.UI"><HintPath></HintPath></Reference>
<Reference Include="Mu3Assembly"><HintPath></HintPath></Reference> <Reference Include="Mu3Assembly"><HintPath></HintPath></Reference>
</ItemGroup> </ItemGroup>
<Target Name="Dist" AfterTargets="Build">
<Copy SourceFiles="$(TargetDir)$(AssemblyName).dll" DestinationFolder="dist" />
<Copy SourceFiles="$(TargetDir)$(AssemblyName).dll" DestinationFolder="dist/BepInEx/monomod" />
<ZipDirectory SourceDirectory="dist/BepInEx" DestinationFile="dist/inohara.zip" Overwrite="true" />
</Target>
</Project> </Project>

View File

@ -23,7 +23,7 @@ public struct BatchMeta {
public struct BatchScore { public struct BatchScore {
public int score; public int score;
public string difficulty; public string difficulty;
public UInt64 timeAchieved; public UInt64? timeAchieved;
public string noteLamp; public string noteLamp;
public string bellLamp; public string bellLamp;
public string matchType; public string matchType;
@ -34,20 +34,20 @@ public struct BatchScore {
[Serializable] [Serializable]
public struct BatchJudgements { public struct BatchJudgements {
public int cbreak; public int? cbreak;
public int breakMyBonesIWill; public int? breakMyBonesIWill;
public int hit; public int? hit;
public int miss; public int? miss;
} }
[Serializable] [Serializable]
public struct BatchOptional { public struct BatchOptional {
public int fast; public int? fast;
public int slow; public int? slow;
public int bellCount; public int? bellCount;
public int totalBellCount; public int? totalBellCount;
public int damage; public int? damage;
public int platScore; public int? platScore;
} }
/** /**

View File

@ -22,6 +22,7 @@ public class Exporter: SingletonMonoBehaviour<Exporter> {
public string StatusPoint; public string StatusPoint;
public string ImportPoint; public string ImportPoint;
public bool EnableText; public bool EnableText;
public bool ExportPBs;
} }
static readonly float REQ2_DELAY = 4f; static readonly float REQ2_DELAY = 4f;
static readonly float DRAW_DURATION = 4f; static readonly float DRAW_DURATION = 4f;
@ -32,9 +33,9 @@ public class Exporter: SingletonMonoBehaviour<Exporter> {
private readonly Font _arial = Resources.GetBuiltinResource<Font>("Arial.ttf"); private readonly Font _arial = Resources.GetBuiltinResource<Font>("Arial.ttf");
private List<BatchScore> _scores = new(); private List<BatchScore> _scores = new();
private void Log(object o, bool text) { private void Log(object o, bool drawText) {
Debug.Log("[Inohara] " + o.ToString()); Debug.Log("[Inohara] " + o.ToString());
if(_cfg.EnableText && text) { if(_cfg.EnableText && drawText) {
StartCoroutine(DrawMessage(o.ToString(), new Color(1f, 1f, 1f, 1.0f))); StartCoroutine(DrawMessage(o.ToString(), new Color(1f, 1f, 1f, 1.0f)));
} }
} }
@ -54,6 +55,7 @@ public class Exporter: SingletonMonoBehaviour<Exporter> {
} }
public void LoadCfg() { public void LoadCfg() {
_cfg.Enable = false;
try { try {
using StreamReader reader = new(Path.Combine(Application.dataPath, "../inohara.cfg")); using StreamReader reader = new(Path.Combine(Application.dataPath, "../inohara.cfg"));
Dictionary<string, string> options = new(); Dictionary<string, string> options = new();
@ -81,6 +83,9 @@ public class Exporter: SingletonMonoBehaviour<Exporter> {
if(!options.ContainsKey("enableosd") || !bool.TryParse(options["enableosd"], out _cfg.EnableText)) { if(!options.ContainsKey("enableosd") || !bool.TryParse(options["enableosd"], out _cfg.EnableText)) {
_cfg.EnableText = false; _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)) { if(!options.ContainsKey("timeout") || !int.TryParse(options["timeout"], out _cfg.Timeout)) {
_cfg.Timeout = 3; _cfg.Timeout = 3;
} }
@ -136,6 +141,7 @@ public class Exporter: SingletonMonoBehaviour<Exporter> {
using var req = new UnityWebRequest(_cfg.BaseUrl + _cfg.StatusPoint, "GET"); using var req = new UnityWebRequest(_cfg.BaseUrl + _cfg.StatusPoint, "GET");
req.SetRequestHeader("Authorization", "Bearer " + tmpBearer); req.SetRequestHeader("Authorization", "Bearer " + tmpBearer);
req.downloadHandler = new DownloadHandlerBuffer(); req.downloadHandler = new DownloadHandlerBuffer();
req.timeout = _cfg.Timeout;
yield return req.Send(); yield return req.Send();
@ -174,7 +180,7 @@ public class Exporter: SingletonMonoBehaviour<Exporter> {
req.SetRequestHeader("Content-Type", "application/json"); req.SetRequestHeader("Content-Type", "application/json");
_scores.Add(Util.CreateScore(result, info)); _scores.Add(Util.CreateScore(result, info));
var batch = Util.CreateBatch(_scores); var batch = Util.CreateBatch(_scores, "inohara");
byte[] jsonToSend = new UTF8Encoding().GetBytes(batch); byte[] jsonToSend = new UTF8Encoding().GetBytes(batch);
req.uploadHandler = new UploadHandlerRaw(jsonToSend); req.uploadHandler = new UploadHandlerRaw(jsonToSend);
@ -221,6 +227,27 @@ public class Exporter: SingletonMonoBehaviour<Exporter> {
} }
} }
public void ExportPBs() {
if(!_cfg.ExportPBs) {
return;
}
var userMusic = Singleton<UserManager>.instance.userMusic;
var scores = new List<BatchScore>();
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 // This is just for fun
private IEnumerator DrawMessage(string message, Color color) { private IEnumerator DrawMessage(string message, Color color) {
GameObject canvasGO = new() { GameObject canvasGO = new() {

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using MU3.Battle; using MU3.Battle;
using MU3.DataStudio; using MU3.DataStudio;
using MU3.Game; using MU3.Game;
using MU3.User;
using UnityEngine; using UnityEngine;
namespace Inohara; namespace Inohara;
@ -62,12 +63,31 @@ class Util {
}; };
} }
public static string CreateBatch(List<BatchScore> 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<BatchScore> scores, string name) {
var bm = new BatchManual { var bm = new BatchManual {
meta = new BatchMeta { meta = new BatchMeta {
game = "ongeki", game = "ongeki",
playtype = "Single", playtype = "Single",
service = "inohara" service = name
}, },
scores = scores.ToArray() scores = scores.ToArray()
}; };

View File

@ -9,16 +9,16 @@ using MU3.Util;
namespace MU3.App; namespace MU3.App;
public class patch_ApplicationMU3 : ApplicationMU3 { 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() { GameObject go = new() {
name = "Inohara" name = "Inohara"
}; };
go.AddComponent<Inohara.Exporter>(); go.AddComponent<Inohara.Exporter>();
DontDestroyOnLoad(go); DontDestroyOnLoad(go);
orig_Execute_WaitAMDaemonReady(); orig_initializeAfterAMDaemonReady();
SingletonMonoBehaviour<Inohara.Exporter>.instance.LoadCfg(); SingletonMonoBehaviour<Inohara.Exporter>.instance.LoadCfg();
} }
} }

View File

@ -10,9 +10,15 @@ namespace MU3;
public class patch_Scene_25_Login : Scene_25_Login { public class patch_Scene_25_Login : Scene_25_Login {
private extern void orig_finishAime(); private extern void orig_finishAime();
private extern void orig_GetUserRival_Init();
private void finishAime() { private void finishAime() {
orig_finishAime(); orig_finishAime();
SingletonMonoBehaviour<Inohara.Exporter>.instance.Authorize(); SingletonMonoBehaviour<Inohara.Exporter>.instance.Authorize();
} }
private void GetUserRival_Init() {
orig_GetUserRival_Init();
SingletonMonoBehaviour<Inohara.Exporter>.instance.ExportPBs();
}
} }

View File

@ -8,11 +8,28 @@ A µ3 score exporter for [Tachi](https://github.com/zkldi/Tachi).
- 1.45 - 1.45
### Installation ### 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) #### The simple method
- Download [BepInEx](https://github.com/BepInEx/BepInEx/releases/) - Update [segatools](https://gitea.tendokyu.moe/Dniel97/segatools).
- Copy the `BepInEx` directory into the base game directory (where `mu3.exe` is); omit `winhttp.dll` - 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`: - Modify this entry in `segatools.ini`:
```ini ```ini
[unity] [unity]
@ -20,19 +37,26 @@ Get the config file [here](https://kamai.tachi.ac/client-file-flow/CIa914320cd34
``` ```
- If you don't have this entry, update segatools. - 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`. - 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` - Download `Assembly-CSharp.Inohara.mm.dll` from [releases](https://gitea.tendokyu.moe/akanyan/inohara/releases) and put it in `BepInEx\monomod`.
- Put `inohara.cfg` in the base game directory (next to `mu3.exe`)
#### The MonoMod method
- Download [MonoMod](https://github.com/MonoMod/MonoMod/releases) #### The hardpatch method
- Copy `Assembly-CSharp.Inohara.mm.dll` into `mu3_Data\Managed` - Download [MonoMod](https://github.com/MonoMod/MonoMod/releases).
- Run `MonoMod.exe mu3_Data\Managed\Assembly-CSharp.dll` - Download `Assembly-CSharp.Inohara.mm.dll` from [releases](https://gitea.tendokyu.moe/akanyan/inohara/releases) and put it in `mu3_Data\Managed`.
- Backup `Assembly-CSharp.dll` - Run:
- Rename `MONOMODDED_Assembly-CSharp.dll` to `Assembly-CSharp.dll` ```
- Move `inohara.cfg` to the base game directory (next to `mu3.exe`) MonoMod.exe mu3_Data\Managed\Assembly-CSharp.dll
```
- Backup `Assembly-CSharp.dll`.
- Rename `MONOMODDED_Assembly-CSharp.dll` to `Assembly-CSharp.dll`.
### Usage ### 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 ### Building
Provide your own `Assembly-CSharp.dll` (or `_unpacked`) and `UnityEngine.UI.dll`, then `dotnet restore`, `dotnet build`. Provide your own `Assembly-CSharp.dll` (or `_unpacked`) and `UnityEngine.UI.dll`, then `dotnet restore`, `dotnet build`.

View File

@ -1,18 +1,29 @@
[Options] [Options]
# Whether to enable score submissions # Whether to enable score submissions
Enable = true Enable = true
# Timeout for web requests, in seconds # Timeout for web requests, in seconds
Timeout = 3 Timeout = 3
# Tachi instance base URL # Tachi instance base URL
BaseUrl = BaseUrl = https://kamai.tachi.ac/
# Tachi status endpoint # Tachi status endpoint
Status = /api/v1/status Status = /api/v1/status
# Tachi score import endpoint # Tachi score import endpoint
Import = /ir/direct-manual/import Import = /ir/direct-manual/import
# Display status on-screen (rudimentarily)
# Whether to display status on-screen (rudimentarily)
EnableOSD = false 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] [Keys]
* = %%TACHI_KEY%% * = %%TACHI_KEY%%
# If you have a multi-user setup, you can configure # If you have a multi-user setup, you can configure