16 Commits
0.0.2 ... 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
c23acb4472 powershell
Some checks failed
Build and Upload Release (Windows EXE) / Build Windows EXE (release) Failing after 3m41s
2025-09-16 14:33:17 -04:00
33fcd762f4 New Run On 2025-09-16 14:27:16 -04:00
ffbbf42d23 change runner location 2025-09-16 14:25:38 -04:00
8599690614 revert a51f341cf3
revert Attempt ubuntu action
2025-09-16 17:43:50 +00:00
572d328890 revert 9afa50820b
revert fix deploy
2025-09-16 17:43:38 +00:00
10ae32812f revert bd1c068e1c
revert add Ico
2025-09-16 17:43:28 +00:00
39cfe519c6 revert 900cb4c210
revert More Action Fixes
2025-09-16 17:43:16 +00:00
19a326f8bf revert 9223d5c5b2
revert Fix Workflow
2025-09-16 17:43:05 +00:00
8ea5de3f54 revert 992b629e24
revert Closer
2025-09-16 17:42:56 +00:00
992b629e24 Closer 2025-09-16 13:22:58 -04:00
9223d5c5b2 Fix Workflow 2025-09-16 13:21:18 -04:00
900cb4c210 More Action Fixes 2025-09-16 13:18:33 -04:00
bd1c068e1c add Ico 2025-09-16 13:15:25 -04:00
9afa50820b fix deploy 2025-09-16 13:09:37 -04:00
8 changed files with 162 additions and 84 deletions

View File

@@ -1,4 +1,4 @@
name: Build and Upload Release (Windows EXE from Ubuntu) name: Build and Upload Release (Windows EXE)
on: on:
release: release:
@@ -7,61 +7,54 @@ on:
jobs: jobs:
build-windows-exe: build-windows-exe:
name: Build Windows EXE (PyInstaller via Docker on Ubuntu) name: Build Windows EXE
runs-on: ubuntu-latest runs-on: windows
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Ensure tools (ImageMagick for .ico) - name: Set up Python
run: | uses: actions/setup-python@v5
sudo apt-get update with:
sudo apt-get install -y imagemagick python-version: '3.11'
- name: Prepare icon (optional) - name: Install dependencies
shell: powershell
run: | run: |
if [ -f "SeaLoader.png" ]; then python -m pip install --upgrade pip
convert SeaLoader.png -resize 256x256 SeaLoader.ico || true if (Test-Path requirements.txt) { pip install -r requirements.txt }
fi pip install pyinstaller
- name: Build EXE with PyInstaller (Windows target via Docker) - name: Build EXE with PyInstaller
shell: powershell
run: | run: |
docker run --rm -v "$PWD":/src cdrx/pyinstaller-windows:python3 \ $ErrorActionPreference = 'Stop'
"/bin/sh -lc 'set -e; if [ -f requirements.txt ]; then pip install -r requirements.txt; fi; pyinstaller --noconfirm --onefile --windowed sealoader_gui.py --name SeaLoader --add-data ""SeaLoader.png;."" $( [ -f SeaLoader.ico ] && echo --icon SeaLoader.ico )'" # Include SeaLoader.png so the packaged app icon in-app works
$addData = "SeaLoader.png;." # Windows uses ';' for --add-data
pyinstaller --noconfirm --onefile --windowed sealoader_gui.py --name SeaLoader --add-data "$addData" --icon SeaLoader.ico
- name: Prepare artifact - name: Prepare artifact
shell: powershell
run: | run: |
mkdir -p dist_upload New-Item -ItemType Directory -Force -Path dist_upload | Out-Null
if [ -f dist/windows/SeaLoader.exe ]; then Copy-Item dist\SeaLoader.exe dist_upload\SeaLoader.exe
cp dist/windows/SeaLoader.exe dist_upload/SeaLoader.exe if (Test-Path README.md) { Copy-Item README.md dist_upload\ }
elif [ -f dist/SeaLoader.exe ]; then if (Test-Path LICENSE) { Copy-Item LICENSE dist_upload\ }
cp dist/SeaLoader.exe dist_upload/SeaLoader.exe Compress-Archive -Path dist_upload\* -DestinationPath SeaLoader_Windows_x64.zip -Force
else
echo "SeaLoader.exe not found" && ls -R dist || true && exit 1
fi
[ -f README.md ] && cp README.md dist_upload/ || true
[ -f LICENSE ] && cp LICENSE dist_upload/ || true
zip -r9 SeaLoader_Windows_x64.zip dist_upload
- name: Upload asset to Release - name: Upload asset to Release
if: ${{ github.event_name == 'release' && github.event.action == 'published' }} if: ${{ github.event_name == 'release' && github.event.action == 'published' }}
shell: powershell
env: env:
TOKEN: ${{ secrets.GITHUB_TOKEN }} TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }} REPO: ${{ github.repository }}
RELEASE_ID: ${{ github.event.release.id }} RELEASE_ID: ${{ github.event.release.id }}
SERVER_URL: ${{ github.server_url }} SERVER_URL: ${{ github.server_url }}
run: | run: |
set -e $ErrorActionPreference = 'Stop'
UPLOAD_URL="$SERVER_URL/api/v1/repos/$REPO/releases/$RELEASE_ID/assets?name=SeaLoader_Windows_x64.zip" $uploadUrl = "$env:SERVER_URL/api/v1/repos/$env:REPO/releases/$env:RELEASE_ID/assets?name=SeaLoader_Windows_x64.zip"
echo "Uploading asset to $UPLOAD_URL" Write-Host "Uploading asset to $uploadUrl"
curl -fSL -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/zip" \ Invoke-RestMethod -Method Post -Uri $uploadUrl -Headers @{ Authorization = "token $env:TOKEN" } -ContentType "application/zip" -InFile "SeaLoader_Windows_x64.zip"
--data-binary @SeaLoader_Windows_x64.zip "$UPLOAD_URL"
- 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/windows/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)