253 lines
10 KiB
C#
253 lines
10 KiB
C#
using Cryville.Audio;
|
|
using Cryville.Audio.Source;
|
|
using Cryville.Common.Font;
|
|
using Cryville.Common.Logging;
|
|
using Cryville.Common.Unity;
|
|
using Cryville.Common.Unity.UI;
|
|
using Cryville.Crtr.UI;
|
|
using Cryville.Culture;
|
|
using Cryville.Input;
|
|
using Cryville.Input.Unity;
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|
using Cryville.Input.Unity.Android;
|
|
using Cryville.Interop.Java;
|
|
using Cryville.Interop.Java.Unity;
|
|
#endif
|
|
using FFmpeg.AutoGen;
|
|
using Ionic.Zip;
|
|
using Newtonsoft.Json;
|
|
using System;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Xml;
|
|
using System.Xml.Linq;
|
|
using UnityEngine;
|
|
using Logger = Cryville.Common.Logging.Logger;
|
|
using unity = UnityEngine;
|
|
|
|
namespace Cryville.Crtr {
|
|
public static class Game {
|
|
public static string GameDataPath { get; private set; }
|
|
public static string UnityDataPath { get; private set; }
|
|
public static IAudioDeviceManager AudioManager;
|
|
public static IAudioDevice AudioDevice;
|
|
public static AudioClient AudioClient;
|
|
public static SimpleSequencerSource AudioSequencer;
|
|
public static SimpleSequencerSession AudioSession;
|
|
public static InputManager InputManager;
|
|
public static readonly NetworkTaskWorker NetworkTaskWorker = new();
|
|
|
|
public static readonly JsonSerializerSettings GlobalJsonSerializerSettings
|
|
= new() {
|
|
DefaultValueHandling = DefaultValueHandling.Ignore,
|
|
};
|
|
|
|
public static Logger MainLogger { get; private set; }
|
|
static FileStream _logFileStream;
|
|
static StreamLoggerListener _logWriter;
|
|
|
|
static bool _init;
|
|
public static void Init() {
|
|
if (_init) return;
|
|
_init = true;
|
|
|
|
bool _bcflag = new Version(Settings.Default.LastRunVersion) < new Version("0.4");
|
|
if (_bcflag) Settings.Default.Reset();
|
|
|
|
GameDataPath = Settings.Default.GameDataPath;
|
|
UnityDataPath = Application.dataPath;
|
|
|
|
var logPath = Directory.CreateDirectory(Path.Combine(GameDataPath, "logs"));
|
|
_logFileStream = new FileStream(
|
|
Path.Combine(
|
|
logPath.FullName,
|
|
string.Format(
|
|
CultureInfo.InvariantCulture,
|
|
"{0}.log",
|
|
(int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds
|
|
)
|
|
),
|
|
FileMode.Create, FileAccess.Write, FileShare.Read
|
|
);
|
|
_logWriter = new StreamLoggerListener(_logFileStream) { AutoFlush = true };
|
|
MainLogger = new Logger();
|
|
var listener = new InstantLoggerListener();
|
|
listener.Log += MainLogger.Log;
|
|
Common.Shared.Logger.AddListener(listener);
|
|
Input.Shared.Logger.AddListener(listener);
|
|
MainLogger.AddListener(_logWriter);
|
|
Application.logMessageReceivedThreaded += OnInternalLog;
|
|
|
|
MainLogger.Log(1, "Game", "Game Version: {0}", Application.version);
|
|
MainLogger.Log(1, "Game", "Unity Version: {0}", Application.unityVersion);
|
|
MainLogger.Log(1, "Game", "Operating System: {0}, Unity = {1}, Family = {2}", Environment.OSVersion, SystemInfo.operatingSystem, SystemInfo.operatingSystemFamily);
|
|
MainLogger.Log(1, "Game", "Platform: Build = {0}, Unity = {1}", PlatformConfig.Name, Application.platform);
|
|
MainLogger.Log(1, "Game", "Culture: {0}, UI = {1}, System = {2}, Unity = {3}", CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture, CultureInfo.InstalledUICulture, Application.systemLanguage);
|
|
MainLogger.Log(1, "Game", "Device: Model = {0}, Type = {1}", SystemInfo.deviceModel, SystemInfo.deviceType);
|
|
MainLogger.Log(1, "Game", "Graphics: Name = {0}, Type = {1}, Vendor = {2}, Version = {3}", SystemInfo.graphicsDeviceName, SystemInfo.graphicsDeviceType, SystemInfo.graphicsDeviceVendor, SystemInfo.graphicsDeviceVersion);
|
|
MainLogger.Log(1, "Game", "Processor: Count = {0}, Frequency = {1}MHz, Type = {2}", SystemInfo.processorCount, SystemInfo.processorFrequency, SystemInfo.processorType);
|
|
|
|
if (_bcflag) MainLogger.Log(2, "Game", "Reset all settings");
|
|
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|
JavaVMManager.Register(JniInvoke.Instance);
|
|
#endif
|
|
|
|
unity::Input.simulateMouseWithTouches = false;
|
|
var emptyObjectArray = new object[0];
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|
InputManager.HandlerRegistries.Add(typeof(AndroidAccelerometerHandler), emptyObjectArray);
|
|
InputManager.HandlerRegistries.Add(typeof(AndroidAccelerometerUncalibratedHandler), emptyObjectArray);
|
|
InputManager.HandlerRegistries.Add(typeof(AndroidGameRotationVectorHandler), emptyObjectArray);
|
|
InputManager.HandlerRegistries.Add(typeof(AndroidGravityHandler), emptyObjectArray);
|
|
InputManager.HandlerRegistries.Add(typeof(AndroidGyroscopeHandler), emptyObjectArray);
|
|
InputManager.HandlerRegistries.Add(typeof(AndroidLinearAccelerationHandler), emptyObjectArray);
|
|
InputManager.HandlerRegistries.Add(typeof(AndroidMagneticFieldHandler), emptyObjectArray);
|
|
InputManager.HandlerRegistries.Add(typeof(AndroidMagneticFieldUncalibratedHandler), emptyObjectArray);
|
|
InputManager.HandlerRegistries.Add(typeof(AndroidRotationVectorHandler), emptyObjectArray);
|
|
InputManager.HandlerRegistries.Add(typeof(AndroidTouchHandler), emptyObjectArray);
|
|
#endif
|
|
InputManager.HandlerRegistries.Add(typeof(UnityGuiInputHandler<UnityKeyReceiver>), emptyObjectArray);
|
|
InputManager.HandlerRegistries.Add(typeof(UnityGuiInputHandler<UnityMouseReceiver>), emptyObjectArray);
|
|
InputManager.HandlerRegistries.Add(typeof(UnityMouseHandler), emptyObjectArray);
|
|
InputManager.HandlerRegistries.Add(typeof(UnityTouchHandler), emptyObjectArray);
|
|
InputManager = new InputManager();
|
|
|
|
#if UNITY_EDITOR_WIN
|
|
ffmpeg.RootPath = Path.Combine(Application.dataPath, "Plugins", "Windows");
|
|
#elif UNITY_STANDALONE_WIN
|
|
ffmpeg.RootPath = Path.Combine(Application.dataPath, "Plugins", "x86_64");
|
|
#elif UNITY_ANDROID
|
|
ffmpeg.RootPath = "";
|
|
#else
|
|
#error No FFmpeg search path.
|
|
#endif
|
|
|
|
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
|
|
EngineBuilder.Engines.Add(typeof(Audio.Wasapi.MMDeviceEnumeratorWrapper));
|
|
EngineBuilder.Engines.Add(typeof(Audio.WaveformAudio.WaveDeviceManager));
|
|
#elif UNITY_ANDROID
|
|
EngineBuilder.Engines.Add(typeof(Audio.AAudio.AAudioManager));
|
|
EngineBuilder.Engines.Add(typeof(Audio.OpenSLES.Engine));
|
|
#else
|
|
#error No audio engine defined.
|
|
#endif
|
|
while (true) {
|
|
try {
|
|
AudioManager = EngineBuilder.Create();
|
|
if (AudioManager == null) {
|
|
Dialog.Show(null, "Fatal error: Cannot initialize audio engine");
|
|
MainLogger.Log(5, "Audio", "Cannot initialize audio engine");
|
|
}
|
|
else {
|
|
MainLogger.Log(1, "Audio", "Using audio API: {0}", AudioManager.GetType().Namespace);
|
|
AudioDevice = AudioManager.GetDefaultDevice(DataFlow.Out);
|
|
AudioClient = AudioDevice.Connect(AudioDevice.DefaultFormat, AudioDevice.MinimumBufferSize + AudioDevice.BurstSize);
|
|
MainLogger.Log(
|
|
1, "Audio",
|
|
"Audio Output = {{ Name = \"{0}\", BurstSize = {1}, Format = {2}, BufferSize = {3} }}",
|
|
AudioDevice.Name, AudioDevice.BurstSize, AudioClient.Format, AudioClient.BufferSize
|
|
);
|
|
AudioClient.Source = AudioSequencer = new SimpleSequencerSource();
|
|
AudioSession = AudioSequencer.NewSession();
|
|
AudioSequencer.Playing = true;
|
|
AudioClient.Start();
|
|
}
|
|
break;
|
|
}
|
|
catch (Exception ex) {
|
|
Dialog.Show(null, "An error occurred while trying to initialize the recommended audio engine\nTrying to use fallback audio engines");
|
|
MainLogger.Log(4, "Audio", "An error occurred when initializing the audio engine: {0}", ex);
|
|
MainLogger.Log(2, "Audio", "Trying to use fallback audio engines");
|
|
EngineBuilder.Engines.Remove(AudioManager.GetType());
|
|
}
|
|
}
|
|
|
|
var dir = new DirectoryInfo(Path.Combine(Settings.Default.GameDataPath, "charts"));
|
|
if (!dir.Exists || Settings.Default.LastRunVersion != Application.version) {
|
|
Directory.CreateDirectory(dir.FullName);
|
|
var defaultData = Resources.Load<TextAsset>("default");
|
|
using var zip = ZipFile.Read(defaultData.bytes);
|
|
zip.ExtractExistingFile = ExtractExistingFileAction.OverwriteSilently;
|
|
zip.ExtractAll(Settings.Default.GameDataPath);
|
|
}
|
|
|
|
Settings.Default.LastRunVersion = Application.version;
|
|
Settings.Default.Save();
|
|
|
|
MainLogger.Log(1, "UI", "Initializing font manager");
|
|
foreach (var res in Resources.LoadAll<TextAsset>("cldr/common/validity")) {
|
|
IdValidity.Load(LoadXmlDocument(res));
|
|
}
|
|
var metadata = new SupplementalMetadata(LoadXmlDocument("cldr/common/supplemental/supplementalMetadata"));
|
|
var subtags = new LikelySubtags(LoadXmlDocument("cldr/common/supplemental/likelySubtags"), metadata);
|
|
var matcher = new LanguageMatching(LoadXmlDocument("cldr/common/supplemental/languageInfo"), subtags);
|
|
TMPAutoFont.FontMatcher = new FallbackListFontMatcher(matcher, PlatformConfig.FontManager) {
|
|
MapScriptToTypefaces = PlatformConfig.ScriptFontMap
|
|
};
|
|
TMPAutoFont.DefaultShader = Resources.Load<Shader>(PlatformConfig.TextShader);
|
|
|
|
MainLogger.Log(1, "Game", "Initialized");
|
|
}
|
|
|
|
static readonly Encoding _encoding = new UTF8Encoding(false, true);
|
|
static readonly XmlReaderSettings _xmlSettings = new() {
|
|
DtdProcessing = DtdProcessing.Ignore,
|
|
};
|
|
static XDocument LoadXmlDocument(string path) {
|
|
return LoadXmlDocument(Resources.Load<TextAsset>(path));
|
|
}
|
|
static XDocument LoadXmlDocument(TextAsset asset) {
|
|
using var stream = new MemoryStream(_encoding.GetBytes(asset.text));
|
|
using var reader = XmlReader.Create(stream, _xmlSettings);
|
|
return XDocument.Load(reader);
|
|
}
|
|
|
|
static bool _shutdown;
|
|
public static void Shutdown() {
|
|
if (_shutdown) return;
|
|
_shutdown = true;
|
|
|
|
MainLogger.Log(1, "Game", "Shutting down");
|
|
try {
|
|
AudioClient.Dispose();
|
|
AudioSequencer.Dispose();
|
|
AudioDevice.Dispose();
|
|
AudioManager.Dispose();
|
|
}
|
|
catch (Exception ex) {
|
|
LogException("Game", "An error occurred while shutting down", ex);
|
|
}
|
|
finally {
|
|
_logWriter.Dispose();
|
|
_logFileStream.Dispose();
|
|
}
|
|
}
|
|
|
|
public static void LogException(string module, string prefix, Exception ex) {
|
|
MainLogger.Log(4, module, "{0}: {1}", prefix, ex);
|
|
}
|
|
|
|
static void OnInternalLog(string condition, string stackTrace, LogType type) {
|
|
var l = type switch {
|
|
LogType.Log => 1,
|
|
LogType.Assert => 2,
|
|
LogType.Warning => 3,
|
|
LogType.Error or LogType.Exception => 4,
|
|
_ => 1,
|
|
};
|
|
MainLogger.Log(l, "Internal", "{0}\n{1}", condition, stackTrace);
|
|
}
|
|
|
|
public static void SuspendBackgroundTasks() {
|
|
NetworkTaskWorker.SuspendBackgroundTasks();
|
|
Dialog.Suppress();
|
|
}
|
|
public static void ResumeBackgroundTasks() {
|
|
Dialog.Release();
|
|
NetworkTaskWorker.ResumeBackgroundTasks();
|
|
}
|
|
}
|
|
}
|