diff --git a/mod_manager.tscn b/mod_manager.tscn index 9312ee3..5820b15 100644 --- a/mod_manager.tscn +++ b/mod_manager.tscn @@ -1,7 +1,10 @@ -[gd_scene load_steps=3 format=3 uid="uid://c6570n2s1k1wv"] +[gd_scene load_steps=6 format=3 uid="uid://c6570n2s1k1wv"] [ext_resource type="Theme" uid="uid://dged4041swr1" path="res://theme.tres" id="1_j4kl3"] [ext_resource type="Script" path="res://scripts/mod_manager.gd" id="1_kosmn"] +[ext_resource type="Script" path="res://scripts/path_container.gd" id="3_2w3wq"] +[ext_resource type="Script" path="res://scripts/mod_loader_container.gd" id="4_66fcs"] +[ext_resource type="Script" path="res://scripts/mod_list_container.gd" id="5_7qwuo"] [node name="ModManager" type="Control"] layout_mode = 3 @@ -28,18 +31,24 @@ layout_mode = 2 [node name="PathContainer" type="HBoxContainer" parent="MarginContainer/MainContainer"] layout_mode = 2 +script = ExtResource("3_2w3wq") [node name="PathLabel" type="Label" parent="MarginContainer/MainContainer/PathContainer"] layout_mode = 2 theme_override_colors/font_color = Color(1, 0, 0, 1) text = "GSS Path: C:/" +[node name="RunGameButton" type="Button" parent="MarginContainer/MainContainer/PathContainer"] +layout_mode = 2 +text = "Run GSS" + [node name="ChangePathButton" type="Button" parent="MarginContainer/MainContainer/PathContainer"] layout_mode = 2 text = "Change path" [node name="ModLoaderContainer" type="HBoxContainer" parent="MarginContainer/MainContainer"] layout_mode = 2 +script = ExtResource("4_66fcs") [node name="ModLoaderLabel" type="Label" parent="MarginContainer/MainContainer/ModLoaderContainer"] layout_mode = 2 @@ -57,6 +66,7 @@ theme_override_constants/margin_bottom = 4 [node name="ModListContainer" type="HBoxContainer" parent="MarginContainer/MainContainer"] layout_mode = 2 +script = ExtResource("5_7qwuo") [node name="ModListLabel" type="Label" parent="MarginContainer/MainContainer/ModListContainer"] layout_mode = 2 @@ -83,7 +93,7 @@ layout_mode = 2 [node name="ModManagerLabel" type="Label" parent="MarginContainer/MainContainer/ModManagerContainer"] layout_mode = 2 -text = "GSS Mod Manager v0.2" +text = "GSS Mod Manager v0.3" [node name="ConfigureButton" type="Button" parent="MarginContainer/MainContainer/ModManagerContainer"] layout_mode = 2 @@ -131,10 +141,11 @@ access = 2 filters = PackedStringArray("*.zip,*.rar,*.7z;Archive Files") use_native_dialog = true -[connection signal="pressed" from="MarginContainer/MainContainer/PathContainer/ChangePathButton" to="." method="_on_change_path_button_pressed"] -[connection signal="pressed" from="MarginContainer/MainContainer/ModLoaderContainer/ModLoaderInstallButton" to="." method="_on_mod_loader_install_button_pressed"] -[connection signal="pressed" from="MarginContainer/MainContainer/ModListContainer/RefreshButton" to="." method="_on_refresh_list_button_pressed"] -[connection signal="pressed" from="MarginContainer/MainContainer/ModListContainer/InstallModButton" to="." method="_on_install_mod_button_pressed"] +[connection signal="pressed" from="MarginContainer/MainContainer/PathContainer/RunGameButton" to="MarginContainer/MainContainer/PathContainer" method="_on_run_game_button_pressed"] +[connection signal="pressed" from="MarginContainer/MainContainer/PathContainer/ChangePathButton" to="MarginContainer/MainContainer/PathContainer" method="_on_change_path_button_pressed"] +[connection signal="pressed" from="MarginContainer/MainContainer/ModLoaderContainer/ModLoaderInstallButton" to="MarginContainer/MainContainer/ModLoaderContainer" method="_on_mod_loader_install_button_pressed"] +[connection signal="pressed" from="MarginContainer/MainContainer/ModListContainer/RefreshButton" to="MarginContainer/MainContainer/ModListContainer" method="_on_refresh_button_pressed"] +[connection signal="pressed" from="MarginContainer/MainContainer/ModListContainer/InstallModButton" to="MarginContainer/MainContainer/ModListContainer" method="_on_install_mod_button_pressed"] [connection signal="pressed" from="MarginContainer/MainContainer/ModManagerContainer/ConfigureButton" to="." method="_on_configure_button_pressed"] -[connection signal="dir_selected" from="FileDialog" to="." method="_on_file_dialog_dir_selected"] -[connection signal="file_selected" from="FileDialog" to="." method="_on_file_dialog_file_selected"] +[connection signal="dir_selected" from="FileDialog" to="." method="_on_file_dialog_gss_path_selected"] +[connection signal="file_selected" from="FileDialog" to="MarginContainer/MainContainer/ModListContainer" method="_on_file_dialog_mod_selected"] diff --git a/scripts/files.gd b/scripts/files.gd index 47d55a1..bbabd5a 100644 --- a/scripts/files.gd +++ b/scripts/files.gd @@ -2,7 +2,7 @@ extends Node ## Copies from path to destination_path recursively, destination_path must exist ## Returns error code -static func copy_recursive(from: String, to: String, folder_name: String = "") -> int: +func copy_recursive(from: String, to: String, folder_name: String = "") -> int: var err = DirAccess.make_dir_absolute("%s%s" % [to, folder_name]) if err != OK and err != ERR_ALREADY_EXISTS: return err @@ -30,7 +30,7 @@ static func copy_recursive(from: String, to: String, folder_name: String = "") - ## Deletes directories recursively ## Returns error code -static func remove_recursive(path: String) -> int: +func remove_recursive(path: String) -> int: var dir = DirAccess.open(path) if dir: dir.list_dir_begin() diff --git a/scripts/mod_list_container.gd b/scripts/mod_list_container.gd new file mode 100644 index 0000000..8bb7028 --- /dev/null +++ b/scripts/mod_list_container.gd @@ -0,0 +1,64 @@ +class_name ModListContainer extends HBoxContainer + +@onready var install_mod_button = $InstallModButton +@onready var mod_list_label = $ModListLabel + +var mm: ModManager + +func _on_refresh_button_pressed() -> void: + mm.update_mod_list() + +func _on_install_mod_button_pressed() -> void: + mm.file_dialog.file_mode = FileDialog.FileMode.FILE_MODE_OPEN_FILE + mm.file_dialog.root_subfolder = "%s\\Downloads" % OS.get_environment("USERPROFILE") + mm.file_dialog.popup() + +func set_install_disabled(disabled: bool) -> void: + install_mod_button.disabled = disabled + +func set_count(count: int) -> void: + mod_list_label.text = "Mod list [%s]" % count + +func _on_file_dialog_mod_selected(path: String) -> void: + # Use 7z to unzip the archive and copy it to GSS + var err = OS.execute("%s/7z.exe" % mm.root_path, ["x", path, "-o" + "%s/mod" % mm.root_path, "-y"]) + if mm.os_error("Execute 7z", err): return + + var names = DirAccess.get_directories_at("%s/mod" % mm.root_path) + var mod_name = names[0] # there can be more, only if the program crashes. sucks to suck + + err = DirAccess.make_dir_absolute("%s/Simulatorita/Binaries/Win64/Mods/%s" % [mm.gss_path, mod_name]) + if mm.error("Make directory for mod", err): return + + err = Files.copy_recursive( + "%s/mod/%s" % [mm.root_path, mod_name], + "%s/Simulatorita/Binaries/Win64/Mods/%s" % [mm.gss_path, mod_name] + ) + if mm.error("Copy from mod to mods folder", err): return + err = Files.remove_recursive("%s/mod/%s" % [mm.root_path, mod_name]) + if mm.error("Remove mod", err): return + + # Add "mod_name : 1" to mods.txt + var file = FileAccess.open("%s/Simulatorita/Binaries/Win64/Mods/mods.txt" % mm.gss_path, FileAccess.READ) + if file: + var lines = [] + while not file.eof_reached(): + lines.append(file.get_line()) + file.close() + lines.append(mod_name + " : 1") + + file = FileAccess.open("%s/Simulatorita/Binaries/Win64/Mods/mods.txt" % mm.gss_path, FileAccess.WRITE) + if file: + for i in range(len(lines)): + file.store_string(lines[i]) + if i < len(lines) - 1: + file.store_string("\n") + file.close() + mm.update_mod_list() + print("Mod added successfully") + else: + err = FileAccess.get_open_error() + mm.error("Open mods.txt file", err) + else: + err = FileAccess.get_open_error() + mm.error("Open mods.txt file", err) diff --git a/scripts/mod_loader_container.gd b/scripts/mod_loader_container.gd new file mode 100644 index 0000000..ec3b3a9 --- /dev/null +++ b/scripts/mod_loader_container.gd @@ -0,0 +1,38 @@ +class_name ModLoaderContainer extends HBoxContainer + +@onready var mod_loader_install_button = $ModLoaderInstallButton +@onready var mod_loader_label = $ModLoaderLabel + +var is_installed := false +var mm: ModManager + +func _on_mod_loader_install_button_pressed() -> void: + if is_installed: + Files.remove_recursive("%s/Simulatorita/Binaries/Win64/Mods" % mm.gss_path) + DirAccess.remove_absolute("%s/Simulatorita/Binaries/Win64/UE4SS.dll" % mm.gss_path) + DirAccess.remove_absolute("%s/Simulatorita/Binaries/Win64/UE4SS-settings.ini" % mm.gss_path) + DirAccess.remove_absolute("%s/Simulatorita/Binaries/Win64/dwmapi.dll" % mm.gss_path) + else: + Files.copy_recursive("%s/UE4SS" % mm.root_path, "%s/Simulatorita/Binaries/Win64" % mm.gss_path) + set_installed(!is_installed) + mm.update_mod_list() + +func check_installed() -> void: + var installed = FileAccess.file_exists("%s/Simulatorita/Binaries/Win64/UE4SS.dll" % mm.gss_path) + set_installed(installed) + +func set_install_disabled(disabled: bool) -> void: + mod_loader_install_button.disabled = disabled + +func set_installed(installed: bool) -> void: + is_installed = installed + if installed: + mod_loader_label.text = "Mod loader installed" + mod_loader_label.add_theme_color_override("font_color", Color.GREEN) + mod_loader_install_button.text = "Uninstall mod loader" + mm.mod_list_container.set_install_disabled(false) + else: + mod_loader_label.text = "Mod loader not installed" + mod_loader_label.add_theme_color_override("font_color", Color.RED) + mod_loader_install_button.text = "Install mod loader" + mm.mod_list_container.set_install_disabled(false) diff --git a/scripts/mod_manager.gd b/scripts/mod_manager.gd index 34025f0..3b855e8 100644 --- a/scripts/mod_manager.gd +++ b/scripts/mod_manager.gd @@ -1,14 +1,13 @@ -extends Control +class_name ModManager extends Control @onready var main = $MarginContainer/MainContainer @onready var file_dialog = $FileDialog -@onready var path_label: Label = main.get_node("PathContainer/PathLabel") -@onready var mod_loader_label: Label = main.get_node("ModLoaderContainer/ModLoaderLabel") -@onready var mod_loader_install_button: Button = main.get_node("ModLoaderContainer/ModLoaderInstallButton") -@onready var install_mod_button: Button = main.get_node("ModListContainer/InstallModButton") -@onready var mod_list_label: Label = main.get_node("ModListContainer/ModListLabel") -@onready var mod_containers: Control = main.get_node("ScrollContainer/MarginContainer") @onready var config_panel = $ConfigPanel +@onready var mod_containers: Control = main.get_node("ScrollContainer/MarginContainer") + +@onready var path_container: PathContainer = main.get_node("PathContainer") +@onready var mod_loader_container: ModLoaderContainer = main.get_node("ModLoaderContainer") +@onready var mod_list_container: ModListContainer = main.get_node("ModListContainer") const BUILTIN_MODS: Array[String] = [ "ActorDumperMod", "BPML_GenericFunctions", "BPModLoaderMod", @@ -19,35 +18,11 @@ const BUILTIN_MODS: Array[String] = [ var root_path := OS.get_executable_path().get_base_dir() var gss_path: String: set(value): - path_label.text = "GSS Path: %s" % value - config.set_value("main", "gss_path", value) - config.save("user://config") gss_path = value - valid_gss_path = FileAccess.file_exists("%s/Simulatorita.exe" % gss_path) - mod_loader_installed = FileAccess.file_exists("%s/Simulatorita/Binaries/Win64/UE4SS.dll" % gss_path) + config.set_value("main", "gss_path", gss_path) + config.save("user://config") + path_container.post_path_change() update_mod_list() -var valid_gss_path: bool: - set(value): - if value: - path_label.add_theme_color_override("font_color", Color.GREEN) - mod_loader_install_button.disabled = false - else: - path_label.add_theme_color_override("font_color", Color.RED) - mod_loader_install_button.disabled = true - valid_gss_path = value -var mod_loader_installed: bool: - set(value): - if value: - mod_loader_label.text = "Mod loader installed" - mod_loader_label.add_theme_color_override("font_color", Color.GREEN) - mod_loader_install_button.text = "Uninstall mod loader" - install_mod_button.disabled = false - else: - mod_loader_label.text = "Mod loader not installed" - mod_loader_label.add_theme_color_override("font_color", Color.RED) - mod_loader_install_button.text = "Install mod loader" - install_mod_button.disabled = true - mod_loader_installed = value var config: ConfigFile var config_editor_path: String var editor_thread := Thread.new() @@ -55,29 +30,14 @@ var editor_thread := Thread.new() # NOTE: Only test in exported! (Unless you find a way to do it in the editor) # TODO: Make editor for editing config configurable +# ^^^^^ Alternate solution: Try to make a standard on how to comment your config. +# ^^^^^ Then make the config editor built in (Topic to discuss) # TODO: Clean up this mess # TODO: Get icon -## Searches Steam paths for Grocery Store Simulator -func detect_gss() -> void: - var drive_paths: Array[String] = [] - drive_paths.append("C:/Program Files (x86)/Steam/steamapps/common") - for idx in range(DirAccess.get_drive_count()): - var drive_name = DirAccess.get_drive_name(idx) - if drive_name == "C:": continue - drive_paths.append("%s/SteamLibrary/steamapps/common" % drive_name) - - for drive_path in drive_paths: - if "Grocery Store Simulator" in DirAccess.get_directories_at(drive_path): - print("Found Grocery Store Simulator at %s" % drive_path) - gss_path = "%s/Grocery Store Simulator" % drive_path - return - print("Didn't find Grocery Store Simulator in Steam directories") - ## Returns dictionary with the schema { mod_name (String): on (bool) } func get_mod_list() -> Dictionary: var enabled: Dictionary = {} - var file = FileAccess.open("%s/Simulatorita/Binaries/Win64/Mods/mods.txt" % gss_path, FileAccess.READ) if file: var lines = file.get_as_text(true).split('\n') @@ -95,7 +55,7 @@ func get_mod_list() -> Dictionary: ## Updates the UI for mods not built-in func update_mod_list() -> void: - if !mod_loader_installed: + if !mod_loader_container.is_installed: for child in mod_containers.get_children(): child.queue_free() return @@ -106,16 +66,22 @@ func update_mod_list() -> void: var list = get_mod_list() var non_builtin_mod_count = 0 for mod in list: - if mod in BUILTIN_MODS: continue + if mod in BUILTIN_MODS: + continue var container = ModContainer.with(mod) mod_containers.add_child(container) + container.set_toggled(list[mod]) container.configure.connect(_on_configure_mod) container.delete.connect(_on_delete_mod) container.toggled.connect(_on_toggle_mod) non_builtin_mod_count += 1 - mod_list_label.text = "Mod list [%s]" % non_builtin_mod_count + mod_list_container.set_count(non_builtin_mod_count) + +func run_config_editor() -> void: + var err = OS.execute("notepad", [config_editor_path]) + os_error("Execute config editor", err) func _on_configure_mod(mod_name: String) -> void: for file in DirAccess.get_files_at("%s/Simulatorita/Binaries/Win64/Mods/%s/Scripts" % [gss_path, mod_name]): @@ -156,6 +122,9 @@ func _on_delete_mod(mod_name: String) -> void: var err = FileAccess.get_open_error() error("Open mods.txt file", err) +func _on_configure_button_pressed() -> void: + config_panel.visible = !config_panel.visible + func _on_toggle_mod(mod_name: String, on: bool) -> void: var file = FileAccess.open("%s/Simulatorita/Binaries/Win64/Mods/mods.txt" % gss_path, FileAccess.READ) if file: @@ -186,100 +155,32 @@ func _on_toggle_mod(mod_name: String, on: bool) -> void: var err = FileAccess.get_open_error() error("Opening mods.txt file", err) +func _on_file_dialog_gss_path_selected(dir: String) -> void: + gss_path = dir + func _process(_delta: float) -> void: if editor_thread.is_started() and !editor_thread.is_alive(): editor_thread.wait_to_finish() func _ready() -> void: + # Set self as the ModManager in containers + path_container.mm = self + mod_loader_container.mm = self + mod_list_container.mm = self + config = ConfigFile.new() var err = config.load("user://config") if err == ERR_FILE_NOT_FOUND: - detect_gss() + path_container.detect_gss() else: error("Read user config", err) var path: String = config.get_value("main", "gss_path") if path == null: - detect_gss() + path_container.detect_gss() else: gss_path = path -func _on_mod_loader_install_button_pressed() -> void: - if mod_loader_installed: - Files.remove_recursive("%s/Simulatorita/Binaries/Win64/Mods" % gss_path) - DirAccess.remove_absolute("%s/Simulatorita/Binaries/Win64/UE4SS.dll" % gss_path) - DirAccess.remove_absolute("%s/Simulatorita/Binaries/Win64/UE4SS-settings.ini" % gss_path) - DirAccess.remove_absolute("%s/Simulatorita/Binaries/Win64/dwmapi.dll" % gss_path) - else: - Files.copy_recursive("%s/UE4SS" % root_path, "%s/Simulatorita/Binaries/Win64" % gss_path) - mod_loader_installed = !mod_loader_installed - update_mod_list() - -func _on_change_path_button_pressed() -> void: - file_dialog.file_mode = FileDialog.FileMode.FILE_MODE_OPEN_DIR - file_dialog.root_subfolder = gss_path - file_dialog.popup() - -func _on_file_dialog_dir_selected(dir: String) -> void: - gss_path = dir - -func run_config_editor() -> void: - var err = OS.execute("notepad", [config_editor_path]) - os_error("Execute config editor", err) - -func _on_install_mod_button_pressed() -> void: - file_dialog.file_mode = FileDialog.FileMode.FILE_MODE_OPEN_FILE - file_dialog.root_subfolder = "%s\\Downloads" % OS.get_environment("USERPROFILE") - file_dialog.popup() - -func _on_configure_button_pressed() -> void: - config_panel.visible = !config_panel.visible - -func _on_refresh_list_button_pressed() -> void: - update_mod_list() - -func _on_file_dialog_file_selected(path: String) -> void: - var err = OS.execute("%s/7z.exe" % root_path, ["x", path, "-o" + "%s/mod" % root_path, "-y"]) - if os_error("Execute 7z", err): return - - var names = DirAccess.get_directories_at("%s/mod" % root_path) - var mod_name = names[0] # there can be more, only if the program crashes. sucks to suck - - err = DirAccess.make_dir_absolute("%s/Simulatorita/Binaries/Win64/Mods/%s" % [gss_path, mod_name]) - if error("Make directory for mod", err): return - - err = Files.copy_recursive( - "%s/mod/%s" % [root_path, mod_name], - "%s/Simulatorita/Binaries/Win64/Mods/%s" % [gss_path, mod_name] - ) - if error("Copy from mod to mods folder", err): return - err = Files.remove_recursive("%s/mod/%s" % [root_path, mod_name]) - if error("Remove mod", err): return - - var file = FileAccess.open("%s/Simulatorita/Binaries/Win64/Mods/mods.txt" % gss_path, FileAccess.READ) - if file: - var lines = [] - while not file.eof_reached(): - lines.append(file.get_line()) - file.close() - lines.append(mod_name + " : 1") - - file = FileAccess.open("%s/Simulatorita/Binaries/Win64/Mods/mods.txt" % gss_path, FileAccess.WRITE) - if file: - for i in range(len(lines)): - file.store_string(lines[i]) - if i < len(lines) - 1: - file.store_string("\n") - file.close() - update_mod_list() - print("Mod added successfully") - else: - err = FileAccess.get_open_error() - error("Open mods.txt file", err) - else: - err = FileAccess.get_open_error() - error("Open mods.txt file", err) - ## Returns a boolean that if is true, action didn't complete successfully func error(action: String, err: int) -> bool: if err: @@ -287,7 +188,7 @@ func error(action: String, err: int) -> bool: return true return false -## `error` function, but suited for `OS.execute` returns +## `error` function, but suited for OS errors (not handled by Godot) func os_error(action: String, err: int) -> bool: if err: print("%s exit code: FAILED [%s]" % [action, err]) diff --git a/scripts/path_container.gd b/scripts/path_container.gd new file mode 100644 index 0000000..f701712 --- /dev/null +++ b/scripts/path_container.gd @@ -0,0 +1,45 @@ +class_name PathContainer extends HBoxContainer + +@onready var path_label: Label = $PathLabel + +var valid_path: bool +var mm: ModManager + +func _on_change_path_button_pressed() -> void: + mm.file_dialog.file_mode = FileDialog.FileMode.FILE_MODE_OPEN_DIR + mm.file_dialog.root_subfolder = mm.gss_path + mm.file_dialog.popup() + +func _on_run_game_button_pressed() -> void: + var err = OS.shell_open("steam://run/2961880") + mm.os_error("Open game", err) + +func detect_gss() -> void: + var drive_paths: Array[String] = [] + drive_paths.append("C:/Program Files (x86)/Steam/steamapps/common") + for idx in range(DirAccess.get_drive_count()): + var drive_name = DirAccess.get_drive_name(idx) + if drive_name == "C:": continue + drive_paths.append("%s/SteamLibrary/steamapps/common" % drive_name) + + for drive_path in drive_paths: + if "Grocery Store Simulator" in DirAccess.get_directories_at(drive_path): + print("Found Grocery Store Simulator at %s" % drive_path) + mm.gss_path = "%s/Grocery Store Simulator" % drive_path + post_path_change() + return + print("Didn't find Grocery Store Simulator in Steam directories") + +func post_path_change() -> void: + path_label.text = "GSS Path: %s" % mm.gss_path + valid_path = FileAccess.file_exists("%s/Simulatorita.exe" % mm.gss_path) + set_valid() + mm.mod_loader_container.check_installed() + +func set_valid() -> void: + if valid_path: + path_label.add_theme_color_override("font_color", Color.GREEN) + mm.mod_loader_container.set_install_disabled(false) + else: + path_label.add_theme_color_override("font_color", Color.RED) + mm.mod_loader_container.set_install_disabled(true)