2 Commits
0.0.3 ... 0.0.4

Author SHA1 Message Date
6b520a1bcc Add Footer, Remove Mods, and Add Main Mod
All checks were successful
Build and Upload Release (Windows EXE) / Build Windows EXE (release) Successful in 3m30s
2025-09-16 15:10:13 -04:00
ca4fd2c9d5 New ICO better Help Text 2025-09-16 14:45:51 -04:00
8 changed files with 134 additions and 55 deletions

View File

@@ -31,7 +31,7 @@ jobs:
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
# Include SeaLoader.png so the packaged app icon in-app works # Include SeaLoader.png so the packaged app icon in-app works
$addData = "SeaLoader.png;." # Windows uses ';' for --add-data $addData = "SeaLoader.png;." # Windows uses ';' for --add-data
pyinstaller --noconfirm --onefile --windowed sealoader_gui.py --name SeaLoader --add-data "$addData" pyinstaller --noconfirm --onefile --windowed sealoader_gui.py --name SeaLoader --add-data "$addData" --icon SeaLoader.ico
- name: Prepare artifact - name: Prepare artifact
shell: powershell shell: powershell
@@ -56,11 +56,5 @@ jobs:
Write-Host "Uploading asset to $uploadUrl" Write-Host "Uploading asset to $uploadUrl"
Invoke-RestMethod -Method Post -Uri $uploadUrl -Headers @{ Authorization = "token $env:TOKEN" } -ContentType "application/zip" -InFile "SeaLoader_Windows_x64.zip" Invoke-RestMethod -Method Post -Uri $uploadUrl -Headers @{ Authorization = "token $env:TOKEN" } -ContentType "application/zip" -InFile "SeaLoader_Windows_x64.zip"
- name: Upload artifact (CI logs) # CI artifact upload removed for GHES compatibility
uses: actions/upload-artifact@v4
with:
name: SeaLoader_Windows_x64
path: |
SeaLoader_Windows_x64.zip
dist/SeaLoader.exe

54
.gitignore vendored Normal file
View File

@@ -0,0 +1,54 @@
# Python bytecode
__pycache__/
*.py[cod]
*$py.class
# Virtual environments
.venv/
venv/
env/
ENV/
.conda/
.python-version
# Testing / type-check / tooling caches
.pytest_cache/
.mypy_cache/
.ruff_cache/
.pyre/
.tox/
.coverage
.coverage.*
.cache/
nosetests.xml
coverage.xml
htmlcov/
# Build and packaging
build/
dist/
.eggs/
*.egg-info/
*.egg
pip-wheel-metadata/
*.manifest
*.spec
dist_upload/
SeaLoader.exe
SeaLoader_Windows_x64.zip
# Editors/IDE
.vscode/
.idea/
*.code-workspace
# OS junk
.DS_Store
Thumbs.db
# Local configuration
usersettings.ini
# Logs
*.log

