Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 137 additions & 10 deletions .github/workflows/linux-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,31 @@ on:
branches:
- Testing
- testing
- main
workflow_dispatch:
inputs:
ref:
description: Git ref to build and scan
required: false
type: string
default: ''
update_information:
description: AppImageUpdate metadata to embed in the AppImage
required: false
type: string
default: ''
workflow_call:
inputs:
ref:
description: Git ref to build
required: false
type: string
default: ''
update_information:
description: AppImageUpdate metadata to embed in the AppImage
required: false
type: string
default: ''
outputs:
hash:
description: SHA256 hash of the AppImage
Expand All @@ -40,7 +51,7 @@ permissions:
jobs:
build-linux:
name: Build Linux Electron AppImage
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
outputs:
hash: ${{ steps.compute_hash.outputs.hash }}
vt_url: ${{ steps.compute_hash.outputs.vt_url }}
Expand All @@ -57,7 +68,7 @@ jobs:
APPIMAGE_PATH: dist/packages/PatchOpsIII.AppImage
ZSYNC_PATH: dist/packages/PatchOpsIII.AppImage.zsync
CSC_IDENTITY_AUTO_DISCOVERY: "false"
VT_API_KEY: ${{ secrets.VT_API_KEY }}
VT_ENABLED: ${{ secrets.VT_API_KEY != '' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Gate the VirusTotal download on VT_ENABLED

When VT_API_KEY is configured and the current VirusTotal CLI release is not cached, VT_ENABLED causes the cache, executable-check, and scan steps to run, but the download step still checks env.VT_API_KEY != ''. After this change that variable exists only in the scan step, so the download is skipped on a cache miss and the subsequent executable check fails because vt is absent, blocking Linux release builds with scanning enabled. Update the remaining download condition to use VT_ENABLED as well.

Useful? React with 👍 / 👎.

VT_CLI_ARCHIVE: vt-cli.zip
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -105,7 +116,23 @@ jobs:
- name: Install Linux packaging prerequisites
run: |
sudo apt-get update
sudo apt-get install -y binutils unzip zsync
sudo apt-get install -y binutils imagemagick squashfs-tools unzip x11-utils xdotool xvfb zsync
- name: Download appimagetool
run: |
set -euo pipefail
appimagetool_path="$RUNNER_TEMP/appimagetool-x86_64.AppImage"
runtime_path="$RUNNER_TEMP/runtime-x86_64"
curl --fail --location --retry 3 \
https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-x86_64.AppImage \
--output "$appimagetool_path"
echo "ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0 $appimagetool_path" | sha256sum --check
curl --fail --location --retry 3 \
https://github.com/AppImage/type2-runtime/releases/download/20251108/runtime-x86_64 \
--output "$runtime_path"
echo "2fca8b443c92510f1483a883f60061ad09b46b978b2631c807cd873a47ec260d $runtime_path" | sha256sum --check
chmod +x "$appimagetool_path"
echo "APPIMAGETOOL_PATH=$appimagetool_path" >> "$GITHUB_ENV"
echo "APPIMAGE_RUNTIME_PATH=$runtime_path" >> "$GITHUB_ENV"
- name: Install Python backend dependencies
run: |
python -m venv .venv
Expand All @@ -116,8 +143,11 @@ jobs:
run: bun install --frozen-lockfile
- name: Build Electron AppImage
run: bun run dist:linux
- name: Generate AppImage zsync metadata
- name: Repack AppImage with update support
env:
UPDATE_INFORMATION: ${{ inputs.update_information || (github.event_name == 'pull_request' && 'gh-releases-zsync|boggedbrush|PatchOpsIII|latest|PatchOpsIII.AppImage.zsync') || '' }}
run: |
set -euo pipefail
if [ ! -f "$APPIMAGE_PATH" ]; then
find dist -maxdepth 4 -type f -print
echo "$APPIMAGE_PATH was not produced" >&2
Expand All @@ -128,8 +158,103 @@ jobs:
appimage_dir="$(dirname "$APPIMAGE_PATH")"
(
cd "$appimage_dir"
zsyncmake -u "$appimage_name" -o "$appimage_name.zsync" "$appimage_name"
"./$appimage_name" --appimage-extract
mv "$appimage_name" "$appimage_name.electron-builder"
if [ -n "$UPDATE_INFORMATION" ]; then
ARCH=x86_64 APPIMAGE_EXTRACT_AND_RUN=1 "$APPIMAGETOOL_PATH" \
--runtime-file "$APPIMAGE_RUNTIME_PATH" \
--updateinformation "$UPDATE_INFORMATION" squashfs-root "$appimage_name"
else
ARCH=x86_64 APPIMAGE_EXTRACT_AND_RUN=1 "$APPIMAGETOOL_PATH" \
--runtime-file "$APPIMAGE_RUNTIME_PATH" \
squashfs-root "$appimage_name"
fi
rm -rf squashfs-root "$appimage_name.electron-builder"
chmod +x "$appimage_name"
if [ -z "$UPDATE_INFORMATION" ]; then
zsyncmake -u "$appimage_name" -o "$appimage_name.zsync" "$appimage_name"
fi
)
- name: Verify AppImage artifact
env:
UPDATE_INFORMATION: ${{ inputs.update_information || (github.event_name == 'pull_request' && 'gh-releases-zsync|boggedbrush|PatchOpsIII|latest|PatchOpsIII.AppImage.zsync') || '' }}
run: |
set -euo pipefail
test -s "$APPIMAGE_PATH"
test -s "$ZSYNC_PATH"
if [ -n "$UPDATE_INFORMATION" ]; then
actual_update_information="$("$APPIMAGE_PATH" --appimage-updateinformation)"
if [ "$actual_update_information" != "$UPDATE_INFORMATION" ]; then
printf 'Expected update information: %s\nFound: %s\n' \
"$UPDATE_INFORMATION" "$actual_update_information" >&2
exit 1
fi
fi
extraction_dir="$(mktemp -d)"
(cd "$extraction_dir" && "$GITHUB_WORKSPACE/$APPIMAGE_PATH" --appimage-extract >/dev/null)
test -x "$extraction_dir/squashfs-root/AppRun"
rm -rf "$extraction_dir"
- name: Smoke test AppImage at 800x600
env:
APPIMAGE_EXTRACT_AND_RUN: "1"
SCREENSHOT_PATH: ${{ runner.temp }}/PatchOpsIII-800x600.png
run: |
set -euo pipefail
xvfb-run --auto-servernum --server-args="-screen 0 800x600x24" bash -euo pipefail <<'SMOKE_TEST'
screen_info="$(xwininfo -root)"
grep -Eq "Width: +800" <<< "$screen_info"
grep -Eq "Height: +600" <<< "$screen_info"

"$GITHUB_WORKSPACE/$APPIMAGE_PATH" --no-sandbox --disable-gpu &
app_pid=$!
cleanup() {
if kill -0 "$app_pid" 2>/dev/null; then
kill "$app_pid" 2>/dev/null || true
wait "$app_pid" 2>/dev/null || true
fi
}
trap cleanup EXIT

window_id=""
for _ in {1..30}; do
if ! kill -0 "$app_pid" 2>/dev/null; then
if wait "$app_pid"; then status=0; else status=$?; fi
echo "AppImage exited before opening its window (status $status)" >&2
exit 1
fi
if window_id="$(xdotool search --onlyvisible --name "PatchOpsIII" 2>/dev/null | head -n 1)" && [ -n "$window_id" ]; then
break
fi
sleep 1
done

if [ -z "$window_id" ]; then
echo "PatchOpsIII window did not appear within 30 seconds" >&2
exit 1
fi

window_info="$(xwininfo -id "$window_id")"
window_x="$(awk '/Absolute upper-left X:/ { print $4; exit }' <<< "$window_info")"
window_y="$(awk '/Absolute upper-left Y:/ { print $4; exit }' <<< "$window_info")"
window_width="$(awk '/Width:/ { print $2; exit }' <<< "$window_info")"
window_height="$(awk '/Height:/ { print $2; exit }' <<< "$window_info")"
if (( window_x < 0 || window_y < 0 || window_x + window_width > 800 || window_y + window_height > 600 )); then
echo "AppImage window exceeds the 800x600 display: ${window_width}x${window_height} at ${window_x},${window_y}" >&2
exit 1
fi

sleep 2
import -window root "$SCREENSHOT_PATH"
test -s "$SCREENSHOT_PATH"
sleep 20
kill -0 "$app_pid"
SMOKE_TEST
- name: Upload AppImage visual review screenshot
uses: actions/upload-artifact@v4
with:
name: PatchOpsIII-AppImage-800x600
path: ${{ runner.temp }}/PatchOpsIII-800x600.png
if-no-files-found: error
- name: Compute AppImage hash
id: compute_hash
run: |
Expand All @@ -140,7 +265,7 @@ jobs:
printf "vt_url=%s\n" "$vt_url" >> "$GITHUB_OUTPUT"
- name: Determine VirusTotal CLI release
id: vt_release
if: ${{ env.VT_API_KEY != '' }}
if: ${{ env.VT_ENABLED == 'true' }}
run: |
set -euo pipefail
python <<'PY'
Expand Down Expand Up @@ -171,29 +296,31 @@ jobs:
PY
- name: Cache VirusTotal CLI
id: cache-vt
if: ${{ env.VT_API_KEY != '' }}
if: ${{ env.VT_ENABLED == 'true' }}
uses: actions/cache@v4
with:
path: |
vt
${{ env.VT_CLI_ARCHIVE }}
key: vt-${{ runner.os }}-${{ steps.vt_release.outputs.tag }}
- name: Download VirusTotal CLI
if: ${{ env.VT_API_KEY != '' && steps.cache-vt.outputs.cache-hit != 'true' }}
if: ${{ env.VT_ENABLED == 'true' && steps.cache-vt.outputs.cache-hit != 'true' }}
run: |
set -euo pipefail
curl -sSL "${{ steps.vt_release.outputs.url }}" -o "$VT_CLI_ARCHIVE"
unzip -q -o "$VT_CLI_ARCHIVE"
- name: Ensure VirusTotal CLI executable
if: ${{ env.VT_API_KEY != '' }}
if: ${{ env.VT_ENABLED == 'true' }}
run: |
if [ ! -f vt ]; then
echo 'VirusTotal CLI binary not found' >&2
exit 1
fi
chmod +x vt
- name: VirusTotal scan
if: ${{ env.VT_API_KEY != '' }}
if: ${{ env.VT_ENABLED == 'true' }}
env:
VT_API_KEY: ${{ secrets.VT_API_KEY }}
run: ./vt scan file "$APPIMAGE_PATH" --apikey "$VT_API_KEY"
- name: Upload Linux artifact
uses: actions/upload-artifact@v4
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release-stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ jobs:
uses: ./.github/workflows/linux-build.yml
with:
ref: ${{ needs.prepare.outputs.tag }}
update_information: gh-releases-zsync|boggedbrush|PatchOpsIII|latest|PatchOpsIII.AppImage.zsync
secrets: inherit

release:
Expand Down
45 changes: 45 additions & 0 deletions Release Notes/PatchOpsIII v1.3.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# PatchOpsIII v1.3.3 Release Notes

## Overview

PatchOpsIII v1.3.3 improves Linux AppImage compatibility and update support, and makes the desktop window usable on 800×600 displays.

## Highlights

- Build the Linux AppImage on Ubuntu 22.04 for compatibility with older glibc versions.
- Embed GitHub Release zsync update information in stable AppImages.
- Fit the desktop window to the available display and improve the compact dashboard layout.

## Detailed Changes

### Linux & Steam Deck

- Repack the AppImage with pinned, SHA-256-verified AppImageKit tools and runtime.
- Generate and publish matching zsync metadata for the stable AppImage.
- Verify AppImage extraction, embedded update information, and startup in the Linux build workflow.

### Desktop Layout

- Constrain the initial window size and minimum size to the available display.
- Keep navigation labels and dashboard controls readable at 800×600.
- Allow lower settings to remain accessible by scrolling.

## Upgrade Notes

- No settings migration is required.

## Downloads & Verification

The release workflow fills in the download URLs, SHA-256 hashes, and VirusTotal links below when publishing.

- **Windows MSI**
- Download: [PatchOpsIII v1.3.3 for Windows]({{WINDOWS_DOWNLOAD_URL}})
- SHA256: `{{WINDOWS_SHA256}}`
- VirusTotal: [Windows MSI scan]({{WINDOWS_VT_URL}})
- Older updater compatibility: [PatchOpsIII.exe](https://github.com/boggedbrush/PatchOpsIII/releases/download/v1.3.3/PatchOpsIII.exe)

- **Linux & Steam Deck**
- Download: [PatchOpsIII v1.3.3 for Linux & Steam Deck]({{LINUX_DOWNLOAD_URL}})
- SHA256: `{{LINUX_SHA256}}`
- VirusTotal: [Linux scan]({{LINUX_VT_URL}})
- Stable AppImages include update information and a matching zsync file.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "patchopsiii-electron",
"version": "v1.3.2",
"version": "v1.3.3",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add matching v1.3.3 release notes before bumping version

With this version set to v1.3.3, both release workflows are blocked because the repository contains release notes only through v1.3.2. I checked .github/workflows/release-stable.yml and release-beta.yml: their metadata steps select an existing note and explicitly exit when its version differs from package.json (stable lines 218–222 and beta lines 220–224), so even the default latest invocation now fails during preparation. Add a v1.3.3 release-notes file or defer this version bump.

Useful? React with 👍 / 👎.

"description": "Electron control center for Call of Duty: Black Ops III maintenance, mod setup, and performance tuning.",
"author": "boggedbrush",
"private": true,
Expand Down
11 changes: 6 additions & 5 deletions src/main/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { app, BrowserWindow, dialog, ipcMain, shell } from "electron";
import { app, BrowserWindow, dialog, ipcMain, screen, shell } from "electron";
import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from "node:child_process";
import { existsSync } from "node:fs";
import { fileURLToPath } from "node:url";
Expand Down Expand Up @@ -114,12 +114,13 @@ async function createWindow() {

const isWindows = process.platform === "win32";
const isMac = process.platform === "darwin";
const { width: displayWidth, height: displayHeight } = screen.getPrimaryDisplay().workAreaSize;

mainWindow = new BrowserWindow({
width: 1320,
height: 860,
minWidth: 1100,
minHeight: 720,
width: Math.min(1320, displayWidth),
height: Math.min(860, displayHeight),
minWidth: Math.min(800, displayWidth),
minHeight: Math.min(600, displayHeight),
title: "PatchOpsIII",
backgroundColor: isWindows ? "#00000000" : "#080806",
backgroundMaterial: isWindows ? "mica" : undefined,
Expand Down
31 changes: 31 additions & 0 deletions src/renderer/styles/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -2890,6 +2890,7 @@ img {

.content-panel > .dashboard-split {
margin-top: 0;
align-items: start;
}

.nav-panel {
Expand Down Expand Up @@ -3007,6 +3008,36 @@ img {
}
}

@media (max-width: 840px) {
.tool-button.browse {
min-width: 140px;
}

.dashboard-split .profile-state {
flex: 0 0 92px;
line-height: 1.15;
white-space: normal;
text-overflow: clip;
}

.nav-button {
grid-template-columns: 1fr;
justify-items: center;
gap: 4px;
min-height: 52px;
padding: 5px 4px;
text-align: center;
}

.nav-button span {
font-size: 12px;
line-height: 1.1;
white-space: normal;
text-overflow: clip;
overflow-wrap: anywhere;
}
}

@media (max-width: 760px) {
:root {
--app-pad: 12px;
Expand Down
Loading