Windows exes carry the coin icon and a version block (the project lead's rule, 4 Oct 2026)

Every Windows executable now ships with the coin icon Explorer shows and the version block Properties shows
(CompanyName Igneum, ProductName Igneum Miner, FileDescription per exe, 0.3.0, LegalCopyright Igneum contributors),
like the Mac app and DMG already do.

- igneumd.exe, igneum-miner.exe: packaging/windows/embed-resources.sh now takes the worktree and target dir
  (defaults vendor/igneum-node-v4 and vendor/igneum-node/target-integration) and relinks with a linker shim first in
  PATH instead of `cargo rustc -- -C link-arg`: cargo rustc takes one package and unifies features differently from
  the two-package cross-build (300 crates differ), and a configured linker is fingerprinted and rebuilds everything;
  the PATH shim changes neither. .rc files moved to 0.3.0.
- igneum-worker-cuda.exe, igneum-worker-opencl.exe: proto-cuda/nvrtc/build-windows.sh compiles the two new .rc
  files with windres and links the objects; the icon is made with make-icons.py if missing.
- igneum-app.exe: app/igneum-app/build.rs runs windres on resources/igneum-app.rc for Windows targets and links it
  (cargo:rustc-link-arg-bins, no crate dependency); it fails the build if the .rc version drifts from Cargo.toml.
- packaging/windows/resources/verify-exe.py: small PE parser that checks the .rsrc section, icon group and version
  strings on the Mac; both build scripts call it, and it checks every exe inside the packages.
- proto-opencl/cl_dynamic.h: clGetEventInfo added to the run-time loader table (4714b51 added the call in host.c
  without it, so the one-click OpenCL worker no longer linked under IGNEUM_CL_DYNAMIC).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-04 10:44:20 +00:00
parent 4714b510f2
commit d69d70c3fc
11 changed files with 498 additions and 100 deletions

View file

@ -5,6 +5,7 @@ edition = "2021"
description = "Igneum Miner engine: supervises the node, the miner and the GPU workers, and serves the dashboard on 127.0.0.1"
license = "MIT"
publish = false
build = "build.rs" # Windows targets: links resources/igneum-app.rc (coin icon, version block) into the exe
[[bin]]
name = "igneum-app"

80
app/igneum-app/build.rs Normal file
View file

@ -0,0 +1,80 @@
// Build script for igneum-app. On a Windows target it compiles resources/igneum-app.rc (the coin icon Explorer shows
// and the version block under Properties > Details) with windres and links the object into igneum-app.exe. Other
// targets: nothing. the project lead's rule (4 October 2026): every shipped exe carries the coin icon and a version block, like the
// Mac app and DMG. No crate dependency: windres is called directly (x86_64-w64-mingw32-windres from Homebrew mingw-w64
// on the Mac, windres from MSYS2 on a PC; IGNEUM_WINDRES names another one).
use std::env;
use std::fs;
use std::path::PathBuf;
use std::process::Command;
fn main() {
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
let rc = manifest.join("resources").join("igneum-app.rc");
let icons = manifest.join("..").join("..").join("brand").join("icons");
let ico = icons.join("igneum.ico");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed={}", rc.display());
println!("cargo:rerun-if-changed={}", ico.display());
println!("cargo:rerun-if-env-changed=IGNEUM_WINDRES");
if env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") {
return;
}
// The .rc carries the version by hand; it must agree with Cargo.toml so Properties shows the number the app reports.
let version = env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION");
let text = fs::read_to_string(&rc).unwrap_or_else(|e| panic!("cannot read {}: {e}", rc.display()));
let numeric = format!("{},0", version.replace('.', ","));
if !text.contains(&format!("\"{version}\"")) || !text.contains(&numeric) {
panic!(
"{} must carry version {version} (FILEVERSION/PRODUCTVERSION {numeric}, FileVersion/ProductVersion \"{version}\")",
rc.display()
);
}
if !ico.is_file() {
panic!("no {}; run python3 brand/icons/make-icons.py first", ico.display());
}
let windres = match find_windres() {
Some(w) => w,
None => {
if env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("msvc") {
println!("cargo:warning=no windres found: igneum-app.exe is built without the coin icon and version block (set IGNEUM_WINDRES)");
return;
}
panic!("no windres found (brew install mingw-w64, or set IGNEUM_WINDRES): igneum-app.exe must carry the coin icon and version block");
}
};
let out = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")).join("igneum-app.res.o");
let status = Command::new(&windres)
.arg("-I").arg(&icons)
.arg("-i").arg(&rc)
.arg("-O").arg("coff")
.arg("-o").arg(&out)
.status()
.unwrap_or_else(|e| panic!("cannot run {windres}: {e}"));
if !status.success() {
panic!("{windres} failed on {} ({status})", rc.display());
}
println!("cargo:rustc-link-arg-bins={}", out.display());
}
fn find_windres() -> Option<String> {
let mut candidates = Vec::new();
if let Ok(w) = env::var("IGNEUM_WINDRES") {
if !w.is_empty() {
candidates.push(w);
}
}
let prefix = match env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default().as_str() {
"x86_64" => Some("x86_64-w64-mingw32"),
"x86" => Some("i686-w64-mingw32"),
"aarch64" => Some("aarch64-w64-mingw32"),
_ => None,
};
if let Some(p) = prefix {
candidates.push(format!("{p}-windres"));
}
candidates.push("windres".to_string());
candidates
.into_iter()
.find(|c| Command::new(c).arg("--version").output().map(|o| o.status.success()).unwrap_or(false))
}

