initial commit
This commit is contained in:
commit
e8b8caf378
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
*.dll
|
||||||
|
*.csproj
|
||||||
|
bin/
|
||||||
|
obj/
|
19
Inohara.csproj.template
Normal file
19
Inohara.csproj.template
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net35</TargetFramework>
|
||||||
|
<AssemblyName>Assembly-CSharp.Inohara.mm</AssemblyName>
|
||||||
|
<Company>7EVENDAYS⇔HOLIDAYS</Company>
|
||||||
|
<Description>Tachi exporter for mu3</Description>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
<LangVersion>11.0</LangVersion>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="UnityEngine.Modules" Version="5.6.4" IncludeAssets="compile" />
|
||||||
|
<Reference Include="UnityEngine.UI"><HintPath></HintPath></Reference>
|
||||||
|
<Reference Include="Mu3Assembly"><HintPath></HintPath></Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
92
Inohara/DataTypes.cs
Normal file
92
Inohara/DataTypes.cs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Inohara;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch manual
|
||||||
|
*/
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct BatchManual {
|
||||||
|
public BatchMeta meta;
|
||||||
|
public BatchScore[] scores;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct BatchMeta {
|
||||||
|
public string game;
|
||||||
|
public string playtype;
|
||||||
|
public string service;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct BatchScore {
|
||||||
|
public int score;
|
||||||
|
public string difficulty;
|
||||||
|
public UInt64 timeAchieved;
|
||||||
|
public string noteLamp;
|
||||||
|
public string bellLamp;
|
||||||
|
public string matchType;
|
||||||
|
public string identifier;
|
||||||
|
public BatchJudgements judgements;
|
||||||
|
public BatchOptional optional;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct BatchJudgements {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BM response 1
|
||||||
|
*/
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct BatchResponse {
|
||||||
|
public bool success;
|
||||||
|
public BatchResponseBody body;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct BatchResponseBody {
|
||||||
|
public string url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BM response 2
|
||||||
|
*/
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct BatchResponse2 {
|
||||||
|
public bool success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API status response
|
||||||
|
*/
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct StatusResponse {
|
||||||
|
public bool success;
|
||||||
|
public StatusBody body;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct StatusBody {
|
||||||
|
public string version;
|
||||||
|
public string whoami;
|
||||||
|
public string[] permissions;
|
||||||
|
}
|
256
Inohara/Exporter.cs
Normal file
256
Inohara/Exporter.cs
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using MU3.CustomUI;
|
||||||
|
using MU3.Game;
|
||||||
|
using MU3.User;
|
||||||
|
using MU3.Util;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Networking;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace Inohara;
|
||||||
|
|
||||||
|
public class Exporter: SingletonMonoBehaviour<Exporter> {
|
||||||
|
private struct Config {
|
||||||
|
public bool Enable;
|
||||||
|
public int Timeout;
|
||||||
|
public string BaseUrl;
|
||||||
|
public string StatusPoint;
|
||||||
|
public string ImportPoint;
|
||||||
|
public bool EnableText;
|
||||||
|
}
|
||||||
|
static readonly float REQ2_DELAY = 4f;
|
||||||
|
static readonly float DRAW_DURATION = 4f;
|
||||||
|
protected new bool _dontDestroyOnLoad = true;
|
||||||
|
private Config _cfg;
|
||||||
|
private readonly Dictionary<string, string> _tokens = new();
|
||||||
|
private string _currToken = "";
|
||||||
|
private readonly Font _arial = Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||||||
|
private List<BatchScore> _scores = new();
|
||||||
|
|
||||||
|
private void Log(object o, bool text) {
|
||||||
|
Debug.Log("[Inohara] " + o.ToString());
|
||||||
|
if(_cfg.EnableText && text) {
|
||||||
|
StartCoroutine(DrawMessage(o.ToString(), new Color(1f, 1f, 1f, 1.0f)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogSuccess(object o) {
|
||||||
|
Debug.Log("[Inohara] " + o.ToString());
|
||||||
|
if(_cfg.EnableText) {
|
||||||
|
StartCoroutine(DrawMessage(o.ToString(), new Color(0f, 0.83f, 0.14f, 1.0f)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogError(object o) {
|
||||||
|
Debug.LogError("[Inohara] " + o.ToString());
|
||||||
|
if(_cfg.EnableText) {
|
||||||
|
StartCoroutine(DrawMessage(o.ToString(), new Color(1f, 0.42f, 0.42f, 1.0f)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadCfg() {
|
||||||
|
try {
|
||||||
|
using StreamReader reader = new(Path.Combine(Application.dataPath, "../inohara.cfg"));
|
||||||
|
Dictionary<string, string> options = new();
|
||||||
|
Dictionary<string, string> dict = null;
|
||||||
|
string line;
|
||||||
|
while ((line = reader.ReadLine()) != null) {
|
||||||
|
line = line.Split(new char[] { '#' }, 2)[0];
|
||||||
|
if(line.ToLower() == "[keys]") {
|
||||||
|
dict = _tokens;
|
||||||
|
} else if(line.ToLower() == "[options]") {
|
||||||
|
dict = options;
|
||||||
|
} else if(line.StartsWith("[")) {
|
||||||
|
dict = null;
|
||||||
|
} else if(dict != null) {
|
||||||
|
string[] tokens = line.Split(new char[] { '=' }, 2);
|
||||||
|
if(tokens.Length == 2) {
|
||||||
|
dict.Add(tokens[0].Trim().ToLower(), tokens[1].Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!options.ContainsKey("enable") || !bool.TryParse(options["enable"], out _cfg.Enable)) {
|
||||||
|
_cfg.Enable = false;
|
||||||
|
}
|
||||||
|
if(!options.ContainsKey("enableosd") || !bool.TryParse(options["enableosd"], out _cfg.EnableText)) {
|
||||||
|
_cfg.EnableText = false;
|
||||||
|
}
|
||||||
|
if(!options.ContainsKey("timeout") || !int.TryParse(options["timeout"], out _cfg.Timeout)) {
|
||||||
|
_cfg.Timeout = 3;
|
||||||
|
}
|
||||||
|
if(!options.TryGetValue("baseurl", out _cfg.BaseUrl)) {
|
||||||
|
LogError("Config error: missing option BaseUrl");
|
||||||
|
_cfg.Enable = false;
|
||||||
|
}
|
||||||
|
if(!options.TryGetValue("status", out _cfg.StatusPoint)) {
|
||||||
|
_cfg.StatusPoint = "/api/v1/status";
|
||||||
|
}
|
||||||
|
if(!options.TryGetValue("import", out _cfg.ImportPoint)) {
|
||||||
|
_cfg.ImportPoint = "/ir/direct-manual/import";
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogError("Config loading failed: " + e);
|
||||||
|
_cfg.Enable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_cfg.Enable) {
|
||||||
|
Log("Score submissions enabled", true);
|
||||||
|
} else {
|
||||||
|
Log("Score submissions disabled", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Authorize() {
|
||||||
|
if(!_cfg.Enable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var username = Singleton<UserManager>.instance.UserName
|
||||||
|
.Normalize(NormalizationForm.FormKC)
|
||||||
|
.ToLower();
|
||||||
|
|
||||||
|
_currToken = "";
|
||||||
|
_scores.Clear();
|
||||||
|
|
||||||
|
string tmpBearer;
|
||||||
|
|
||||||
|
if(!_tokens.ContainsKey(username) && !_tokens.ContainsKey("*")) {
|
||||||
|
LogError("User " + username + " not found");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if(!_tokens.TryGetValue(username, out tmpBearer)) {
|
||||||
|
tmpBearer = _tokens["*"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StartCoroutine(AuthorizeReq(tmpBearer));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator AuthorizeReq(string tmpBearer) {
|
||||||
|
using var req = new UnityWebRequest(_cfg.BaseUrl + _cfg.StatusPoint, "GET");
|
||||||
|
req.SetRequestHeader("Authorization", "Bearer " + tmpBearer);
|
||||||
|
req.downloadHandler = new DownloadHandlerBuffer();
|
||||||
|
|
||||||
|
yield return req.Send();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(req.responseCode == 200) {
|
||||||
|
var res = JsonUtility.FromJson<StatusResponse>(req.downloadHandler.text);
|
||||||
|
if(res.success == true) {
|
||||||
|
if(res.body.permissions.Contains("submit_score")) {
|
||||||
|
LogSuccess("Logged in");
|
||||||
|
Log(string.Format("Tachi {0} (user id={1})", res.body.version, res.body.whoami), false);
|
||||||
|
_currToken = tmpBearer;
|
||||||
|
} else {
|
||||||
|
LogError("Missing the submit_score permission");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LogError("Unable to log into Tachi");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
LogError("Unable to log into Tachi: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Export(BattleResult result, SessionInfo info) {
|
||||||
|
if(_currToken == "" || !_cfg.Enable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StartCoroutine(ExportReq(result, info));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator ExportReq(BattleResult result, SessionInfo info) {
|
||||||
|
using var req = new UnityWebRequest(_cfg.BaseUrl + _cfg.ImportPoint, "POST");
|
||||||
|
req.timeout = _cfg.Timeout;
|
||||||
|
req.SetRequestHeader("Authorization", "Bearer " + _currToken);
|
||||||
|
req.SetRequestHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
|
_scores.Add(Util.CreateScore(result, info));
|
||||||
|
var batch = Util.CreateBatch(_scores);
|
||||||
|
|
||||||
|
byte[] jsonToSend = new UTF8Encoding().GetBytes(batch);
|
||||||
|
req.uploadHandler = new UploadHandlerRaw(jsonToSend);
|
||||||
|
req.downloadHandler = new DownloadHandlerBuffer();
|
||||||
|
|
||||||
|
Log("Uploading " + _scores.Count + " score(s)", false);
|
||||||
|
|
||||||
|
yield return req.Send();
|
||||||
|
|
||||||
|
if(req.responseCode == 200) {
|
||||||
|
LogSuccess("Upload successful");
|
||||||
|
Log("Warning: Non-deferred DIRECT-MANUAL is untested", false);
|
||||||
|
_scores.Clear();
|
||||||
|
} else if (req.responseCode == 202) {
|
||||||
|
var res = JsonUtility.FromJson<BatchResponse>(req.downloadHandler.text);
|
||||||
|
if(res.success == true) {
|
||||||
|
StartCoroutine(ExportReq2(res.body.url));
|
||||||
|
} else {
|
||||||
|
LogError("Upload failed: " + res.body);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LogError(string.Format("Upload failed ({0}): {1}", req.responseCode, req.error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator ExportReq2(string url) {
|
||||||
|
yield return new WaitForSeconds(REQ2_DELAY);
|
||||||
|
|
||||||
|
using var req = new UnityWebRequest(url, "GET");
|
||||||
|
req.downloadHandler = new DownloadHandlerBuffer();
|
||||||
|
|
||||||
|
yield return req.Send();
|
||||||
|
|
||||||
|
if(req.responseCode == 200) {
|
||||||
|
var res = JsonUtility.FromJson<BatchResponse2>(req.downloadHandler.text);
|
||||||
|
if(res.success == true) {
|
||||||
|
LogSuccess("Upload successful");
|
||||||
|
_scores.Clear();
|
||||||
|
} else {
|
||||||
|
LogError("Upload failed");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LogError(string.Format("Upload failed ({0}): {1}", req.responseCode, req.error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is just for fun
|
||||||
|
private IEnumerator DrawMessage(string message, Color color) {
|
||||||
|
GameObject canvasGO = new() {
|
||||||
|
name = "Canvas"
|
||||||
|
};
|
||||||
|
DontDestroyOnLoad(canvasGO);
|
||||||
|
canvasGO.AddComponent<Canvas>();
|
||||||
|
canvasGO.AddComponent<CanvasScaler>();
|
||||||
|
canvasGO.AddComponent<GraphicRaycaster>();
|
||||||
|
|
||||||
|
Canvas canvas = canvasGO.GetComponent<Canvas>();
|
||||||
|
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||||
|
|
||||||
|
GameObject textGO = new();
|
||||||
|
textGO.transform.parent = canvasGO.transform;
|
||||||
|
textGO.AddComponent<MU3Text>();
|
||||||
|
var text = textGO.GetComponent<MU3Text>();
|
||||||
|
text.font = _arial;
|
||||||
|
text.text = message;
|
||||||
|
text.fontSize = 20;
|
||||||
|
text.color = color;
|
||||||
|
text.alignment = TextAnchor.UpperCenter;
|
||||||
|
|
||||||
|
RectTransform rectTransform = text.GetComponent<RectTransform>();
|
||||||
|
rectTransform.localPosition = new Vector3(0, 0, 0);
|
||||||
|
rectTransform.sizeDelta = new Vector2(1080, 1400);
|
||||||
|
|
||||||
|
yield return new WaitForSeconds(DRAW_DURATION);
|
||||||
|
|
||||||
|
Destroy(canvasGO);
|
||||||
|
Destroy(text);
|
||||||
|
}
|
||||||
|
}
|
77
Inohara/Util.cs
Normal file
77
Inohara/Util.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MU3.Battle;
|
||||||
|
using MU3.DataStudio;
|
||||||
|
using MU3.Game;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Inohara;
|
||||||
|
|
||||||
|
class Util {
|
||||||
|
public static string GetLamp(BattleResult result) {
|
||||||
|
if(result.playResult == PlayResult.Failed) {
|
||||||
|
return "LOSS";
|
||||||
|
}
|
||||||
|
if(result.notesComboResult == NotesComboResult.None) {
|
||||||
|
return "CLEAR";
|
||||||
|
}
|
||||||
|
if(result.notesComboResult == NotesComboResult.FullCombo) {
|
||||||
|
return "FULL COMBO";
|
||||||
|
}
|
||||||
|
if(result.notesComboResult == NotesComboResult.AllBreak) {
|
||||||
|
return "ALL BREAK";
|
||||||
|
}
|
||||||
|
return "INVALID";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetStringDiff(FumenDifficulty diff) {
|
||||||
|
return diff switch {
|
||||||
|
FumenDifficulty.Basic => "BASIC",
|
||||||
|
FumenDifficulty.Advanced => "ADVANCED",
|
||||||
|
FumenDifficulty.Expert => "EXPERT",
|
||||||
|
FumenDifficulty.Master => "MASTER",
|
||||||
|
FumenDifficulty.Lunatic => "LUNATIC",
|
||||||
|
_ => "INVALID",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BatchScore CreateScore(BattleResult result, SessionInfo info) {
|
||||||
|
var timestampSec = (int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
|
||||||
|
return new BatchScore {
|
||||||
|
score = result.technicalScore,
|
||||||
|
difficulty = GetStringDiff(info.musicLevel),
|
||||||
|
timeAchieved = (ulong)timestampSec * 1000,
|
||||||
|
matchType = "inGameID",
|
||||||
|
identifier = info.musicData.id.ToString(),
|
||||||
|
bellLamp = result.bellComboResult == BellComboResult.None ? "NONE" : "FULL BELL",
|
||||||
|
noteLamp = GetLamp(result),
|
||||||
|
optional = new BatchOptional() {
|
||||||
|
fast = result.numNotesFast,
|
||||||
|
slow = result.numNotesLate,
|
||||||
|
bellCount = result.numBellCatch,
|
||||||
|
totalBellCount = result.numBellAny,
|
||||||
|
damage = result.countDamage,
|
||||||
|
platScore = result.platinumScore
|
||||||
|
},
|
||||||
|
judgements = new BatchJudgements() {
|
||||||
|
cbreak = result.numNotesCBreak,
|
||||||
|
breakMyBonesIWill = result.numNotesBreak,
|
||||||
|
hit = result.numNotesHit,
|
||||||
|
miss = result.numNotesMiss
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CreateBatch(List<BatchScore> scores) {
|
||||||
|
var bm = new BatchManual {
|
||||||
|
meta = new BatchMeta {
|
||||||
|
game = "ongeki",
|
||||||
|
playtype = "Single",
|
||||||
|
service = "inohara"
|
||||||
|
},
|
||||||
|
scores = scores.ToArray()
|
||||||
|
};
|
||||||
|
|
||||||
|
return JsonUtility.ToJson(bm).Replace("breakMyBonesIWill", "break");
|
||||||
|
}
|
||||||
|
}
|
24
LICENSE
Normal file
24
LICENSE
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <http://unlicense.org/>
|
24
MU3.App/patch_ApplicationMU3.cs
Normal file
24
MU3.App/patch_ApplicationMU3.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma warning disable CS0626
|
||||||
|
#pragma warning disable CS0649
|
||||||
|
#pragma warning disable IDE0051
|
||||||
|
#pragma warning disable IDE1006
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
using MU3.Util;
|
||||||
|
|
||||||
|
namespace MU3.App;
|
||||||
|
|
||||||
|
public class patch_ApplicationMU3 : ApplicationMU3 {
|
||||||
|
private extern void orig_Execute_WaitAMDaemonReady();
|
||||||
|
|
||||||
|
private void Execute_WaitAMDaemonReady() {
|
||||||
|
GameObject go = new() {
|
||||||
|
name = "Inohara"
|
||||||
|
};
|
||||||
|
go.AddComponent<Inohara.Exporter>();
|
||||||
|
|
||||||
|
DontDestroyOnLoad(go);
|
||||||
|
orig_Execute_WaitAMDaemonReady();
|
||||||
|
SingletonMonoBehaviour<Inohara.Exporter>.instance.LoadCfg();
|
||||||
|
}
|
||||||
|
}
|
20
MU3.Battle/patch_GameEngine.cs
Normal file
20
MU3.Battle/patch_GameEngine.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#pragma warning disable CS0626
|
||||||
|
#pragma warning disable CS0649
|
||||||
|
#pragma warning disable IDE0051
|
||||||
|
#pragma warning disable IDE1006
|
||||||
|
|
||||||
|
using MU3.Game;
|
||||||
|
using MU3.Util;
|
||||||
|
|
||||||
|
namespace MU3.Battle;
|
||||||
|
|
||||||
|
public class patch_GameEngine : GameEngine {
|
||||||
|
private SessionInfo _sessionInfo;
|
||||||
|
|
||||||
|
private extern void orig_applyResultToUserData(SessionResult sessionResult);
|
||||||
|
|
||||||
|
private void applyResultToUserData(SessionResult sessionResult) {
|
||||||
|
SingletonMonoBehaviour<Inohara.Exporter>.instance.Export(sessionResult.battleResult, _sessionInfo);
|
||||||
|
orig_applyResultToUserData(sessionResult);
|
||||||
|
}
|
||||||
|
}
|
18
MU3/patch_Scene_25_Login.cs
Normal file
18
MU3/patch_Scene_25_Login.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#pragma warning disable CS0626
|
||||||
|
#pragma warning disable CS0649
|
||||||
|
#pragma warning disable IDE0051
|
||||||
|
#pragma warning disable IDE1006
|
||||||
|
|
||||||
|
using MU3.Util;
|
||||||
|
|
||||||
|
namespace MU3;
|
||||||
|
|
||||||
|
public class patch_Scene_25_Login : Scene_25_Login {
|
||||||
|
|
||||||
|
private extern void orig_finishAime();
|
||||||
|
|
||||||
|
private void finishAime() {
|
||||||
|
orig_finishAime();
|
||||||
|
SingletonMonoBehaviour<Inohara.Exporter>.instance.Authorize();
|
||||||
|
}
|
||||||
|
}
|
38
README.md
Normal file
38
README.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
## Inohara
|
||||||
|
|
||||||
|
A µ3 score exporter for [Tachi](https://github.com/zkldi/Tachi).
|
||||||
|
|
||||||
|
### Supported versions
|
||||||
|
- 1.39
|
||||||
|
- 1.40
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
#### 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`
|
||||||
|
- 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`)
|
||||||
|
|
||||||
|
#### 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`)
|
||||||
|
|
||||||
|
### 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`.
|
||||||
|
|
||||||
|
### Building
|
||||||
|
Provide your own `Assembly-CSharp.dll` (or `_unpacked`) and `UnityEngine.UI.dll`, then `dotnet restore`, `dotnet build`.
|
22
inohara.cfg.example
Normal file
22
inohara.cfg.example
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[Options]
|
||||||
|
# Whether to enable score submissions
|
||||||
|
Enable = true
|
||||||
|
# Timeout for web requests, in seconds
|
||||||
|
Timeout = 3
|
||||||
|
# Tachi instance base URL
|
||||||
|
BaseUrl =
|
||||||
|
# Tachi status endpoint
|
||||||
|
Status = /api/v1/status
|
||||||
|
# Tachi score import endpoint
|
||||||
|
Import = /ir/direct-manual/import
|
||||||
|
# Display status on-screen (rudimentarily)
|
||||||
|
EnableOSD = false
|
||||||
|
|
||||||
|
[Keys]
|
||||||
|
* = %%TACHI_KEY%%
|
||||||
|
|
||||||
|
# If you have a multi-user setup, you can configure
|
||||||
|
# keys per-profile:
|
||||||
|
# InGameUsername = TachiApiKey
|
||||||
|
#
|
||||||
|
# An * matches everyone else
|
Loading…
Reference in New Issue
Block a user