Add Windows app, zip download and SignPath signing
Build Windows app / app (push) Waiting to run

This commit is contained in:
Justin Oros
2026-09-28 14:24:42 -07:00
parent 079caa599d
commit a22512b506
18 changed files with 1494 additions and 18 deletions
+39 -7
View File
@@ -336,6 +336,39 @@ function versionLine(pkg, status) {
return items;
}
function formatSize(bytes) {
if (!bytes) return "";
if (bytes >= 1048576) return `${(bytes / 1048576).toFixed(1)} MB`;
return `${Math.max(1, Math.round(bytes / 1024))} KB`;
}
function renderGet() {
const node = $("get");
const { app, bundle } = state.manifest;
const cards = [];
if (app) {
cards.push(
el("div", { class: "get-card" },
el("h2", {}, "Windows app"),
el("p", {}, "Finds your game automatically, even inside Program Files, and installs RedLoader and mods with one click."),
el("a", { class: "button primary", href: app.url }, `Download the app (${formatSize(app.size)})`),
el("p", { class: "get-note" }, `Version ${app.version}. If Windows shows a warning, click More info, then Run anyway.`),
),
);
}
if (bundle) {
cards.push(
el("div", { class: "get-card" },
el("h2", {}, "Zip download"),
el("p", {}, "RedLoader and every mod in one zip. Extract it into your Sons of the Forest folder, the one that contains SonsOfTheForest.exe."),
el("a", { class: "button secondary", href: bundle.url, download: "sotf-mods.zip" }, `Download zip (${formatSize(bundle.size)})`),
),
);
}
node.hidden = !cards.length;
node.replaceChildren(...cards);
}
function renderFolder() {
const node = $("folder");
node.replaceChildren();
@@ -344,8 +377,8 @@ function renderFolder() {
if (!supportsFs) {
node.append(
el("div", { class: "folder-text" },
el("p", { class: "folder-name" }, "This browser can't install mods"),
el("p", { class: "folder-help" }, "Open this page in Chrome or Edge on Windows to install mods with one click. You can still download each mod from its release page below."),
el("p", { class: "folder-name" }, "Install from this page"),
el("p", { class: "folder-help" }, "This browser can't write to your game folder. Use the Windows app or the zip download above."),
),
);
return;
@@ -378,15 +411,13 @@ function renderFolder() {
node.append(
el("div", { class: "folder-text" },
el("p", { class: "folder-name" }, "Choose your game folder"),
el("p", { class: "folder-name" }, "Install from this page"),
el("p", { class: "folder-help" },
state.folderError ? `${state.folderError} ` : "",
"On Steam it's usually ",
el("code", {}, "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Sons Of The Forest"),
". In Steam you can find it with Manage, Browse local files.",
"Chrome and Edge can't open folders inside Program Files, which is where Steam installs games by default. If Chrome says the folder contains system files, use the Windows app instead.",
),
),
el("button", { class: "primary", disabled: state.busy, onclick: chooseFolder }, "Choose game folder"),
el("button", { class: "secondary", disabled: state.busy, onclick: chooseFolder }, "Choose game folder"),
);
}
@@ -486,6 +517,7 @@ function renderFooter() {
function render() {
if (!state.manifest) return;
renderGet();
renderFolder();
renderRedloader();
renderMods();
+3 -2
View File
@@ -15,9 +15,10 @@
<main class="page">
<header class="masthead">
<h1>Sons of the Forest mod loader</h1>
<p class="lede">Pick your game folder once, then install, update and remove mods with one click.</p>
<p class="lede">Install, update and remove Sons of the Forest mods with one click.</p>
</header>
<section id="folder" class="folder" aria-label="Game folder"></section>
<section id="get" class="get" aria-label="Ways to install" hidden></section>
<section id="folder" class="folder" aria-label="Install from this page"></section>
<section id="redloader" class="block" aria-labelledby="redloader-title" hidden></section>
<section id="mods" class="block" aria-labelledby="mods-title"></section>
<p id="status" class="status" role="status" aria-live="polite"></p>
+72 -1
View File
@@ -74,13 +74,84 @@ a:hover {
font-size: 1.15rem;
}
.get {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
gap: 1rem;
margin-top: 2.75rem;
}
.get-card {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 1.35rem 1.5rem 1.4rem;
background: var(--bark);
border: 1px solid var(--bark-line);
border-radius: 6px;
}
.get-card:first-child {
border-top: 5px solid var(--flare);
}
.get-card h2 {
margin: 0;
font-family: var(--display);
font-weight: 700;
font-size: 1.75rem;
line-height: 1.1;
}
.get-card p {
margin: 0.4rem 0 1.1rem;
color: var(--lichen);
}
.get-card .get-note {
margin: 0.8rem 0 0;
font-size: 0.875rem;
}
a.button {
display: inline-block;
margin-top: auto;
padding: 0.6rem 1.1rem;
border: 1px solid transparent;
border-radius: 4px;
font-weight: 600;
font-size: 0.95rem;
line-height: 1.2;
text-decoration: none;
}
a.button.primary {
background: var(--flare);
color: var(--flare-ink);
}
a.button.primary:hover {
background: var(--flare-hover);
color: var(--flare-ink);
}
a.button.secondary {
color: var(--fog);
border-color: var(--bark-line);
}
a.button.secondary:hover {
border-color: var(--lichen);
color: var(--fog);
}
.folder {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 1rem 1.5rem;
margin-top: 2.75rem;
margin-top: 1rem;
padding: 1.25rem 1.5rem;
background: var(--bark);
border: 1px solid var(--bark-line);