Skip to content

AutoUpdate #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions installer/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"win32": "win",
"darwin": "macos",
}[sys.platform]
BEPINEX = f"733/BepInEx-Unity.IL2CPP-{OS}-x64-6.0.0-be.733%2B995f049"
POLYMOD = "https://github.com/PolyModdingTeam/PolyMod/releases/latest/download/PolyMod.dll"
BEPINEX = "https://polymod.dev/data/bepinex.txt"
POLYMOD = "https://api.github.com/repos/PolyModdingTeam/PolyMod/releases"


def resource_path(path):
Expand Down Expand Up @@ -54,6 +54,7 @@ def prepare(target):
return
path_entry.configure(state=customtkinter.DISABLED)
browse_button.configure(state=customtkinter.DISABLED)
prerelease_checkbox.destroy()
install_button.destroy()
uninstall_button.destroy()
progress_bar = customtkinter.CTkProgressBar(app, determinate_speed=50 / 2)
Expand All @@ -65,13 +66,16 @@ def prepare(target):
def install(path):
to_zip(
requests.get(
f"https://builds.bepinex.dev/projects/bepinex_be/{BEPINEX}.zip"
requests.get(BEPINEX).text.strip().replace("{os}", OS)
)
).extractall(path)
progress_bar.step()

for release in requests.get(POLYMOD).json():
if release["prerelease"] and not prerelease_checkbox.get(): continue
latest = release
open(path + "/BepInEx/plugins/PolyMod.dll", "wb").write(
requests.get(POLYMOD).content
requests.get(latest["assets"][0]["browser_download_url"]).content
)
progress_bar.step()

Expand Down Expand Up @@ -133,14 +137,17 @@ def quit():
app, placeholder_text="Game path", width=228)
browse_button = customtkinter.CTkButton(
app, text="Browse", command=browse, width=1)
prerelease_checkbox = customtkinter.CTkCheckBox(
app, text="Prerelease", width=1)
install_button = customtkinter.CTkButton(
app, text="Install", command=lambda: prepare(install))
uninstall_button = customtkinter.CTkButton(
app, text="Uninstall", command=lambda: prepare(uninstall))

path_entry.grid(column=0, row=0, padx=5, pady=5)
browse_button.grid(column=1, row=0, padx=(0, 5), pady=5)
install_button.grid(column=0, row=1, columnspan=2, padx=5, pady=5)
uninstall_button.grid(column=0, row=2, columnspan=2, padx=5, pady=5)
prerelease_checkbox.grid(column=0, row=1, columnspan=2, padx=5, pady=5)
install_button.grid(column=0, row=2, columnspan=2, padx=5, pady=5)
uninstall_button.grid(column=0, row=3, columnspan=2, padx=5, pady=5)

