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