BIN
SeaLoader.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
hrsys.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 2048 2048">
<!-- Generator: Adobe Illustrator 29.6.0, SVG Export Plug-In . SVG Version: 2.1.1 Build 207) -->
<defs>
<style>
.st0, .st1, .st2 {
fill: #f16465;
}
.st0, .st1, .st3 {
display: none;
}
.st1 {
font-family: FMBolyarSansPro-400, 'FM Bolyar Sans Pro';
font-size: 176.84px;
}
.st4 {
letter-spacing: .6em;
}
</style>
</defs>
<g id="Systems" class="st3">
<text/>
</g>
<g id="H">
<g>
<text class="st1" transform="translate(151.86 1585.59)"><tspan class="st4" x="0" y="0">SYSTEM</tspan><tspan x="1589.37" y="0">S</tspan></text>
<rect class="st0" x="43.86" y="1699.23" width="1960.29" height="16.64"/>
<g>
<path class="st2" d="M516.81,384.73c354.34.79,708.68,1.58,1063.01,2.37,25.33-.26,191.03.81,306.99,134.63,27.84,32.13,97.9,123.95,92.85,253.41-5.85,150-109.21,265.44-206.05,311.69-40.43,19.31-82.89,28.07-95.95,30.69-75.28,15.13-139.12,8.06-179.02.41,180.82,180.82,361.64,361.64,542.47,542.47h-281.15c-240.47-240.47-480.95-480.95-721.42-721.42h600.36c98.23-15.25,167-101.58,161.66-191.46-6.01-101.15-104.58-184.37-216.28-168.88h-897.5c-56.65-64.64-113.31-129.28-169.96-193.92Z"/>
<polygon class="st2" points="6.89 1663.27 6.89 385.2 190.13 385.88 189.56 945.29 940.52 945.29 1173.93 1166.01 1173.93 1663.27 991.26 1663.27 991.26 1125.42 192.09 1125.42 192.09 1663.27 6.89 1663.27"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -3,6 +3,7 @@ import os
import shutil import shutil
import sys import sys
import threading import threading
import webbrowser
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Dict, List, Tuple from typing import Dict, List, Tuple
@@ -209,9 +210,23 @@ class SeaLoaderApp(tk.Tk):
bottom.pack(fill=tk.X, padx=12, pady=(0, 12)) bottom.pack(fill=tk.X, padx=12, pady=(0, 12))
self.enable_btn = ttk.Button(bottom, text="Enable Matching Mods", command=self._on_enable) self.enable_btn = ttk.Button(bottom, text="Enable Matching Mods", command=self._on_enable)
self.enable_btn.pack(side=tk.LEFT) self.enable_btn.pack(side=tk.LEFT)
self.disable_all_btn = ttk.Button(bottom, text="Disable All Mods", command=self._on_disable_all)
self.disable_all_btn.pack(side=tk.LEFT, padx=(8, 0))
self.help_btn = ttk.Button(bottom, text="Help", command=self._on_help) self.help_btn = ttk.Button(bottom, text="Help", command=self._on_help)
self.help_btn.pack(side=tk.RIGHT) self.help_btn.pack(side=tk.RIGHT)
# Footer attribution
footer = ttk.Frame(self, style="TFrame")
footer.pack(fill=tk.X, padx=12, pady=(0, 10))
self._footer_icon = self._load_png_icon("hrsys.png", max_px=18)
if self._footer_icon is not None:
icon_lbl = ttk.Label(footer, image=self._footer_icon, style="TLabel")
icon_lbl.pack(side=tk.LEFT)
icon_lbl.bind("<Button-1>", lambda e: self._open_link("https://hudsonriggs.systems"))
link_lbl = ttk.Label(footer, text="Created by HudsonRiggs.Systems", style="TLabel", foreground=MUTED)
link_lbl.pack(side=tk.LEFT, padx=(6, 0))
link_lbl.bind("<Button-1>", lambda e: self._open_link("https://hudsonriggs.systems"))
def _create_tree(self, parent, columns: List[str], ratios: List[float]): def _create_tree(self, parent, columns: List[str], ratios: List[float]):
tree = ttk.Treeview(parent, columns=columns, show="headings", height=12) tree = ttk.Treeview(parent, columns=columns, show="headings", height=12)
for col in columns: for col in columns:
@@ -219,12 +234,10 @@ class SeaLoaderApp(tk.Tk):
tree.column(col, width=50, anchor=tk.W, stretch=True) tree.column(col, width=50, anchor=tk.W, stretch=True)
vsb = ttk.Scrollbar(parent, orient="vertical", command=tree.yview) vsb = ttk.Scrollbar(parent, orient="vertical", command=tree.yview)
hsb = ttk.Scrollbar(parent, orient="horizontal", command=tree.xview) tree.configure(yscrollcommand=vsb.set)
tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
vsb.pack(side=tk.LEFT, fill=tk.Y) vsb.pack(side=tk.LEFT, fill=tk.Y)
hsb.pack(side=tk.BOTTOM, fill=tk.X)
self._bind_auto_columns(parent, tree, ratios) self._bind_auto_columns(parent, tree, ratios)
return tree return tree
@@ -348,12 +361,14 @@ class SeaLoaderApp(tk.Tk):
) )
text = ( text = (
"Usage:\n\n" "Usage:\n\n"
"1) Paste a Steam Workshop URL at the top and click 'Fetch Required Mods'.\n" "1) Download all required mods from Steam Workshop.\n"
" - Using subscribe to all wil work, you must load into seapower after they have downloaded then close the game.\n"
"2) Paste a Steam Workshop URL at the top and click 'Fetch Required Mods'.\n"
" - Right list shows required Mod IDs, names and whether they are installed.\n" " - Right list shows required Mod IDs, names and whether they are installed.\n"
"2) Installed mods load automatically from usersettings.ini (left list).\n" "3) Installed mods load automatically from usersettings.ini (left list).\n"
"3) Click 'Enable Matching Mods' to turn on any installed required mods.\n" "4) Click 'Enable Matching Mods' to turn on any installed required mods.\n"
" - A .bak backup of usersettings.ini is created before changes.\n" " - A .bak backup of usersettings.ini is created before changes.\n"
"4) Mods marked Missing must be installed separately in Sea Power.\n\n" "5) Mods marked Missing must be downloaded from the workshop see step 1.\n\n"
"Tips:\n" "Tips:\n"
"- Run with a custom INI path: python sealoader_gui.py \"<path-to-usersettings.ini>\"\n" "- Run with a custom INI path: python sealoader_gui.py \"<path-to-usersettings.ini>\"\n"
"- Press F1 to open this help.\n\n" "- Press F1 to open this help.\n\n"
@@ -370,6 +385,59 @@ class SeaLoaderApp(tk.Tk):
pass pass
return Path(__file__).resolve().parent / name return Path(__file__).resolve().parent / name
def _open_link(self, url: str) -> None:
try:
webbrowser.open(url)
except Exception:
pass
def _load_png_icon(self, filename: str, max_px: int = 18):
try:
png_path = self._resource_path(filename)
if not png_path.exists():
return None
img = tk.PhotoImage(file=str(png_path))
# Downscale if needed using integer subsample
sx = max(img.width() // max_px, 1)
sy = max(img.height() // max_px, 1)
scale = max(sx, sy)
if scale > 1:
img = img.subsample(scale, scale)
return img
except Exception:
return None
def _on_disable_all(self) -> None:
confirm = messagebox.askyesno("SeaLoader", "Disable all mods in usersettings.ini?")
if not confirm:
return
try:
self._disable_all_mods()
except Exception as exc:
messagebox.showerror("SeaLoader", f"Failed to disable all mods: {exc}")
return
self._load_installed_mods()
messagebox.showinfo("SeaLoader", "All mods disabled.")
def _disable_all_mods(self) -> None:
parser = configparser.ConfigParser()
parser.optionxform = str
parser.read(self.ini_path, encoding="utf-8")
section = "LoadOrder"
if section not in parser:
raise ValueError("[LoadOrder] section not found in usersettings.ini")
shutil.copyfile(self.ini_path, self.ini_path + ".bak")
for key, value in list(parser[section].items()):
if not key.lower().startswith("mod"):
continue
parts = value.split(",")
if not parts:
continue
left_token = parts[0].strip()
parser[section][key] = f"{left_token},False"
with open(self.ini_path, "w", encoding="utf-8") as f:
parser.write(f)
def main() -> None: def main() -> None:
ini_path = DEFAULT_INI_PATH ini_path = DEFAULT_INI_PATH

View File

@@ -128,10 +128,10 @@ def extract_required_item_ids(url: str) -> List[str]:
html = fetch_page(url) html = fetch_page(url)
found_ids = extract_required_item_ids_from_html(html) found_ids = extract_required_item_ids_from_html(html)
# Remove the current page's ID if present # Ensure the current page's ID is included (user wants main mod too)
current_id = parse_main_item_id(url) current_id = parse_main_item_id(url)
if current_id and current_id in found_ids: if current_id:
found_ids.remove(current_id) found_ids.add(current_id)
return sorted(found_ids, key=int) return sorted(found_ids, key=int)