app.mainloop()
20 changes: 20 additions & 0 deletions resources/localization.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,26 @@
"Elyrion": "πȱ∫ỹmȱδ ƒƒƒƒƒƒƒ ŋȱŧ ȱrrȱ #₺rr∑ŋŧ ƒƒƒƒƒƒƒ ỹ maỹ ŋȱŧ ~ȱr§ #ȱrr∑#ŧ∫ỹ!",
"German (Germany)": "Diese Version von PolyMod ist nicht für die aktuelle Version der Anwendung ausgelegt und könnte nicht funktionieren!"
},
"polymod_debug": {
"English": "Debug",
"Russian": "Дебаг"
},
"polymod_autoupdate": {
"English": "Auto-update",
"Russian": "Автообновление"
},
"polymod_autoupdate_alpha": {
"English": "Include alphas",
"Russian": "Include alphas"
},
"polymod_autoupdate_description": {
"English": "New update available!",
"Russian": "Доступно новое обновление!"
},
"polymod_autoupdate_update": {
"English": "Update",
"Russian": "Обновить"
},
"polymod_hub_config": {
"English": "CONFIG",
"Russian": "КОНФИГ"
Expand Down
162 changes: 162 additions & 0 deletions src/Managers/AutoUpdate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
using System.Diagnostics;
using System.IO.Compression;
using System.Text.Json;
using HarmonyLib;
using UnityEngine;

namespace PolyMod.Managers;

internal static class AutoUpdate
{
[HarmonyPostfix]
[HarmonyPatch(typeof(StartScreen), nameof(StartScreen.Start))]
private static void StartScreen_Start()
{
if (!Plugin.config.autoUpdate) return;
if (Environment.GetEnvironmentVariable("WINEPREFIX") != null)
{
Plugin.logger.LogError("Autoupdate is not supported on Wine!");
return;
}
HttpClient client = new();
client.DefaultRequestHeaders.Add("User-Agent", "PolyMod");
try
{
var json = JsonDocument.Parse(
client.GetAsync("https://api.github.com/repos/PolyModdingTeam/PolyMod/releases").UnwrapAsync()
.Content.ReadAsStringAsync().UnwrapAsync()
);
JsonElement? latest = null;
for (int i = 0; i < json.RootElement.GetArrayLength(); i++)
{
var release = json.RootElement[i];
if (release.GetProperty("prerelease").GetBoolean() && !Plugin.config.updatePrerelease) continue;
latest = release;
break;
}
string newVersion = latest?.GetProperty("tag_name").GetString()!.TrimStart('v')!;
if (newVersion.IsVersionOlderOrEqual(Plugin.VERSION)) return;
string os = Application.platform switch
{
RuntimePlatform.WindowsPlayer => "win",
RuntimePlatform.LinuxPlayer => "linux",
RuntimePlatform.OSXPlayer => "macos",
_ => "unknown",
};
if (os == "unknown")
{
Plugin.logger.LogError("Unsupported platform for autoupdate!");
return;
}
string bepinex_url = client
.GetAsync("https://polymod.dev/data/bepinex.txt").UnwrapAsync()
.Content.ReadAsStringAsync().UnwrapAsync()
.Replace("{os}", os);
void Update()
{
Time.timeScale = 0;
File.WriteAllBytes(
Path.Combine(Plugin.BASE_PATH, "PolyMod.new.dll"),
client.GetAsync(latest?.GetProperty("assets")[0].GetProperty("browser_download_url").GetString()!).UnwrapAsync()
.Content.ReadAsByteArrayAsync().UnwrapAsync()
);
using ZipArchive bepinex = new(client.GetAsync(bepinex_url).UnwrapAsync().Content.ReadAsStream());
bepinex.ExtractToDirectory(Path.Combine(Plugin.BASE_PATH, "New"), overwriteFiles: true);
ProcessStartInfo info = new()
{
WorkingDirectory = Path.Combine(Plugin.BASE_PATH),
CreateNoWindow = true,
};
if (Application.platform == RuntimePlatform.WindowsPlayer)
{
string batchPath = Path.Combine(Plugin.BASE_PATH, "update.bat");
File.WriteAllText(batchPath, $@"
@echo off
echo Waiting for Polytopia.exe to exit...
:waitloop
tasklist | findstr /I ""Polytopia.exe"" >nul
if not errorlevel 1 (
timeout /T 1 >nul
goto waitloop
)

echo Updating...
robocopy ""New"" . /E /MOVE /NFL /NDL /NJH /NJS /NP >nul
rmdir /S /Q ""New""
del /F /Q ""BepInEx\plugins\PolyMod.dll""
move /Y ""PolyMod.new.dll"" ""BepInEx\plugins\PolyMod.dll""

echo Launching game...
start steam://rungameid/874390
timeout /T 3 /NOBREAK >nul
exit
");
info.FileName = "cmd.exe";
info.Arguments = $"/C start \"\" \"{batchPath}\"";
info.WorkingDirectory = Plugin.BASE_PATH;
info.CreateNoWindow = true;
info.UseShellExecute = false;
}
if (Application.platform == RuntimePlatform.LinuxPlayer || Application.platform == RuntimePlatform.OSXPlayer)
{
string bashPath = Path.Combine(Plugin.BASE_PATH, "update.sh");
File.WriteAllText(bashPath, $@"
#!/bin/bash

echo ""Waiting for Polytopia to exit...""
while pgrep -x ""Polytopia"" > /dev/null; do
sleep 1
done

echo ""Updating...""
mv New/* . && rm -rf New
rm -f BepInEx/plugins/PolyMod.dll
mv -f PolyMod.new.dll BepInEx/plugins/PolyMod.dll

echo ""Launching game...""
xdg-open steam://rungameid/874390 &

sleep 3
exit 0
");

System.Diagnostics.Process chmod = new System.Diagnostics.Process();
chmod.StartInfo.FileName = "chmod";
chmod.StartInfo.Arguments = $"+x \"{bashPath}\"";
chmod.StartInfo.UseShellExecute = false;
chmod.StartInfo.CreateNoWindow = true;
chmod.Start();
chmod.WaitForExit();

info.FileName = "/bin/bash";
info.Arguments = $"\"{bashPath}\"";
info.WorkingDirectory = Plugin.BASE_PATH;
info.CreateNoWindow = true;
info.UseShellExecute = false;
}
Process.Start(info);
Application.Quit();
}
PopupManager.GetBasicPopup(new(
Localization.Get("polymod.autoupdate"),
Localization.Get("polymod.autoupdate.description"),
new(new PopupBase.PopupButtonData[] {
new(
"polymod.autoupdate.update",
PopupBase.PopupButtonData.States.None,
(Il2CppSystem.Action)Update
)
}))
).Show();
}
catch (Exception e)
{
Plugin.logger.LogError($"Failed to check updates: {e.Message}");
}
}

internal static void Init()
{
Harmony.CreateAndPatchAll(typeof(AutoUpdate));
}
}
48 changes: 35 additions & 13 deletions src/Managers/Hub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ internal static void ShowConfigPopup()
polymodPopup.Description = "";

polymodPopup.buttonData = CreateConfigPopupButtonData();
polymodPopup.ShowSetWidth(POPUP_WIDTH);
polymodPopup.Show();
}

Expand All @@ -315,41 +316,62 @@ internal static PopupButtonData[] CreateConfigPopupButtonData()
else
{
string debugButtonName = Localization.Get(
"polymod.hub.config.enable",
new Il2CppSystem.Object[] { "DEBUG" }
Plugin.config.debug ? "polymod.hub.config.disable" : "polymod.hub.config.enable",
new Il2CppSystem.Object[] { Localization.Get("polymod.debug",
new Il2CppSystem.Object[]{}).ToUpperInvariant() }
);
string autoUpdateButtonName = Localization.Get(
Plugin.config.autoUpdate ? "polymod.hub.config.disable" : "polymod.hub.config.enable",
new Il2CppSystem.Object[] { Localization.Get("polymod.autoupdate",
new Il2CppSystem.Object[]{}).ToUpperInvariant() }
);
string includeAlphasButtonName = Localization.Get(
Plugin.config.updatePrerelease ? "polymod.hub.config.disable" : "polymod.hub.config.enable",
new Il2CppSystem.Object[] { Localization.Get("polymod.autoupdate.alpha",
new Il2CppSystem.Object[]{}).ToUpperInvariant() }
);
if (Plugin.config.debug)
{
debugButtonName = Localization.Get(
"polymod.hub.config.disable",
new Il2CppSystem.Object[] { "DEBUG" }
);
}
popupButtons.Add(new PopupButtonData(debugButtonName, PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnDebugButtonClicked, -1, true, null));
//popupButtons.Add(new PopupButtonData("", PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnAutoUpdateButtonClicked, -1, true, null));
//popupButtons.Add(new PopupButtonData("", PopupButtonData.States.Disabled, (UIButtonBase.ButtonAction)OnIncludeAlphasButtonClicked, -1, true, null));
popupButtons.Add(new PopupButtonData(autoUpdateButtonName, PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnAutoUpdateButtonClicked, -1, true, null));
popupButtons.Add(new PopupButtonData(includeAlphasButtonName, Plugin.config.autoUpdate ? PopupButtonData.States.None : PopupButtonData.States.Disabled, (UIButtonBase.ButtonAction)OnIncludeAlphasButtonClicked, -1, true, null));
}
return popupButtons.ToArray();

void OnDebugButtonClicked(int buttonId, BaseEventData eventData)
{
Plugin.config = new(debug: !Plugin.config.debug);
Plugin.config = new(debug: !Plugin.config.debug, autoUpdate: Plugin.config.autoUpdate, updatePrerelease: Plugin.config.updatePrerelease);
Plugin.WriteConfig();
Plugin.UpdateConsole();
NotificationManager.Notify(Localization.Get(
"polymod.config.setto",
new Il2CppSystem.Object[] { "Debug", Plugin.config.debug }
new Il2CppSystem.Object[] { Localization.Get("polymod.debug",
new Il2CppSystem.Object[]{}), Plugin.config.debug }
));
isConfigPopupActive = false;
}

void OnAutoUpdateButtonClicked(int buttonId, BaseEventData eventData)
{
Plugin.config = new(debug: Plugin.config.debug, autoUpdate: !Plugin.config.autoUpdate, updatePrerelease: Plugin.config.updatePrerelease);
Plugin.WriteConfig();
Plugin.UpdateConsole();
NotificationManager.Notify(Localization.Get(
"polymod.config.setto",
new Il2CppSystem.Object[] { Localization.Get("polymod.autoupdate",
new Il2CppSystem.Object[]{}), Plugin.config.autoUpdate }
));
isConfigPopupActive = false;
}

void OnIncludeAlphasButtonClicked(int buttonId, BaseEventData eventData)
{
Plugin.config = new(debug: Plugin.config.debug, autoUpdate: Plugin.config.autoUpdate, updatePrerelease: !Plugin.config.updatePrerelease);
Plugin.WriteConfig();
Plugin.UpdateConsole();
NotificationManager.Notify(Localization.Get(
"polymod.config.setto",
new Il2CppSystem.Object[] { Localization.Get("polymod.autoupdate.alpha",
new Il2CppSystem.Object[]{}), Plugin.config.updatePrerelease }
));
isConfigPopupActive = false;
}

Expand Down
7 changes: 5 additions & 2 deletions src/Plugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ namespace PolyMod;
public partial class Plugin : BepInEx.Unity.IL2CPP.BasePlugin
{
internal record PolyConfig(
bool debug = false
bool debug = false,
bool autoUpdate = true,
bool updatePrerelease = false
);

internal const int AUTOIDX_STARTS_FROM = 1000;
Expand Down Expand Up @@ -49,15 +51,16 @@ public override void Load()
catch
{
config = new();
WriteConfig();
}
WriteConfig();
UpdateConsole();
logger = Log;
ConfigFile.CoreConfig[new("Logging.Disk", "WriteUnityLog")].BoxedValue = true;

Compatibility.Init();

Audio.Init();
AutoUpdate.Init();
Loc.Init();
Visual.Init();
Hub.Init();
Expand Down
Loading