feat: polish VR gameplay and sync tools

This commit is contained in:
jongjae0305
2026-05-28 19:01:20 +09:00
parent ee34d79a66
commit 03105a4f85
50 changed files with 4986 additions and 328 deletions
+93 -20
View File
@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.Networking;
@@ -25,14 +26,20 @@ public class NasPublisher : MonoBehaviour
private void LoadConfig()
{
string path = Path.Combine(Application.streamingAssetsPath, "nas_config.json");
if (!File.Exists(path)) { Debug.LogWarning("[NasPublisher] nas_config.json not found: " + path); return; }
if (!File.Exists(path))
{
NormalizeSettings();
return;
}
var cfg = JsonUtility.FromJson<NasConfig>(File.ReadAllText(path));
if (cfg == null) return;
_password = cfg.password ?? "";
if (!string.IsNullOrEmpty(cfg.host)) nasBaseUrl = cfg.host;
if (!string.IsNullOrEmpty(cfg.account)) nasAccount = cfg.account;
if (!string.IsNullOrEmpty(cfg.rootPath)) nasRootPath = cfg.rootPath;
if (!string.IsNullOrEmpty(cfg.staticUrl)) staticBaseUrl = cfg.staticUrl;
if (!string.IsNullOrWhiteSpace(cfg.host)) nasBaseUrl = cfg.host.Trim();
if (!string.IsNullOrWhiteSpace(cfg.account)) nasAccount = cfg.account.Trim();
if (!string.IsNullOrWhiteSpace(cfg.rootPath)) nasRootPath = cfg.rootPath.Trim();
if (!string.IsNullOrWhiteSpace(cfg.staticUrl)) staticBaseUrl = cfg.staticUrl.Trim();
NormalizeSettings();
}
[Serializable] private class NasConfig
@@ -52,6 +59,8 @@ public class NasPublisher : MonoBehaviour
Action onComplete,
Action<string> onError)
{
NormalizeSettings();
bool failed = false;
void OnErr(string e) { onError?.Invoke(e); failed = true; }
@@ -92,31 +101,53 @@ public class NasPublisher : MonoBehaviour
private IEnumerator Login(Action<string> onError)
{
if (string.IsNullOrWhiteSpace(_password))
{
onError?.Invoke("NAS password missing. Create Assets/StreamingAssets/nas_config.json with host, account, rootPath, staticUrl, and password.");
yield break;
}
string url = $"{nasBaseUrl}/webapi/auth.cgi" +
$"?api=SYNO.API.Auth&version=6&method=login" +
$"&account={UnityWebRequest.EscapeURL(nasAccount)}" +
$"&passwd={UnityWebRequest.EscapeURL(_password)}" +
$"&session=FileStation&format=sid&enable_syno_token=yes";
using var req = UnityWebRequest.Get(url);
yield return req.SendWebRequest();
if (req.result != UnityWebRequest.Result.Success)
UnityWebRequest req;
try
{
onError?.Invoke($"DSM login failed: {req.error}");
req = UnityWebRequest.Get(url);
}
catch (UriFormatException e)
{
onError?.Invoke($"DSM login URL invalid: '{nasBaseUrl}' — {e.Message}");
yield break;
}
string resp = req.downloadHandler.text;
_sid = ParseJsonString(resp, "sid");
_synoToken = ParseJsonString(resp, "synotoken");
string resp;
using (req)
{
yield return req.SendWebRequest();
if (req.result != UnityWebRequest.Result.Success)
{
onError?.Invoke($"DSM login failed: {req.error}");
yield break;
}
resp = req.downloadHandler.text;
_sid = ParseJsonString(resp, "sid");
_synoToken = ParseJsonString(resp, "synotoken");
}
if (string.IsNullOrEmpty(_sid))
onError?.Invoke("DSM sid parse failed — check credentials.");
onError?.Invoke($"DSM sid parse failed. Check NAS account/password/permissions. Response: {Shorten(resp)}");
}
private IEnumerator Logout()
{
NormalizeSettings();
string url = $"{nasBaseUrl}/webapi/auth.cgi" +
$"?api=SYNO.API.Auth&version=1&method=logout&session=FileStation&_sid={_sid}";
using var req = UnityWebRequest.Get(url);
@@ -133,6 +164,8 @@ public class NasPublisher : MonoBehaviour
private IEnumerator UploadBytes(byte[] bytes, string fileName,
string nasFolder, Action<string> onError)
{
NormalizeSettings();
string uploadUrl = $"{nasBaseUrl}/webapi/entry.cgi" +
$"?api=SYNO.FileStation.Upload&version=2&method=upload" +
$"&_sid={UnityWebRequest.EscapeURL(_sid)}";
@@ -179,6 +212,8 @@ public class NasPublisher : MonoBehaviour
private IEnumerator PatchSongsJson(SongInfo newSong, Action<string> onError)
{
NormalizeSettings();
SongsList list = null;
using (var req = UnityWebRequest.Get($"{staticBaseUrl}/songs.json"))
@@ -201,12 +236,16 @@ public class NasPublisher : MonoBehaviour
private static string ParseJsonString(string json, string key)
{
string search = $"\"{key}\":\"";
int start = json.IndexOf(search, StringComparison.Ordinal);
if (start < 0) return null;
start += search.Length;
int end = json.IndexOf('"', start);
return end > start ? json.Substring(start, end - start) : null;
if (string.IsNullOrEmpty(json) || string.IsNullOrEmpty(key))
return null;
Match match = Regex.Match(
json,
$"\"{Regex.Escape(key)}\"\\s*:\\s*\"(?<value>(?:\\\\.|[^\"])*)\"");
return match.Success
? Regex.Unescape(match.Groups["value"].Value)
: null;
}
private static void AssignMapFile(SongInfo song, string diff, string fileName)
@@ -214,4 +253,38 @@ public class NasPublisher : MonoBehaviour
var info = song.difficulties.Get(diff);
if (info != null) info.mapFile = $"maps/{fileName}";
}
private void OnValidate()
{
NormalizeSettings();
}
private void NormalizeSettings()
{
nasBaseUrl = NormalizeBaseUrl(nasBaseUrl);
staticBaseUrl = NormalizeBaseUrl(staticBaseUrl);
nasAccount = nasAccount?.Trim() ?? "";
nasRootPath = NormalizeRootPath(nasRootPath);
}
private static string NormalizeBaseUrl(string value)
{
return (value ?? "").Trim().TrimEnd('/');
}
private static string NormalizeRootPath(string value)
{
value = (value ?? "").Trim().Replace('\\', '/');
if (string.IsNullOrEmpty(value))
return "/";
return value.StartsWith("/") ? value.TrimEnd('/') : "/" + value.TrimEnd('/');
}
private static string Shorten(string value, int maxLength = 240)
{
if (string.IsNullOrEmpty(value) || value.Length <= maxLength)
return value ?? "";
return value.Substring(0, maxLength) + "...";
}
}