View file

@ -0,0 +1,35 @@
// Windows resources for igneum-app.exe: the coin icon Explorer shows and the version block under Properties > Details.
// Compiled with x86_64-w64-mingw32-windres (the icon path is relative to brand/icons, passed with -I).
// the project lead's rule (4 October 2026): every shipped exe carries the coin icon and a version block, like the Mac app and DMG.
#include <winver.h>
1 ICON "igneum.ico"
1 VERSIONINFO
FILEVERSION 0,3,0,0
PRODUCTVERSION 0,3,0,0
FILEFLAGSMASK 0x3fL
FILEFLAGS 0x0L
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904B0"
BEGIN
VALUE "CompanyName", "Igneum"
VALUE "FileDescription", "Igneum Miner engine"
VALUE "FileVersion", "0.3.0"
VALUE "InternalName", "igneum-app"
VALUE "LegalCopyright", "Igneum contributors"
VALUE "OriginalFilename", "igneum-app.exe"
VALUE "ProductName", "Igneum Miner"
VALUE "ProductVersion", "0.3.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

View file

@ -1,61 +1,107 @@
#!/usr/bin/env bash
# Gives igneumd.exe and igneum-miner.exe the coin icon and a version block (CompanyName Igneum, ProductName
# Igneum Miner, 0.1.0). Two steps, both on the Mac:
# Gives igneumd.exe and igneum-miner.exe the coin icon and the version block Explorer shows under Properties > Details
# (CompanyName Igneum, ProductName Igneum Miner, 0.3.0). the project lead's rule (4 October 2026): every shipped exe carries the
# coin icon and a version block, like the Mac app and DMG. Two steps, both on the Mac:
# 1. windres compiles resources/igneumd.rc and resources/igneum-miner.rc into COFF objects (cheap, always done).
# 2. --relink runs `cargo rustc` for each binary with `-C link-arg=<object>`: only the final crate is recompiled and
# relinked, every dependency comes from the cache, so it costs a few minutes after a normal cross-build. The
# objects are linked in as extra inputs, nothing in vendor/ changes. Without --relink the exact commands are
# printed, and build-installer.ps1 stamps the resources with rcedit on the PC instead.
# The resulting exes land where proto-cuda/windows-app/make-package.sh already picks them up
# (vendor/igneum-node-win/target/x86_64-pc-windows-gnu/release/).
# 2. --relink runs the same cargo build as proto-cuda/windows-node/cross-build.sh (same packages, features and
# environment, so every cached dependency is reused) with a linker shim first in PATH: a script named
# x86_64-w64-mingw32-gcc (the linker rustc picks for x86_64-pc-windows-gnu) that runs the real one and appends
# the matching object whenever the output being linked is igneumd-<hash>.exe or igneum-miner-<hash>.exe. The two
# exes are removed from deps/ first so cargo relinks them even when nothing changed. Nothing in vendor/ changes.
# Why not `cargo rustc -- -C link-arg=<obj>`: it takes one package, and a one-package selection unifies features
# differently from the two-package cross-build (300 crates differ, checked 4 Oct 2026), so it would rebuild half
# the tree and ship a differently-featured miner. Why PATH and not CARGO_TARGET_..._LINKER: cargo puts the
# configured linker in its fingerprints, so that rebuilds every crate (checked on app/igneum-app the same day).
# The exes land in <target dir>/x86_64-pc-windows-gnu/release/, where proto-cuda/windows-app/make-package.sh and
# packaging/windows/make-payload.sh pick them up. Without --relink only the objects are compiled and the command is
# printed (build-installer.ps1 can stamp the resources with rcedit on the PC instead).
#
# packaging/windows/embed-resources.sh compile the objects, print the relink commands
# packaging/windows/embed-resources.sh --relink also relink both exes (needs a completed cross-build in the cache)
# Usage: packaging/windows/embed-resources.sh [--relink] [worktree] [target dir]
# worktree the node checkout to build, default vendor/igneum-node-v4 (or NODE_WORKTREE)
# target dir CARGO_TARGET_DIR, default vendor/igneum-node/target-integration (or CARGO_TARGET_DIR); relative paths
# are taken from the repository root
# JOBS=n cargo jobs, default 4
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)"
ICONS="$ROOT/brand/icons"
OUT="$HERE/build/res"
NODE="${NODE_WORKTREE:-$ROOT/vendor/igneum-node-win}"
VERIFY="$HERE/resources/verify-exe.py"
MINGW="/opt/homebrew/opt/mingw-w64/toolchain-x86_64/x86_64-w64-mingw32"
RELINK=0
if [ "${1:-}" = "--relink" ]; then RELINK=1; shift; fi
NODE="${1:-${NODE_WORKTREE:-$ROOT/vendor/igneum-node-v4}}"
TD="${2:-${CARGO_TARGET_DIR:-$ROOT/vendor/igneum-node/target-integration}}"
case "$NODE" in /*) ;; *) NODE="$ROOT/$NODE" ;; esac
case "$TD" in /*) ;; *) TD="$ROOT/$TD" ;; esac
JOBS="${JOBS:-4}"
command -v x86_64-w64-mingw32-windres >/dev/null || { echo "x86_64-w64-mingw32-windres not found (brew install mingw-w64)" >&2; exit 1; }
[ -f "$ICONS/igneum.ico" ] || { echo "no $ICONS/igneum.ico; run python3 brand/icons/make-icons.py first" >&2; exit 1; }
[ -f "$ICONS/igneum.ico" ] || { echo "no $ICONS/igneum.ico, making the icons"; python3 "$ICONS/make-icons.py"; }
[ -d "$NODE" ] || { echo "no worktree at $NODE" >&2; exit 1; }
mkdir -p "$OUT"
for name in igneumd igneum-miner; do
x86_64-w64-mingw32-windres -I "$ICONS" -i "$HERE/resources/$name.rc" -O coff -o "$OUT/$name.res.o"
echo "compiled $OUT/$name.res.o ($(stat -f %z "$OUT/$name.res.o") bytes)"
done
# The linker shim: the real x86_64-w64-mingw32-gcc plus the resource object for the exe being linked
REAL_GCC="$(command -v x86_64-w64-mingw32-gcc)" || { echo "x86_64-w64-mingw32-gcc not found" >&2; exit 1; }
SHIM="$HERE/build/linker-shim"
rm -rf "$SHIM" && mkdir -p "$SHIM"
cat > "$SHIM/x86_64-w64-mingw32-gcc" <<SHIM_SH
#!/bin/bash
# Written by packaging/windows/embed-resources.sh: runs the real linker and appends the resource object for the exe
# being linked (igneumd-<hash>.exe or igneum-miner-<hash>.exe, found after -o, also inside a @response file).
REAL="$REAL_GCC"
RES="$OUT"
out=""
prev=""
for a in "\$@"; do
case "\$a" in @*) if [ -f "\${a#@}" ]; then
p2=""; for w in \$(cat "\${a#@}"); do if [ "\$p2" = "-o" ]; then out="\$w"; fi; p2="\$w"; done
fi ;; esac
if [ "\$prev" = "-o" ]; then out="\$a"; fi
prev="\$a"
done
case "\$(basename "\$out")" in
igneumd-*.exe|igneumd.exe) exec "\$REAL" "\$@" "\$RES/igneumd.res.o" ;;
igneum-miner-*.exe|igneum_miner-*.exe|igneum-miner.exe) exec "\$REAL" "\$@" "\$RES/igneum-miner.res.o" ;;
esac
exec "\$REAL" "\$@"
SHIM_SH
chmod +x "$SHIM/x86_64-w64-mingw32-gcc"
# The same environment as proto-cuda/windows-node/cross-build.sh, so the cached dependencies are reused.
export PATH="$HOME/.cargo/bin:/opt/homebrew/bin:$PATH"
export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-target}"
export CARGO_TARGET_DIR="$TD"
export CC_x86_64_pc_windows_gnu=x86_64-w64-mingw32-gcc
export CXX_x86_64_pc_windows_gnu=x86_64-w64-mingw32-g++
export AR_x86_64_pc_windows_gnu=x86_64-w64-mingw32-ar
export LIBCLANG_PATH=/opt/homebrew/opt/llvm/lib
export BINDGEN_EXTRA_CLANG_ARGS_x86_64_pc_windows_gnu="--target=x86_64-w64-mingw32 --sysroot=$MINGW -I$MINGW/include"
export CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUSTFLAGS="-C link-arg=-static -C link-arg=-static-libgcc"
REL="$TD/x86_64-pc-windows-gnu/release"
BUILD="nice -n 19 cargo build --release -j $JOBS -p kaspad -p igneum-miner --features igneum-pow --target x86_64-pc-windows-gnu"
relink_cmd() { # $1 package, $2 bin, $3 object
printf 'nice -n 19 cargo rustc --release -j 2 -p %s --bin %s --features igneum-pow --target x86_64-pc-windows-gnu -- -C link-arg=%q\n' "$1" "$2" "$3"
}
if [ "${1:-}" = "--relink" ]; then
if [ "$RELINK" = 1 ]; then
cd "$NODE"
for spec in "kaspad igneumd" "igneum-miner igneum-miner"; do
set -- $spec
echo "relinking $2.exe"
eval "$(relink_cmd "$1" "$2" "$OUT/$2.res.o")"
exe="$CARGO_TARGET_DIR/x86_64-pc-windows-gnu/release/$2.exe"
x86_64-w64-mingw32-objdump -h "$exe" | grep -q '\.rsrc' && echo "$exe: .rsrc section present ($(stat -f %z "$exe") bytes)" || { echo "$exe has no .rsrc section" >&2; exit 1; }
echo "worktree $NODE at $(git rev-parse --short HEAD) ($(git status --porcelain | grep -v '^??' | wc -l | tr -d ' ') modified files), target dir $TD"
rm -f "$REL"/deps/igneumd-*.exe "$REL"/deps/igneum-miner-*.exe "$REL"/deps/igneum_miner-*.exe
PATH="$SHIM:$PATH" $BUILD
for exe in igneumd igneum-miner; do
f="$REL/$exe.exe"
x86_64-w64-mingw32-objdump -h "$f" | grep -q '\.rsrc' || { echo "$f has no .rsrc section" >&2; exit 1; }
echo "$f: $(stat -f %z "$f") bytes; DLLs: $(x86_64-w64-mingw32-objdump -p "$f" | grep 'DLL Name' | awk '{print $3}' | sort -u | tr '\n' ' ')"
done
python3 "$VERIFY" --version 0.3.0 "$REL/igneumd.exe" "$REL/igneum-miner.exe"
else
cat <<TXT
Not relinked (no --relink). After the next cross-build (proto-cuda/windows-node/cross-build.sh), from $NODE
with the same environment variables as that script, run:
$(relink_cmd kaspad igneumd "$OUT/igneumd.res.o")
$(relink_cmd igneum-miner igneum-miner "$OUT/igneum-miner.res.o")
Then proto-cuda/windows-app/make-package.sh ships the branded exes. Check with:
x86_64-w64-mingw32-objdump -h vendor/igneum-node-win/target/x86_64-pc-windows-gnu/release/igneum-miner.exe | grep rsrc
Not relinked (no --relink). To relink from $NODE into $TD, run with the same environment as
proto-cuda/windows-node/cross-build.sh, the shim first in PATH, and the old exes removed from $REL/deps/:
PATH=$SHIM:\$PATH $BUILD
Or simply: $0 --relink${1:+ $1}${2:+ $2}
Then proto-cuda/windows-app/make-package.sh and packaging/windows/make-payload.sh ship the branded exes. Check with:
python3 $VERIFY --version 0.3.0 $REL/igneumd.exe $REL/igneum-miner.exe
TXT
fi

View file

@ -1,34 +1,35 @@
// Windows resources for igneum-miner.exe: the coin icon and the version block Explorer shows under Properties > Details.
// Compiled by embed-resources.sh with x86_64-w64-mingw32-windres (icon path relative to brand/icons, passed with -I).
#include <winver.h>
1 ICON "igneum.ico"
1 VERSIONINFO
FILEVERSION 0,1,0,0
PRODUCTVERSION 0,1,0,0
FILEFLAGSMASK 0x3fL
FILEFLAGS 0x0L
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904B0"
BEGIN
VALUE "CompanyName", "Igneum"
VALUE "FileDescription", "Igneum Miner"
VALUE "FileVersion", "0.1.0"
VALUE "InternalName", "igneum-miner"
VALUE "LegalCopyright", "Igneum. Nothing is bought or sold."
VALUE "OriginalFilename", "igneum-miner.exe"
VALUE "ProductName", "Igneum Miner"
VALUE "ProductVersion", "0.1.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
// Windows resources for igneum-miner.exe: the coin icon Explorer shows and the version block under Properties > Details.
// Compiled with x86_64-w64-mingw32-windres (the icon path is relative to brand/icons, passed with -I).
// the project lead's rule (4 October 2026): every shipped exe carries the coin icon and a version block, like the Mac app and DMG.
#include <winver.h>
1 ICON "igneum.ico"
1 VERSIONINFO
FILEVERSION 0,3,0,0
PRODUCTVERSION 0,3,0,0
FILEFLAGSMASK 0x3fL
FILEFLAGS 0x0L
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904B0"
BEGIN
VALUE "CompanyName", "Igneum"
VALUE "FileDescription", "Igneum Miner lottery miner"
VALUE "FileVersion", "0.3.0"
VALUE "InternalName", "igneum-miner"
VALUE "LegalCopyright", "Igneum contributors"
VALUE "OriginalFilename", "igneum-miner.exe"
VALUE "ProductName", "Igneum Miner"
VALUE "ProductVersion", "0.3.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

View file

@ -1,34 +1,35 @@
// Windows resources for igneumd.exe: the coin icon and the version block Explorer shows under Properties > Details.
// Compiled by embed-resources.sh with x86_64-w64-mingw32-windres (icon path relative to brand/icons, passed with -I).
#include <winver.h>
1 ICON "igneum.ico"
1 VERSIONINFO
FILEVERSION 0,1,0,0
PRODUCTVERSION 0,1,0,0
FILEFLAGSMASK 0x3fL
FILEFLAGS 0x0L
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904B0"
BEGIN
VALUE "CompanyName", "Igneum"
VALUE "FileDescription", "Igneum Miner node"
VALUE "FileVersion", "0.1.0"
VALUE "InternalName", "igneumd"
VALUE "LegalCopyright", "Igneum. Nothing is bought or sold."
VALUE "OriginalFilename", "igneumd.exe"
VALUE "ProductName", "Igneum Miner"
VALUE "ProductVersion", "0.1.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
// Windows resources for igneumd.exe: the coin icon Explorer shows and the version block under Properties > Details.
// Compiled with x86_64-w64-mingw32-windres (the icon path is relative to brand/icons, passed with -I).
// the project lead's rule (4 October 2026): every shipped exe carries the coin icon and a version block, like the Mac app and DMG.
#include <winver.h>
1 ICON "igneum.ico"
1 VERSIONINFO
FILEVERSION 0,3,0,0
PRODUCTVERSION 0,3,0,0
FILEFLAGSMASK 0x3fL
FILEFLAGS 0x0L
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904B0"
BEGIN
VALUE "CompanyName", "Igneum"
VALUE "FileDescription", "Igneum Miner node"
VALUE "FileVersion", "0.3.0"
VALUE "InternalName", "igneumd"
VALUE "LegalCopyright", "Igneum contributors"
VALUE "OriginalFilename", "igneumd.exe"
VALUE "ProductName", "Igneum Miner"
VALUE "ProductVersion", "0.3.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

View file

@ -0,0 +1,147 @@
#!/usr/bin/env python3
"""Checks that a Windows exe carries the Igneum resources: a .rsrc section with an icon group and a version block whose
CompanyName is Igneum and ProductName is Igneum Miner. Prints what it finds. Small PE parser, no dependencies, runs on
the Mac where rcedit and Explorer are not available.
python3 verify-exe.py [--version 0.3.0] exe [exe ...] exit 1 if any exe fails"""
import struct, sys
RT_ICON, RT_GROUP_ICON, RT_VERSION = 3, 14, 16
REQUIRED = ("CompanyName", "ProductName", "FileDescription", "FileVersion", "ProductVersion", "LegalCopyright")
def sections_of(data):
pe = struct.unpack_from("<I", data, 0x3C)[0]
if data[pe:pe + 4] != b"PE\0\0":
raise ValueError("not a PE file")
nsec, opt_size = struct.unpack_from("<H", data, pe + 6)[0], struct.unpack_from("<H", data, pe + 20)[0]
opt = pe + 24
magic = struct.unpack_from("<H", data, opt)[0]
dd = opt + (112 if magic == 0x20B else 96)
res_rva, res_size = struct.unpack_from("<II", data, dd + 2 * 8)
secs = []
for i in range(nsec):
s = opt + opt_size + 40 * i
name = data[s:s + 8].rstrip(b"\0").decode("latin1")
vsize, vaddr, rsize, raw = struct.unpack_from("<IIII", data, s + 8)
secs.append((name, vaddr, max(vsize, rsize), raw))
return secs, res_rva, res_size
def rva_to_off(secs, rva):
for _, vaddr, size, raw in secs:
if vaddr <= rva < vaddr + size:
return raw + (rva - vaddr)
raise ValueError("rva 0x%x outside every section" % rva)
def walk(data, base, off, depth, found, path=()):
named, ids = struct.unpack_from("<HH", data, base + off + 12)
for i in range(named + ids):
e = base + off + 16 + 8 * i
ident, child = struct.unpack_from("<II", data, e)
if child & 0x80000000:
walk(data, base, child & 0x7FFFFFFF, depth + 1, found, path + (ident,))
else:
rva, size = struct.unpack_from("<II", data, base + child)
found.append((path + (ident,), rva, size))
def version_strings(data, off, size):
"""Parses VS_VERSIONINFO into (fixed FileVersion tuple, {key: value})."""
end = off + size
def block(pos):
length, vlen, vtype = struct.unpack_from("<HHH", data, pos)
key_end = pos + 6
while data[key_end:key_end + 2] != b"\0\0":
key_end += 2
key = data[pos + 6:key_end].decode("utf-16-le")
val = (key_end + 2 + 3) & ~3
return length, vlen, vtype, key, val
strings, fixed = {}, None
length, vlen, _, key, val = block(off)
if vlen >= 52 and struct.unpack_from("<I", data, val)[0] == 0xFEEF04BD:
ms, ls, pms, pls = struct.unpack_from("<IIII", data, val + 8)
fixed = (ms >> 16, ms & 0xFFFF, ls >> 16, ls & 0xFFFF, pms >> 16, pms & 0xFFFF, pls >> 16, pls & 0xFFFF)
pos = (val + vlen + 3) & ~3
while pos < off + length:
clen, _, _, ckey, cval = block(pos)
if ckey == "StringFileInfo":
tpos = cval
while tpos < pos + clen:
tlen, _, _, _, tval = block(tpos)
spos = tval
while spos < tpos + tlen:
slen, svlen, _, skey, sval = block(spos)
if slen == 0:
break
raw = data[sval:sval + 2 * svlen]
strings[skey] = raw.decode("utf-16-le").rstrip("\0")
spos = (spos + slen + 3) & ~3
tpos = (tpos + tlen + 3) & ~3
if clen == 0:
break
pos = (pos + clen + 3) & ~3
return fixed, strings
def check(path, want_version):
data = open(path, "rb").read()
secs, res_rva, res_size = sections_of(data)
names = [s[0] for s in secs]
problems = []
if ".rsrc" not in names:
problems.append("no .rsrc section")
if not res_rva:
problems.append("resource table empty in the PE data directory")
icons = groups = 0
fixed, strings = None, {}
if res_rva:
base = rva_to_off(secs, res_rva)
found = []
walk(data, base, 0, 0, found)
for p, rva, size in found:
if p[0] == RT_ICON:
icons += 1
elif p[0] == RT_GROUP_ICON:
groups += 1
elif p[0] == RT_VERSION and fixed is None:
fixed, strings = version_strings(data, rva_to_off(secs, rva), size)
if not groups:
problems.append("no icon group (RT_GROUP_ICON)")
if fixed is None:
problems.append("no version block (RT_VERSION)")
for k in REQUIRED:
if not strings.get(k):
problems.append("missing " + k)
if strings.get("CompanyName") not in (None, "Igneum"):
problems.append("CompanyName is %r, not Igneum" % strings["CompanyName"])
if strings.get("ProductName") not in (None, "Igneum Miner"):
problems.append("ProductName is %r, not Igneum Miner" % strings["ProductName"])
if want_version:
for k in ("FileVersion", "ProductVersion"):
if strings.get(k) and strings[k] != want_version:
problems.append("%s is %s, not %s" % (k, strings[k], want_version))
if fixed and ".".join(map(str, fixed[:3])) != want_version:
problems.append("FILEVERSION is %s, not %s" % (".".join(map(str, fixed[:4])), want_version))
print("%s: %d bytes, sections %s" % (path, len(data), " ".join(names)))
print(" icon images %d, icon groups %d, FILEVERSION %s" % (icons, groups, ".".join(map(str, fixed[:4])) if fixed else "none"))
for k in REQUIRED + ("InternalName", "OriginalFilename"):
if k in strings:
print(" %-17s %s" % (k, strings[k]))
for p in problems:
print(" FAIL: " + p)
return not problems
if __name__ == "__main__":
args = sys.argv[1:]
want = None
if args[:1] == ["--version"]:
want, args = args[1], args[2:]
if not args:
sys.exit(__doc__)
ok = all([check(p, want) for p in args])
print("all %d exe(s) carry the Igneum resources" % len(args) if ok else "FAILED")
sys.exit(0 if ok else 1)

View file

@ -5,27 +5,43 @@
# proto-opencl/igneum-worker-opencl.exe host.c with IGNEUM_CL_DYNAMIC (OpenCL.dll loaded at run time) and the generic
# --pack serve mode; the pack it is built against is only a placeholder
# Static libgcc/libstdc++/winpthread, so each exe needs only KERNEL32 and the Universal CRT (Windows 10 and 11 have it).
# Each exe carries the coin icon and a version block (igneum-worker-cuda.rc, igneum-worker-opencl.rc, compiled with windres
# and linked in; the project lead's rule, 4 October 2026: every shipped exe shows the coin in Explorer and a publisher in Properties).
# Usage: build-windows.sh (then windows-app/make-package.sh ships them with the NVRTC DLLs)
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)"
RED="$HERE/redist"
ICONS="$ROOT/brand/icons"
[ -f "$RED/include/cuda.h" ] && [ -f "$RED/include/nvrtc.h" ] && [ -f "$RED/include/CL/cl.h" ] || { echo "run $HERE/fetch-redist.sh first" >&2; exit 1; }
CXX=x86_64-w64-mingw32-g++
CC=x86_64-w64-mingw32-gcc
STRIP=x86_64-w64-mingw32-strip
WINDRES=x86_64-w64-mingw32-windres
command -v "$WINDRES" >/dev/null || { echo "$WINDRES not found (brew install mingw-w64)" >&2; exit 1; }
PLACEHOLDER="$ROOT/proto-cuda/packs/igneum-devnet-v4-epoch0"
VERIFY="$ROOT/packaging/windows/resources/verify-exe.py"
# The coin icon and the version blocks, as COFF objects the linker takes like any other input
[ -f "$ICONS/igneum.ico" ] || { echo "== no $ICONS/igneum.ico, making the icons"; python3 "$ICONS/make-icons.py"; }
RES="$(mktemp -d)"
for w in cuda opencl; do
"$WINDRES" -I "$ICONS" -i "$HERE/igneum-worker-$w.rc" -O coff -o "$RES/igneum-worker-$w.res.o"
done
echo "== igneum-worker-cuda.exe"
"$CXX" -std=c++17 -O2 -Wall -Wextra -static -I "$HERE" -I "$RED/include" -o "$HERE/igneum-worker-cuda.exe" "$HERE/worker.cpp"
"$CXX" -std=c++17 -O2 -Wall -Wextra -static -I "$HERE" -I "$RED/include" -o "$HERE/igneum-worker-cuda.exe" "$HERE/worker.cpp" "$RES/igneum-worker-cuda.res.o"
"$STRIP" "$HERE/igneum-worker-cuda.exe"
echo "== igneum-worker-opencl.exe"
"$CC" -std=c99 -O2 -Wall -Wextra -Wno-stringop-truncation -Wno-format-truncation -static -DIGNEUM_CL_DYNAMIC -DCL_TARGET_OPENCL_VERSION=120 \
-I "$RED/include" -I "$PLACEHOLDER" -DIGNEUM_KERNEL_PATH='"kernel_bound.cl"' \
-o "$ROOT/proto-opencl/igneum-worker-opencl.exe" "$ROOT/proto-opencl/host.c"
-o "$ROOT/proto-opencl/igneum-worker-opencl.exe" "$ROOT/proto-opencl/host.c" "$RES/igneum-worker-opencl.res.o"
"$STRIP" "$ROOT/proto-opencl/igneum-worker-opencl.exe"
rm -rf "$RES"
for exe in "$HERE/igneum-worker-cuda.exe" "$ROOT/proto-opencl/igneum-worker-opencl.exe"; do
printf '%s: %d bytes, imports:' "$(basename "$exe")" "$(stat -f %z "$exe")"
x86_64-w64-mingw32-objdump -p "$exe" | sed -n 's/^[[:space:]]*DLL Name: //p' | tr '\n' ' '
echo
done
# the icon and version block survived the strip (strip keeps .rsrc; this proves it)
python3 "$VERIFY" --version 0.3.0 "$HERE/igneum-worker-cuda.exe" "$ROOT/proto-opencl/igneum-worker-opencl.exe"

View file

@ -0,0 +1,35 @@
// Windows resources for igneum-worker-cuda.exe: the coin icon Explorer shows and the version block under Properties > Details.
// Compiled with x86_64-w64-mingw32-windres (the icon path is relative to brand/icons, passed with -I).
// the project lead's rule (4 October 2026): every shipped exe carries the coin icon and a version block, like the Mac app and DMG.
#include <winver.h>
1 ICON "igneum.ico"
1 VERSIONINFO
FILEVERSION 0,3,0,0
PRODUCTVERSION 0,3,0,0
FILEFLAGSMASK 0x3fL
FILEFLAGS 0x0L
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904B0"
BEGIN
VALUE "CompanyName", "Igneum"
VALUE "FileDescription", "Igneum Miner CUDA worker"
VALUE "FileVersion", "0.3.0"
VALUE "InternalName", "igneum-worker-cuda"
VALUE "LegalCopyright", "Igneum contributors"
VALUE "OriginalFilename", "igneum-worker-cuda.exe"
VALUE "ProductName", "Igneum Miner"
VALUE "ProductVersion", "0.3.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

View file

@ -0,0 +1,35 @@
// Windows resources for igneum-worker-opencl.exe: the coin icon Explorer shows and the version block under Properties > Details.
// Compiled with x86_64-w64-mingw32-windres (the icon path is relative to brand/icons, passed with -I).
// the project lead's rule (4 October 2026): every shipped exe carries the coin icon and a version block, like the Mac app and DMG.
#include <winver.h>
1 ICON "igneum.ico"
1 VERSIONINFO
FILEVERSION 0,3,0,0
PRODUCTVERSION 0,3,0,0
FILEFLAGSMASK 0x3fL
FILEFLAGS 0x0L
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904B0"
BEGIN
VALUE "CompanyName", "Igneum"
VALUE "FileDescription", "Igneum Miner OpenCL worker"
VALUE "FileVersion", "0.3.0"
VALUE "InternalName", "igneum-worker-opencl"
VALUE "LegalCopyright", "Igneum contributors"
VALUE "OriginalFilename", "igneum-worker-opencl.exe"
VALUE "ProductName", "Igneum Miner"
VALUE "ProductVersion", "0.3.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

View file

@ -19,7 +19,7 @@
X(clCreateCommandQueue) X(clCreateProgramWithSource) X(clBuildProgram) X(clGetProgramBuildInfo) X(clCreateKernel) \
X(clSetKernelArg) X(clEnqueueNDRangeKernel) X(clEnqueueReadBuffer) X(clEnqueueWriteBuffer) X(clCreateBuffer) \
X(clReleaseMemObject) X(clReleaseKernel) X(clReleaseProgram) X(clReleaseCommandQueue) X(clReleaseContext) \
X(clFinish) X(clWaitForEvents) X(clReleaseEvent) X(clGetEventProfilingInfo) X(clGetKernelWorkGroupInfo) \
X(clFinish) X(clWaitForEvents) X(clReleaseEvent) X(clGetEventInfo) X(clGetEventProfilingInfo) X(clGetKernelWorkGroupInfo) \
X(clGetExtensionFunctionAddressForPlatform)
/* One pointer per entry point, typed from the header's declaration (so a signature drift is a compile error). */
@ -72,6 +72,7 @@ static int ig_cl_load(void) {
#define clFinish ig_clFinish
#define clWaitForEvents ig_clWaitForEvents
#define clReleaseEvent ig_clReleaseEvent
#define clGetEventInfo ig_clGetEventInfo
#define clGetEventProfilingInfo ig_clGetEventProfilingInfo
#define clGetKernelWorkGroupInfo ig_clGetKernelWorkGroupInfo
#define clGetExtensionFunctionAddressForPlatform ig_clGetExtensionFunctionAddressForPlatform