256 lines
8.8 KiB
C#
256 lines
8.8 KiB
C#
|
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);
|
|||
|
}
|
|||
|
}
|