Skip to content
Closed
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
7 changes: 7 additions & 0 deletions host/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ static LRESULT CALLBACK wndproc(HWND w, UINT msg, WPARAM wp, LPARAM lp) {
switch (msg) {
case WM_TIMER:
return 0;
case WM_SYSCOMMAND:
if ((wp & 0xFFF0) == SC_SCREENSAVE && !(wp & SCF_ISSECURE) &&
renderer && GetForegroundWindow() == w && !IsIconic(w)) {
BOOL secure = TRUE; /* unknown or protected policy keeps Windows' normal handling */
if (SystemParametersInfoA(SPI_GETSCREENSAVESECURE, 0, &secure, 0) && !secure) return 0;
}
break;
case WM_INPUT: {
RAWINPUT ri;
UINT size = sizeof ri;
Expand Down
1 change: 1 addition & 0 deletions tests/run_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
# Arguments some tests need. {name} is a tool or folder found below (find_tools); a check whose tools are missing is
# skipped, or fails with --strict. Tests not listed run with no arguments and their own defaults.
ARGS = {
"tests/test_screensaver.py": ["--cc", "gcc"],
"tests/test_system_header_cache.py": ["--cc", "clang"],
"tests/test_precompile_compute_guard.py": ["--cxx", "clang++"],
"tests/test_input_background.py": ["--cc", "gcc"],
Expand Down
123 changes: 123 additions & 0 deletions tests/screensaver_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/* All APIs are mocks: no window, screen saver, security or power setting is accessed. */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

typedef void *HWND;
typedef void *HRAWINPUT;
typedef unsigned UINT;
typedef int BOOL;
typedef int32_t LONG;
typedef uintptr_t WPARAM;
typedef intptr_t LPARAM;
typedef intptr_t LRESULT;
#define CALLBACK
#define TRUE 1
#define FALSE 0
#define LOWORD(v) ((unsigned)(uintptr_t)(v) & 0xffff)
#define HIWORD(v) (((unsigned)(uintptr_t)(v) >> 16) & 0xffff)
enum {
WM_TIMER = 0x113, WM_SYSCOMMAND = 0x112, WM_INPUT = 0xff, WM_LBUTTONDOWN = 0x201,
WM_KEYDOWN = 0x100, WM_SYSKEYDOWN = 0x104, WM_SYSCHAR = 0x106, WM_APP = 0x8000,
WM_DPICHANGED = 0x2e0, WM_KILLFOCUS = 8, WM_CANCELMODE = 0x1f, WM_SETCURSOR = 0x20,
WM_MOVE = 3, WM_SIZE = 5, WM_PAINT = 0xf, WM_ERASEBKGND = 0x14, WM_CLOSE = 0x10,
VK_ESCAPE = 0x1b, VK_F11 = 0x7a, VK_RETURN = 0xd, HTCLIENT = 1,
SC_SCREENSAVE = 0xf140, SCF_ISSECURE = 1, SPI_GETSCREENSAVESECURE = 0x76,
SC_CLOSE = 0xf060, SC_MONITORPOWER = 0xf170, SC_MAXIMIZE = 0xf030,
SWP_NOZORDER = 4, SWP_NOACTIVATE = 0x10, RID_INPUT = 0x10000003,
RIM_TYPEMOUSE = 0, MOUSE_MOVE_ABSOLUTE = 1
};
typedef struct { LONG left, top, right, bottom; } RECT;
typedef struct { UINT dwType; } RAWINPUTHEADER;
typedef struct {
RAWINPUTHEADER header;
struct { struct { unsigned short usFlags; LONG lLastX, lLastY; } mouse; } data;
} RAWINPUT;

#define CHECK(name, condition) do { if (!(condition)) { \
fprintf(stderr, "FAIL: %s (line %d)\n", name, __LINE__); exit(1); } } while (0)
static HWND game = (HWND)(uintptr_t)1, foreground;
static int renderer, fullscreen, mouse_captured;
static volatile LONG mouse_dx, mouse_dy;
static int iconic, query_ok, query_writes, policy_secure, query_calls, forwarded, cases;
static UINT forwarded_msg;
static WPARAM forwarded_wp;
static LPARAM forwarded_lp;
static const LRESULT default_result = 1234;

HWND GetForegroundWindow(void) { return foreground; }
BOOL IsIconic(HWND w) { CHECK("iconic query targets game", w == game); return iconic; }
BOOL SystemParametersInfoA(UINT action, UINT parameter, void *value, UINT flags) {
CHECK("only read the secure screen saver policy", action == SPI_GETSCREENSAVESECURE);
CHECK("no profile write/notification flags", parameter == 0 && flags == 0 && value != NULL);
query_calls++;
if (query_writes) *(BOOL *)value = policy_secure;
return query_ok;
}
static LRESULT DefWindowProcA(HWND w, UINT msg, WPARAM wp, LPARAM lp) {
CHECK("forward original window", w == game);
forwarded++;
forwarded_msg = msg; forwarded_wp = wp; forwarded_lp = lp;
return default_result;
}

/* The complete procedure compiles, but these unrelated branches must never run in this fixture. */
static void unexpected(void) { fputs("FAIL: unrelated window action executed\n", stderr); abort(); }
static UINT GetRawInputData(HRAWINPUT h, UINT command, void *data, UINT *size, UINT header) {
(void)h; (void)command; (void)data; (void)size; (void)header; unexpected(); return (UINT)-1;
}
static LONG InterlockedExchangeAdd(volatile LONG *target, LONG value) {
(void)target; (void)value; unexpected(); return 0;
}
static void mouse_capture(HWND w, int on) { (void)w; (void)on; unexpected(); }
static void set_fullscreen(HWND w, int on) { (void)w; (void)on; unexpected(); }
static void hos_vk_note_key_press(unsigned key) { (void)key; unexpected(); }
static BOOL SetWindowPos(HWND w, HWND after, int x, int y, int width, int height, UINT flags) {
(void)w; (void)after; (void)x; (void)y; (void)width; (void)height; (void)flags; unexpected(); return FALSE;
}
static void *SetCursor(void *cursor) { (void)cursor; unexpected(); return NULL; }
static void rx_resize(unsigned width, unsigned height) { (void)width; (void)height; unexpected(); }
static BOOL ValidateRect(HWND w, const RECT *rect) { (void)w; (void)rect; unexpected(); return FALSE; }
static void hos_log(const char *text) { (void)text; unexpected(); }
static void hos_log_flush(void) { unexpected(); }
static void ExitProcess(unsigned code) { (void)code; unexpected(); }

/* ACTUAL_WNDPROC */

static void reset(void) {
renderer = 1; foreground = game; iconic = 0;
query_ok = 1; query_writes = 1; policy_secure = FALSE;
query_calls = forwarded = 0;
}
static void call(const char *name, UINT msg, WPARAM wp, int handled, int queries) {
const LPARAM lp = 0x5678;
const LRESULT result = wndproc(game, msg, wp, lp);
CHECK(name, result == (handled ? 0 : default_result));
CHECK("forwarding count", forwarded == !handled);
CHECK("policy query count", query_calls == queries);
if (!handled) CHECK("forward original message and parameters",
forwarded_msg == msg && forwarded_wp == wp && forwarded_lp == lp);
cases++;
}
int main(void) {
reset(); call("foreground nonsecure saver is handled", WM_SYSCOMMAND, SC_SCREENSAVE, 1, 1);
reset(); call("secure request is forwarded", WM_SYSCOMMAND, SC_SCREENSAVE | SCF_ISSECURE, 0, 0);
reset(); call("secure request flag survives other low bits", WM_SYSCOMMAND, SC_SCREENSAVE | SCF_ISSECURE | 8, 0, 0);
reset(); policy_secure = TRUE; call("password policy is forwarded", WM_SYSCOMMAND, SC_SCREENSAVE, 0, 1);
reset(); policy_secure = 2; call("any true policy is forwarded", WM_SYSCOMMAND, SC_SCREENSAVE, 0, 1);
reset(); query_ok = 0; call("failed policy query is forwarded even if it wrote false", WM_SYSCOMMAND, SC_SCREENSAVE, 0, 1);
reset(); query_ok = 0; query_writes = 0; call("unknown policy is forwarded", WM_SYSCOMMAND, SC_SCREENSAVE, 0, 1);
reset(); query_writes = 0; call("unwritten policy stays secure", WM_SYSCOMMAND, SC_SCREENSAVE, 0, 1);
reset(); renderer = 0; call("no renderer is forwarded", WM_SYSCOMMAND, SC_SCREENSAVE, 0, 0);
reset(); foreground = (HWND)(uintptr_t)2; call("background game is forwarded", WM_SYSCOMMAND, SC_SCREENSAVE, 0, 0);
reset(); foreground = NULL; call("unknown foreground is forwarded", WM_SYSCOMMAND, SC_SCREENSAVE, 0, 0);
reset(); iconic = 1; call("minimized game is forwarded", WM_SYSCOMMAND, SC_SCREENSAVE, 0, 0);
reset(); call("close command is forwarded", WM_SYSCOMMAND, SC_CLOSE, 0, 0);
reset(); call("monitor power command is forwarded", WM_SYSCOMMAND, SC_MONITORPOWER, 0, 0);
reset(); call("maximize command is forwarded", WM_SYSCOMMAND, SC_MAXIMIZE, 0, 0);
reset(); call("unknown command is forwarded", WM_SYSCOMMAND, 0xf2e0, 0, 0);
reset(); call("zero command is forwarded", WM_SYSCOMMAND, 0, 0, 0);
reset(); call("unrelated message is forwarded", WM_APP + 99, 0xabc, 0, 0);
printf("PASS: %d actual-wndproc screensaver/secure-policy/forwarding cases; all APIs mocked\n", cases);
return 0;
}
39 changes: 39 additions & 0 deletions tests/test_screensaver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Compile the complete production window procedure with portable, authored Win32 API mocks."""
import argparse
from pathlib import Path
import subprocess
import tempfile


ROOT = Path(__file__).resolve().parents[1]


def wndproc(source):
start = source.index("static LRESULT CALLBACK wndproc(")
end = source.index("{", start) + 1
depth = 1
while depth:
depth += (source[end] == "{") - (source[end] == "}")
end += 1
return source[start:end]


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--cc", default="gcc")
parser.add_argument("--source", type=Path, default=ROOT / "host/display.c")
args = parser.parse_args()
procedure = wndproc(args.source.read_text(encoding="utf-8"))
template = (ROOT / "tests/screensaver_test.c").read_text(encoding="utf-8")
assert template.count("/* ACTUAL_WNDPROC */") == 1
with tempfile.TemporaryDirectory(prefix="hookshot-screensaver-") as temp:
source, executable = Path(temp) / "fixture.c", Path(temp) / "fixture.exe"
source.write_text(template.replace("/* ACTUAL_WNDPROC */", procedure), encoding="utf-8")
priority = {"creationflags": getattr(subprocess, "BELOW_NORMAL_PRIORITY_CLASS", 0)}
subprocess.run([args.cc, "-std=c11", "-O0", "-Wall", "-Wextra", "-Werror", str(source), "-o", str(executable)],
check=True, **priority)
subprocess.run([str(executable)], check=True, **priority)


if __name__ == "__main__":
main()
Loading