Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
6b520a1bcc
|
|||
|
ca4fd2c9d5
|
|||
|
c23acb4472
|
|||
|
33fcd762f4
|
|||
|
ffbbf42d23
|
|||
| 8599690614 | |||
| 572d328890 | |||
| 10ae32812f | |||
| 39cfe519c6 | |||
| 19a326f8bf | |||
| 8ea5de3f54 | |||
|
992b629e24
|
|||
|
9223d5c5b2
|
|||
|
900cb4c210
|
|||
|
bd1c068e1c
|
|||
|
9afa50820b
|
|||
|
a51f341cf3
|
@@ -8,7 +8,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build-windows-exe:
|
build-windows-exe:
|
||||||
name: Build Windows EXE
|
name: Build Windows EXE
|
||||||
runs-on: windows-latest
|
runs-on: windows
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -19,22 +19,22 @@ jobs:
|
|||||||
python-version: '3.11'
|
python-version: '3.11'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
shell: pwsh
|
shell: powershell
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
if (Test-Path requirements.txt) { pip install -r requirements.txt }
|
if (Test-Path requirements.txt) { pip install -r requirements.txt }
|
||||||
pip install pyinstaller
|
pip install pyinstaller
|
||||||
|
|
||||||
- name: Build EXE with PyInstaller
|
- name: Build EXE with PyInstaller
|
||||||
shell: pwsh
|
shell: powershell
|
||||||
run: |
|
run: |
|
||||||
$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: pwsh
|
shell: powershell
|
||||||
run: |
|
run: |
|
||||||
New-Item -ItemType Directory -Force -Path dist_upload | Out-Null
|
New-Item -ItemType Directory -Force -Path dist_upload | Out-Null
|
||||||
Copy-Item dist\SeaLoader.exe dist_upload\SeaLoader.exe
|
Copy-Item dist\SeaLoader.exe dist_upload\SeaLoader.exe
|
||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
|
|
||||||
- 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: pwsh
|
shell: powershell
|
||||||
env:
|
env:
|
||||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
REPO: ${{ github.repository }}
|
REPO: ${{ github.repository }}
|
||||||
@@ -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/SeaLoader.exe
|
|
||||||
|
|
||||||
|
|||||||
54
.gitignore
vendored
Normal file
54
.gitignore
vendored
Normal 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
BIN
SeaLoader.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
Binary file not shown.
@@ -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 |
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user