using System; using System.Collections; using System.IO; using System.IO.Compression; using System.Reflection; using UnityEngine; using UnityEngine.Networking; using Newtonsoft.Json.Linq; public class UpdateHandler : MonoBehaviour { /// Set by harmony/bepinex public static string PluginVersion = "1.0.0"; [Header("Message")] public PopupMessage PanelMessage; [Header("UpdateChecker")] public UpdateChecker UpdateChecker; [Header("UpdateProgress")] public ProgressSlider UpdateProgress; [Header("UpdateAvailable")] public UpdateAvailable UpdatePanel; [Header("Settings")] public SettingsPanel PanelSettings; public const string REPO_DEFAULT = "https://git.japari.cafe/api/v1/repos/Vorked/VorkedTranslationPack"; //playerprefs keys public const string TRANSLATION_VER = "translationVersion"; public const string TRANSLATOR_VER = "KF3TLVersion"; public const string REPO_CUSTOM_URL = "customRepositoryUrl"; public const string REPO_CUSTOM_NAME = "customRepository"; private int _idleFrames = 0; public static void Create() { #if UNITY_EDITOR //var prefab = AssetBundle.LoadFromFile(Application.dataPath + "/AssetBundles/UpdateHandler"); var prefab = Resources.Load("UpdateHandler"); Instantiate(prefab); #else var prefab = AssetBundle.LoadFromMemory(LoadResource("UpdateHandler.asset")); #endif Instantiate(prefab.LoadAsset("UpdateHandler")); prefab.Unload(false); } public static byte[] LoadResource(string fileName) { Assembly asm = Assembly.GetExecutingAssembly(); fileName = asm.GetName().Name + "." + fileName; var bytes = new byte[0]; using (Stream resFilestream = asm.GetManifestResourceStream(fileName)) { if (resFilestream == null) return null; byte[] byteArray = new byte[resFilestream.Length]; resFilestream.Read(byteArray, 0, byteArray.Length); bytes = byteArray; } Console.WriteLine(bytes.Length); return bytes; } public IEnumerator Start() { yield return 0; transform.SetAsLastSibling(); UpdateChecker.SetActive(true); yield return CheckKF3TLUpdate(); yield return CheckTranslationUpdate(); yield return new WaitForSeconds(5); UpdateChecker.SetActive(false); } private void Update() { for (int i = 0; i < transform.childCount; i++) { if (transform.GetChild(i).gameObject.activeSelf) { _idleFrames = 0; return; } } if (_idleFrames >= 5) { Destroy(this.gameObject); } else { _idleFrames++; } } public IEnumerator CheckKF3TLUpdate() { Debug.Log("Checking KF3TL Update"); string currentVersion = PlayerPrefs.GetString(TRANSLATOR_VER, "undefined"); var firstRun = currentVersion == "undefined"; BranchCheckResult result = null; yield return GetRepoVersion("https://api.github.com/repos/Vorked/KF3TL/branches/main", data => { result = data; }); if (result == null || !result.Success) { UpdateChecker.SetActive(false); if (result == null) { yield return SetAwaitableMessage("Failed to get response from server", 5); } else { yield return SetAwaitableMessage("Failed to check for update!\nError: " + result.Error, 5); } yield break; } Debug.Log("local version: " + currentVersion); Debug.Log("server version: " + result.Version); if (firstRun) { yield return SetAwaitableMessage("First run detected. Press Ignore on next prompt if you have the newest version of KF3TL", 150); } if (result.Version != currentVersion) { var panel = Instantiate(UpdatePanel, transform); panel.Init("KF3TL Plugin Update", currentVersion, result.Version); panel.OnDownloadAction = () => { StartCoroutine(UpdateKF3TL(result.Version, () => Destroy(panel.gameObject))); panel.gameObject.SetActive(false); }; panel.OnIgnoreAction = () => { Destroy(panel.gameObject); }; if (firstRun) { panel.OnIgnorePersistentAction = () => { PlayerPrefs.SetString(TRANSLATOR_VER, result.Version); PlayerPrefs.Save(); Destroy(panel.gameObject); }; } else { panel.IgnorePersistentButton.gameObject.SetActive(false); } while (panel != null) yield return 0; } } public IEnumerator UpdateKF3TL(string version, System.Action onComplete) { byte[] bytes = null; yield return DownloadBytes("https://api.github.com/repos/Vorked/KF3TL/zipball/main", (newBytes) => { bytes = newBytes; }, UpdateProgress.SetUpdateProgress ); UpdateProgress.SetUpdateProgress(1); if (bytes.Length <= 0) { yield return SetAwaitableMessage("Failed to download the update!\nDownload new version from https://github.com/Vorked/KF3TL", 15); yield break; } var path = Application.dataPath + "/../" + "KF3TL.zip"; File.WriteAllBytes(path, bytes); PlayerPrefs.SetString(TRANSLATOR_VER, version); PlayerPrefs.Save(); UpdateProgress.SetActive(false); var message = Instantiate(PanelMessage, transform).Init($"New version downloaded as KF3TL.zip. Please close the game and install it.", 150); message.OnFinishedAction = () => { System.Diagnostics.Process.Start("explorer.exe", "/select," + path.Replace("/", "\\")); onComplete?.Invoke(); }; } public IEnumerator CheckTranslationUpdate() { Debug.Log("Checking Translation Update"); string repositoryUrl = PlayerPrefs.GetInt(REPO_CUSTOM_NAME, 0) == 0 ? REPO_DEFAULT : PlayerPrefs.GetString(REPO_CUSTOM_URL, REPO_DEFAULT); string currentVersion = PlayerPrefs.GetString(TRANSLATION_VER, "undefined"); BranchCheckResult result = null; yield return GetRepoVersion(repositoryUrl + "/branches/master", data => { result = data; }); if (result == null || !result.Success) { UpdateChecker.SetActive(false); if (result == null) { yield return SetAwaitableMessage("Failed to get response from server", 5); } else { yield return SetAwaitableMessage("Failed to check for update!\nError: " + result.Error, 5); } yield break; } Debug.Log("local version: " + currentVersion); Debug.Log("server version: " + result.Version); if (result.Version != currentVersion) { var panel = Instantiate(UpdatePanel, transform); panel.Init("Translation pack", currentVersion, result.Version); panel.OnDownloadAction = () => { StartCoroutine(UpdateTranslation(result.Version, () => Destroy(panel.gameObject))); panel.gameObject.SetActive(false); }; panel.OnIgnoreAction = () => { Destroy(panel.gameObject); }; panel.OnIgnorePersistentAction = () => { PlayerPrefs.SetString(TRANSLATION_VER, result.Version); PlayerPrefs.Save(); Destroy(panel.gameObject); }; do yield return 0; while (panel != null); } } public IEnumerator UpdateTranslation(string version, System.Action onComplete) { byte[] bytes = null; string repositoryUrl = PlayerPrefs.GetInt(REPO_CUSTOM_NAME, 0) == 0 ? REPO_DEFAULT : PlayerPrefs.GetString(REPO_CUSTOM_URL, REPO_DEFAULT); yield return DownloadBytes(repositoryUrl + "/archive/master.zip", (newBytes) => { bytes = newBytes; }, UpdateProgress.SetUpdateProgress ); UpdateProgress.SetUpdateProgress(1); if (bytes.Length <= 0) { yield return SetAwaitableMessage("Failed to download the update!\nCheck your settings.", 5); yield break; } try { using (var compressedFileStream = new MemoryStream(bytes)) { using (var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Read, false)) { foreach (var file in zipArchive.Entries) { if (file.Name != "") { //remove repo name from file path var outFilePath = file.FullName.Split(new[] { '/' }, 2)[1]; Directory.CreateDirectory(Path.GetDirectoryName(outFilePath)); file.ExtractToFile(outFilePath, true); } } } } } catch (System.Exception e) { Instantiate(PanelMessage, transform).Init("Failed to install the update!\nError:" + e.Message, 5); yield break; } PlayerPrefs.SetString(TRANSLATION_VER, version); PlayerPrefs.Save(); UpdateProgress.SetActive(false); var message = Instantiate(PanelMessage, transform).Init("Update completed! Please restart the game to apply it.", 5); message.OnFinishedAction = onComplete; } private IEnumerator GetRepoVersion(string url, System.Action callback) { BranchCheckResult result = null; yield return DownloadText(url, (json) => { try { if (string.IsNullOrEmpty(json)) { throw new System.Exception("No response from " + url); } result = new BranchCheckResult(); var newData = JObject.Parse(json).ToObject(); result.Version = newData.commit.timestamp /* gitea */ ?? newData.commit.commit.author.date; //github result.Success = true; } catch (System.Exception e) { result.Error = e.Message; result.Success = false; } } ); callback(result); } private IEnumerator SetAwaitableMessage(string message, float time) { var msg = Instantiate(PanelMessage, transform).Init(message, time); do yield return 0; while (msg != null); } public void OpenSettingsDetached() { if (PanelSettings.gameObject.activeSelf) return; StartCoroutine(OpenSettings()); } private IEnumerator OpenSettings() { PanelSettings.Init(PluginVersion); do yield return 0; while (PanelSettings.gameObject.activeSelf); } public static IEnumerator DownloadBytes(string url, System.Action callback, System.Action onProgressChanged = null) { UnityWebRequest www = UnityWebRequest.Get(url); var request = www.SendWebRequest(); while (!request.isDone) { onProgressChanged?.Invoke(request.progress); yield return 0; } if (www.result != UnityWebRequest.Result.Success) { Debug.Log(url); Debug.Log(www.error); callback(null); } else { callback(www.downloadHandler.data); } } public static IEnumerator DownloadText(string url, System.Action callback) { UnityWebRequest www = UnityWebRequest.Get(url); yield return www.SendWebRequest(); if (www.result != UnityWebRequest.Result.Success) { Debug.Log(url); Debug.Log(www.error); callback(null); } else { callback(www.downloadHandler.text); } } [System.Serializable] public class BranchInfo { public CommitInfo commit; [System.Serializable] public class CommitInfo { public string id; public string sha; public string timestamp; public CommitInfo2 commit; [System.Serializable] public class CommitInfo2 { public AuthorInfo author; [System.Serializable] public class AuthorInfo { public string date; } } } } public class BranchCheckResult { public bool Success; public string Version; public string Error; } }