diff --git a/Assets/Cryville/Crtr/Game.cs b/Assets/Cryville/Crtr/Game.cs index 3a6ab21..f6148ed 100644 --- a/Assets/Cryville/Crtr/Game.cs +++ b/Assets/Cryville/Crtr/Game.cs @@ -18,10 +18,8 @@ using unity = UnityEngine; namespace Cryville.Crtr { public static class Game { - public static string GameDataPath { - get; - private set; - } + public static string GameDataPath { get; private set; } + public static string UnityDataPath { get; private set; } public static IAudioDeviceManager AudioManager; public static AudioClient AudioClient; public static SimpleSequencerSource AudioSequencer; @@ -52,6 +50,7 @@ namespace Cryville.Crtr { if (_bcflag) Logger.Log("main", 2, "Game", "Reset all settings"); GameDataPath = Settings.Default.GameDataPath; + UnityDataPath = Application.dataPath; unity::Input.simulateMouseWithTouches = false; var emptyObjectArray = new object[0]; diff --git a/Assets/Cryville/Crtr/Network/UpdateChecker.cs b/Assets/Cryville/Crtr/Network/UpdateChecker.cs index 23387f2..3a2eb0e 100644 --- a/Assets/Cryville/Crtr/Network/UpdateChecker.cs +++ b/Assets/Cryville/Crtr/Network/UpdateChecker.cs @@ -1,8 +1,10 @@ +using Cryville.Common; using Cryville.Common.Network.Http11; using Cryville.Common.Unity; using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading; @@ -57,64 +59,117 @@ namespace Cryville.Crtr.Network { Dialog.Show(null, string.Format("You are playing an unknown version of Cosmo Resona: {0}\nThe latest version is: {1}", _currentVersion, latestVersion.name)); return; } - if (latestVersion.name != _currentVersion) { - var latestResources = latestVersion.platforms[PlatformConfig.Name].resources; - VersionResourceInfo fullPackage = null; - if (latestResources != null) { - fullPackage = (from r in latestResources where r.upstream == null select r).SingleOrDefault(); + if (latestVersion.name == _currentVersion) return; + var latestResources = latestVersion.platforms[PlatformConfig.Name].resources; + VersionResourceInfo fullPackage = null; + if (latestResources != null) { + fullPackage = (from r in latestResources where r.upstream == null select r).SingleOrDefault(); + } + if (fullPackage == null) { + Dialog.Show(null, string.Format("A new version is present: {0}\nUpdate is not available.", latestVersion.name)); + return; + } + long totalDiffSize = 0; + int searchIndex = versionIndex[latestVersion.name], targetIndex = versionIndex[currentVersion.name]; + var stream = new List(); + while (searchIndex != targetIndex) { + var searchVersion = availableVersions[searchIndex]; + VersionResourceInfo matchedUpstream = null; + var resources = searchVersion.platforms[PlatformConfig.Name].resources; + if (resources == null) { + totalDiffSize = 0; + break; } - if (fullPackage == null) { - Dialog.Show(null, string.Format("A new version is present: {0}\nUpdate is not available.", latestVersion.name)); - return; + foreach (var r in resources) { + if (r.upstream == null) continue; + var upstreamIndex = versionIndex[r.upstream]; + if (upstreamIndex >= targetIndex && upstreamIndex < searchIndex) { + matchedUpstream = r; + searchIndex = upstreamIndex; + } } - long totalDiffSize = 0; - int searchIndex = versionIndex[latestVersion.name], targetIndex = versionIndex[currentVersion.name]; - while (searchIndex != targetIndex) { - var searchVersion = availableVersions[searchIndex]; - VersionResourceInfo matchedUpstream = null; - var resources = searchVersion.platforms[PlatformConfig.Name].resources; - if (resources == null) { + if (matchedUpstream != null) { + if (matchedUpstream.external) { + totalDiffSize = 0; + Dialog.ShowAndWait("An error occurred while checking for update.\nPlease report this to the developers."); + throw new InvalidOperationException("Diff package is external, which is not expected"); + } + stream.Add(matchedUpstream); + totalDiffSize += matchedUpstream.size; + if (totalDiffSize >= fullPackage.size) { totalDiffSize = 0; break; } - foreach (var r in resources) { - if (r.upstream == null) continue; - var upstreamIndex = versionIndex[r.upstream]; - if (upstreamIndex >= targetIndex && upstreamIndex < searchIndex) { - matchedUpstream = r; - searchIndex = upstreamIndex; - } - } - if (matchedUpstream != null) { - if (matchedUpstream.external) { - totalDiffSize = 0; - Dialog.ShowAndWait("An error occurred while checking for update.\nPlease report this to the developers."); - Logger.Log("main", 4, "Network", "Diff package is external, which is not expected"); - break; - } - totalDiffSize += matchedUpstream.size; - if (totalDiffSize >= fullPackage.size) { - totalDiffSize = 0; - break; - } - } - else { - totalDiffSize = 0; - break; - } - } - if (totalDiffSize == 0 || totalDiffSize >= fullPackage.size) { - // TODO Check if external - if (Dialog.ShowAndWait(string.Format("A new version is available: {0}\nYou have to download the full package.\nOpen the download link now?", latestVersion.name), "Yes", "No") == 0) { - UrlOpener.OpenThreaded(fullPackage.url); - } } else { - Dialog.ShowAndWait(string.Format("A new version is available: {0}\nDo you want to update?", latestVersion.name), "Yes", "No"); + totalDiffSize = 0; + break; + } + } + if (totalDiffSize == 0) { + // TODO Check if external + if (Dialog.ShowAndWait(string.Format("A new version is available: {0}\nYou have to download the full package.\nOpen the download link now?", latestVersion.name), "Yes", "No") == 0) { + UrlOpener.OpenThreaded(fullPackage.url); + } + } + else { + if (Dialog.ShowAndWait(string.Format("A new version is available: {0}\nDo you want to update?", latestVersion.name), "Yes", "No") == 0) { + var diffPaths = new List(); + var updateDir = Directory.CreateDirectory(Path.Combine(Game.GameDataPath, "update")).FullName; + foreach (var diff in stream) { + string path = Path.Combine(updateDir, StringUtils.EscapeFileName(diff.upstream)); + diffPaths.Add(path); + Download(diff, path); + } + while (Dialog.ShowAndWait("The new version has been downloaded.\nUpdate now?\n(The game will be shut down)", "OK", "Later") == 1) + Thread.Sleep(60000); + ExecuteUpdate(diffPaths); } } - Logger.Log("main", 0, "Network", "Update checker exited"); } + void Download(VersionResourceInfo diff, string path) { + var uri = new Uri(diff.url); + using (var client = new Https11Client(uri)) { + client.Connect(); + using (var response = client.Request("GET", uri)) { + var data = response.MessageBody.ReadToEnd(); + using (var file = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None)) { + file.Write(data); + } + } + } + } + void ExecuteUpdate(List diffPaths) { +#if UNITY_EDITOR + Dialog.Show(null, "Could not update the game in editor"); +#elif UNITY_STANDALONE_WIN + var gameDir = new DirectoryInfo(Game.UnityDataPath).Parent; + var exe = new FileInfo(Path.Combine(gameDir.FullName, System.Diagnostics.Process.GetCurrentProcess().ProcessName + ".exe")); + var updateDir = Path.Combine(Game.GameDataPath, "update"); + File.Copy(Path.Combine(Game.UnityDataPath, "Plugins", "x86_64", "hpatchz.dll"), Path.Combine(updateDir, "hpatchz.exe"), true); + var batchPath = Path.Combine(updateDir, "update.bat"); + var oldPath = Path.Combine(updateDir, "old"); + var newPath = Path.Combine(updateDir, "new"); + using (var batch = new StreamWriter(batchPath, false, new UTF8Encoding(false))) { + batch.WriteLine("@echo off\r\ntitle [Updater] Cosmo Resona\r\necho Waiting for the game to shut down...\r\n:pending\r\ntasklist /fi \"ImageName eq {0}\" | find /i \"{0}\" >nul\r\nif \"%ERRORLEVEL%\" == \"0\" goto pending\r\necho Copying old data...\r\nrmdir /s /q {2}\r\nxcopy \"{1}\" \"{2}\" /s /e /h /i /q /y\r\nif not \"%ERRORLEVEL%\" == \"0\" goto error", exe.Name, gameDir, oldPath); + for (int i = 0; i < diffPaths.Count; i++) { + string path = diffPaths[i]; + batch.WriteLine("echo Applying patch... ({0}/{1})\r\nhpatchz -f \"{2}\" \"{3}\" \"{4}\"\r\nif not \"%ERRORLEVEL%\" == \"0\" goto error\r\nrmdir /s /q \"{2}\"\r\nmove /y \"{4}\" \"{2}\"\r\nif not \"%ERRORLEVEL%\" == \"0\" goto error", i + 1, diffPaths.Count, oldPath, path, newPath); + } + batch.WriteLine("echo Copying new data...\r\nrmdir /s /q {1}\r\nmove /y \"{0}\" \"{1}\"\r\nif not \"%ERRORLEVEL%\" == \"0\" goto error\r\nrmdir /s /q \"{2}\"\r\necho Update succeeded\r\npause\r\nexit\r\n:error\r\necho Update failed\r\npause\r\nexit", oldPath, gameDir, updateDir); + } + ShellExecute(IntPtr.Zero, "open", batchPath, null, Path.Combine(updateDir), 1); + _shutdown = true; +#elif UNITY_ANDROID + // TODO +#else +#error No update logic for the selected platform. +#endif + } +#if UNITY_STANDALONE_WIN && !UNITY_EDITOR + [DllImport("shell32.dll")] + static extern int ShellExecute(IntPtr hwnd, [MarshalAs(UnmanagedType.LPStr)] string lpOperation, [MarshalAs(UnmanagedType.LPStr)] string lpFile, [MarshalAs(UnmanagedType.LPStr)] string lpParameters, [MarshalAs(UnmanagedType.LPStr)] string lpDirectory, int nShowCmd); +#endif class VersionInfo { [JsonRequired] public string name; diff --git a/Assets/Plugins/Windows/hpatchz.dll b/Assets/Plugins/Windows/hpatchz.dll new file mode 100644 index 0000000..386bb0e Binary files /dev/null and b/Assets/Plugins/Windows/hpatchz.dll differ diff --git a/Assets/Plugins/Windows/hpatchz.dll.meta b/Assets/Plugins/Windows/hpatchz.dll.meta new file mode 100644 index 0000000..6af7b78 --- /dev/null +++ b/Assets/Plugins/Windows/hpatchz.dll.meta @@ -0,0 +1,70 @@ +fileFormatVersion: 2 +guid: 5caa62ca31e13404ea507d8b08b57ea8 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux64: 0 + Exclude OSXUniversal: 0 + Exclude Win: 0 + Exclude Win64: 0 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 1 + settings: + CPU: x86 + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: x86_64 + userData: + assetBundleName: + assetBundleVariant: