diff --git a/Injector/AutoUpdater.gd b/Injector/AutoUpdater.gd new file mode 100644 index 0000000..f1feabc --- /dev/null +++ b/Injector/AutoUpdater.gd @@ -0,0 +1,106 @@ +extends Node +class_name AutoUpdater + +@export var Main : InjectorMain + +func _ready() -> void: + pass + +func checkInjectorUpdate(): + var deletemePath = ProjectSettings.globalize_path(".").path_join("Injector.pck.deleteme") + if FileAccess.file_exists(deletemePath): + DirAccess.remove_absolute(deletemePath) + + var httpReq = HTTPRequest.new() + add_child(httpReq) + var err = httpReq.request(Main.githubAPIBaseURL + "repos/Ryhon0/VostokMods/releases", ["accept: application/vnd.github+json"]) + if err != OK: + push_error("Failed to create mod loader releases request ", err) + Main.launchOrShowConfig() + return + + Main.StatusLabel.text = "Checking for updates" + Main.showHttpProgress(httpReq) + + httpReq.request_completed.connect(injectorReleasesRequestCompleted) + +func injectorReleasesRequestCompleted(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray): + if result != HTTPRequest.RESULT_SUCCESS: + push_error("Failed to get mod loader releases") + Main.launchOrShowConfig() + return + if response_code < 200 || response_code >= 300: + push_error("Failed to get mod loader releases (HTTP code " + str(response_code) + ")") + Main.launchOrShowConfig() + return + + var json = JSON.parse_string(body.get_string_from_utf8()) + for r in json: + if r["draft"]: continue + if r["prerelease"] && !Main.config.autoUpdatePreRelease: + continue + var tag = r["tag_name"] + + var injectorAsset + for a in r["assets"]: + if a["name"] == "Injector.pck": + injectorAsset = a + break + if !injectorAsset: + continue + + print("Latest version: " + tag) + if Main.version != tag: + downloadLoaderUpdate(tag, injectorAsset) + else: Main.launchOrShowConfig() + return + +func downloadLoaderUpdate(tag, asset): + var httpReq = HTTPRequest.new() + add_child(httpReq) + var err = httpReq.request(asset["browser_download_url"]) + if err != OK: + Main.StatusLabel.text = "Failed to download mod loader update.\nCode " + str(err) + get_tree().create_timer(2).timeout.connect(Main.launchOrShowConfig) + return + + Main.StatusLabel.text = "Downloading mod loader version " + tag + Main.showHttpProgress(httpReq) + + httpReq.request_completed.connect(injectorFileDownloaded) + +func injectorFileDownloaded(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray): + if result != HTTPRequest.RESULT_SUCCESS: + push_error("Failed to download mod loader") + Main.StatusLabel.text = "Failed to save mod loader, error " + str(FileAccess.get_open_error()) + get_tree().create_timer(2).timeout.connect(Main.launchOrShowConfig) + return + if response_code < 200 || response_code >= 300: + push_error("Failed to get mod loader releases (HTTP code " + str(response_code) + ")") + Main.StatusLabel.text = "Failed to save mod loader, error " + str(FileAccess.get_open_error()) + get_tree().create_timer(2).timeout.connect(Main.launchOrShowConfig) + return + + var dir = ProjectSettings.globalize_path(".") + var injectorPath = dir.path_join("Injector.pck") + var deletemePath = dir.path_join("Injector.pck.deleteme") + + var err = DirAccess.rename_absolute(injectorPath, deletemePath) + if err != OK: + Main.StatusLabel.text = "Failed to move moad loader, error " + str(err) + get_tree().create_timer(2).timeout.connect(Main.launchOrShowConfig) + return + + var f = FileAccess.open(injectorPath, FileAccess.WRITE) + if !f: + DirAccess.rename_absolute(deletemePath, injectorPath) + Main.StatusLabel.text = "Failed to save mod loader, error " + str(FileAccess.get_open_error()) + get_tree().create_timer(2).timeout.connect(Main.launchOrShowConfig) + return + f.store_buffer(body) + f.close() + + var args = ["--main-pack", "Injector.pck", "--"] + args.append(OS.get_cmdline_user_args()) + OS.create_process(OS.get_executable_path(), args, false) + get_tree().quit() diff --git a/Injector/Main.gd b/Injector/Main.gd index 7f1182c..f09625a 100644 --- a/Injector/Main.gd +++ b/Injector/Main.gd @@ -1,4 +1,5 @@ extends Control +class_name InjectorMain @export var VersionLabel : Label @export var StatusLabel: Label @@ -8,12 +9,16 @@ extends Control @export var ConfigScreen : Control @export var SettingsPage : Control -@export var ModList : Control +@export var Mods : ModList +@export var Updater : AutoUpdater + +var version const configPath = "user://ModConfig.json" class ModLoaderConfig: var customModDir : String = "" var startOnConfigScreen : bool = false + var autoUpdatePreRelease : bool = false var config : ModLoaderConfig = ModLoaderConfig.new() func loadConfig(): @@ -27,13 +32,16 @@ func loadConfig(): config.customModDir = obj["customModDir"] if "startOnConfigScreen" in obj: config.startOnConfigScreen = obj["startOnConfigScreen"] + if "autoUpdatePreRelease" in obj: + config.autoUpdatePreRelease = obj["autoUpdatePreRelease"] SettingsPage.onLoaded() func saveConfig(): var jarr = { "customModDir": config.customModDir, - "startOnConfigScreen": config.startOnConfigScreen + "startOnConfigScreen": config.startOnConfigScreen, + "autoUpdatePreRelease": config.autoUpdatePreRelease } var jstr = JSON.stringify(jarr) var f = FileAccess.open(configPath, FileAccess.WRITE) @@ -88,9 +96,17 @@ func _ready() -> void: loadConfig() var f = FileAccess.open("res://VM_VERSION", FileAccess.READ) - VersionLabel.text = "Version " + f.get_as_text() + version = f.get_as_text() + VersionLabel.text = "Version " + version f.close() + Mods.loadMods() + showLoadingScreen() + if !OS.has_feature("editor"): + Updater.checkInjectorUpdate() + else: launchOrShowConfig() + +func launchOrShowConfig(): if config.startOnConfigScreen: showConfigScreen() else: @@ -190,7 +206,7 @@ Update the injector or verify game files" var args = ["--main-pack", pckdir, "--", "--mods-dir", modsDir] args.append(OS.get_cmdline_user_args()) - var pid = OS.create_process(OS.get_executable_path(), args, true) + var pid = OS.create_process(OS.get_executable_path(), args, false) if pid == -1: StatusLabel.text = "Failed to start Road to Vostok" shutdown() diff --git a/Injector/Main.tscn b/Injector/Main.tscn index 1b678e9..d82e7f4 100644 --- a/Injector/Main.tscn +++ b/Injector/Main.tscn @@ -1,12 +1,13 @@ -[gd_scene load_steps=6 format=3 uid="uid://b6nywv6aqxm3r"] +[gd_scene load_steps=7 format=3 uid="uid://b6nywv6aqxm3r"] [ext_resource type="Script" path="res://Main.gd" id="1_x5slr"] [ext_resource type="Texture2D" uid="uid://wbaqykif8euy" path="res://icon.svg" id="2_74yqq"] [ext_resource type="Script" path="res://Settings.gd" id="2_b0jil"] [ext_resource type="Script" path="res://ModList.gd" id="3_xyy35"] [ext_resource type="Texture2D" uid="uid://bcc6fhil26kqe" path="res://donate_icon.png" id="5_r2nj5"] +[ext_resource type="Script" path="res://AutoUpdater.gd" id="6_etu4i"] -[node name="Main" type="Control" node_paths=PackedStringArray("VersionLabel", "StatusLabel", "Progress", "LoadingScreen", "ConfigScreen", "SettingsPage", "ModList")] +[node name="Main" type="Control" node_paths=PackedStringArray("VersionLabel", "StatusLabel", "Progress", "LoadingScreen", "ConfigScreen", "SettingsPage", "Mods", "Updater")] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 @@ -20,7 +21,8 @@ Progress = NodePath("LoadingScreen/VBoxContainer/Progress") LoadingScreen = NodePath("LoadingScreen") ConfigScreen = NodePath("ConfigScreen") SettingsPage = NodePath("ConfigScreen/TabContainer/Settings") -ModList = NodePath("ConfigScreen/TabContainer/Mods") +Mods = NodePath("ConfigScreen/TabContainer/Mods") +Updater = NodePath("AutoUpdater") [node name="LoadingScreen" type="CenterContainer" parent="."] visible = false @@ -46,6 +48,7 @@ layout_mode = 2 max_value = 1.0 [node name="ConfigScreen" type="HBoxContainer" parent="."] +visible = false layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -202,6 +205,10 @@ layout_mode = 2 theme_override_font_sizes/font_size = 24 text = "Launch" +[node name="AutoUpdater" type="Node" parent="." node_paths=PackedStringArray("Main")] +script = ExtResource("6_etu4i") +Main = NodePath("..") + [connection signal="tab_changed" from="ConfigScreen/TabContainer" to="ConfigScreen/TabContainer/Mods" method="tabChanged"] [connection signal="pressed" from="ConfigScreen/TabContainer/Settings/VBoxContainer/GridContainer/HBoxContainer/Button" to="ConfigScreen/TabContainer/Settings" method="openModDirDialog"] [connection signal="column_title_clicked" from="ConfigScreen/TabContainer/Mods/ModListTree" to="ConfigScreen/TabContainer/Mods" method="titleClicked"] diff --git a/Injector/ModList.gd b/Injector/ModList.gd index 2b31d02..cf0c60c 100644 --- a/Injector/ModList.gd +++ b/Injector/ModList.gd @@ -1,26 +1,31 @@ extends ScrollContainer +class_name ModList -@export var Main : Control -@export var List : Tree -var dirty : bool = true +@export var Main: Control +@export var List: Tree +var mods : Array[ModInfo] = [] + +class ModInfo: + var zipPath : String + var config : ConfigFile func _ready(): - List.set_column_title(0,"Name") - List.set_column_title(1,"ID") - List.set_column_title(2,"Version") - List.set_column_title(3,"File name") - List.set_column_title(4,"Enabled") + List.set_column_title(0, "Name") + List.set_column_title(1, "ID") + List.set_column_title(2, "Version") + List.set_column_title(3, "File name") + List.set_column_title(4, "Enabled") for i in range(List.columns): - List.set_column_expand(i,false) + List.set_column_expand(i, false) - List.set_column_expand(0,true) + List.set_column_expand(0, true) List.set_column_custom_minimum_width(1, 175) List.set_column_custom_minimum_width(3, 175) -func populateList(): +func loadMods(): + mods = [] var modsdir = Main.getModsDir() - dirty = false List.clear() List.create_item() @@ -54,9 +59,14 @@ func populateList(): if !cfg.has_section_key("mod", "name") || !cfg.has_section_key("mod", "id") || !cfg.has_section_key("mod", "version"): continue - var modname = cfg.get_value("mod","name") - var modid = cfg.get_value("mod","id") - var modver = cfg.get_value("mod","version") + var modname = cfg.get_value("mod", "name") + var modid = cfg.get_value("mod", "id") + var modver = cfg.get_value("mod", "version") + + var modi = ModInfo.new() + modi.config = cfg + modi.zipPath = modsdir.path_join(zipname) + mods.append(modi) var li = List.create_item() li.set_meta("filename", zipname) @@ -65,22 +75,16 @@ func populateList(): li.set_text(1, modid) li.set_text(2, modver) li.set_text(3, zipname) - li.set_cell_mode(List.columns-1, TreeItem.CELL_MODE_CHECK) - li.set_checked(List.columns-1, !disabled) - li.set_editable(List.columns-1, true) - -func tabChanged(idx: int): - if idx == get_index(): - if dirty: - populateList() - dirty = false + li.set_cell_mode(List.columns - 1, TreeItem.CELL_MODE_CHECK) + li.set_checked(List.columns - 1, !disabled) + li.set_editable(List.columns - 1, true) func itemEdited() -> void: - if List.get_edited_column() != List.columns-1: + if List.get_edited_column() != List.columns - 1: return - var item : TreeItem = List.get_edited() - var disabled = item.is_checked(List.columns-1) + var item: TreeItem = List.get_edited() + var disabled = item.is_checked(List.columns - 1) var file = Main.getModsDir().path_join(item.get_meta("filename")) var from = file + ".zip" @@ -89,10 +93,10 @@ func itemEdited() -> void: else: to += ".disabled" - if DirAccess.rename_absolute(from,to) != OK: + if DirAccess.rename_absolute(from, to) != OK: OS.alert("Could not move file " + from) -func titleClicked(col : int, mouse : int) -> void: +func titleClicked(col: int, mouse: int) -> void: if mouse != MOUSE_BUTTON_LEFT: return @@ -101,7 +105,7 @@ func titleClicked(col : int, mouse : int) -> void: for i in items: root.remove_child(i) - items.sort_custom(func (a:TreeItem,b : TreeItem) -> bool: + items.sort_custom(func(a: TreeItem, b: TreeItem) -> bool: if a.get_cell_mode(col) == TreeItem.CELL_MODE_STRING: return a.get_text(col).naturalnocasecmp_to(b.get_text(col)) < 0 @@ -111,4 +115,3 @@ func titleClicked(col : int, mouse : int) -> void: for i in items: root.add_child(i) - diff --git a/Injector/Settings.gd b/Injector/Settings.gd index 474ac32..9aab479 100644 --- a/Injector/Settings.gd +++ b/Injector/Settings.gd @@ -1,11 +1,11 @@ extends Control -@export var Main : Control -@export var CustomModDirLine : LineEdit -@export var StartOnConfigCheckBox : CheckBox +@export var Main: Control +@export var CustomModDirLine: LineEdit +@export var StartOnConfigCheckBox: CheckBox func _ready() -> void: - CustomModDirLine.text_changed.connect(func(val): Main.config.customModDir = val; Main.ModList.dirty = true) + CustomModDirLine.text_changed.connect(func(val): Main.config.customModDir = val; Main.Mods.loadMods()) StartOnConfigCheckBox.toggled.connect(func(val): Main.config.startOnConfigScreen = val) func openModDirDialog(): @@ -13,10 +13,10 @@ func openModDirDialog(): fd.access = FileDialog.ACCESS_FILESYSTEM fd.file_mode = FileDialog.FILE_MODE_OPEN_DIR fd.show_hidden_files = true - fd.dir_selected.connect(func(dir): CustomModDirLine.text = dir; Main.config.customModDir = dir; Main.ModList.dirty = true) + fd.dir_selected.connect(func(dir): CustomModDirLine.text = dir; Main.config.customModDir = dir; Main.Mods.loadMods()) add_child(fd) fd.popup_centered_ratio() func onLoaded(): CustomModDirLine.text = Main.config.customModDir - StartOnConfigCheckBox.button_pressed = Main.config.startOnConfigScreen \ No newline at end of file + StartOnConfigCheckBox.button_pressed = Main.config.startOnConfigScreen diff --git a/Injector/VM_VERSION b/Injector/VM_VERSION index 6da28dd..341cf11 100644 --- a/Injector/VM_VERSION +++ b/Injector/VM_VERSION @@ -1 +1 @@ -0.1.1 \ No newline at end of file +0.2.0 \ No newline at end of file