fix: CSPRNG on Windows, and a window's first title is the package's app name
Crypto.random_* and Uuid.* read /dev/urandom, which Windows lacks, so every byte was zero; the Windows target now calls RtlGenRandom (advapi32). ludicc takes --title, which ludic build/run/bundle pass from package.ludic's app name, so rt_init opens the window under that name instead of the program name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
28f661c36f
commit
7f9f8de08a
9 changed files with 33592 additions and 33300 deletions
5
changes/crypto-random-windows.md
Normal file
5
changes/crypto-random-windows.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
bump: patch
|
||||
type: fix
|
||||
`Crypto.random_bytes`, `Crypto.random_hex`, `Crypto.random_u32` and `Uuid.*` draw from the
|
||||
system CSPRNG on Windows (`RtlGenRandom`, advapi32). They read `/dev/urandom`, which Windows
|
||||
does not have, and every byte came back zero.
|
||||
7
changes/window-first-title.md
Normal file
7
changes/window-first-title.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
bump: patch
|
||||
type: fix
|
||||
A windowed game opens under its package's `app name`. `ludic build`, `ludic run` and
|
||||
`ludic bundle` pass it to the compiler (`ludicc --title <name>`), so `game_title()` - the
|
||||
window's first title - is the name the player knows rather than the `program` name, which
|
||||
showed for up to two seconds before the renderer retitled the window. Without an `app name`
|
||||
it is the `program` name, as before.
|
||||
|
|
@ -10,6 +10,14 @@ function emit_str_const(s: pointer) -> pointer {
|
|||
let n = len(s)
|
||||
emith(name); emith(" = private unnamed_addr constant [")
|
||||
emith(itoa(n + 1)); emith(" x i8] c\"")
|
||||
emit_escaped(s)
|
||||
emith("\\00\"\n")
|
||||
return name
|
||||
}
|
||||
|
||||
# the bytes of s as the body of an IR c"..." string: " \ and non-print -> \XX
|
||||
function emit_escaped(s: pointer) -> void {
|
||||
let n = len(s)
|
||||
var i = 0
|
||||
while i < n {
|
||||
let c = s[i]
|
||||
|
|
@ -20,8 +28,6 @@ function emit_str_const(s: pointer) -> pointer {
|
|||
} else { buf_putc(head, c) }
|
||||
i += 1
|
||||
}
|
||||
emith("\\00\"\n")
|
||||
return name
|
||||
}
|
||||
|
||||
# the constant initializer for a global var: a literal, or 0/null
|
||||
|
|
@ -144,7 +150,7 @@ function emit_header() -> void {
|
|||
emith("@L_argv = internal global ptr null\n")
|
||||
emith("@L_clock = internal global i32 0\n") # Clock.* — the game-controlled simulated clock
|
||||
emith("@.gametitle = private unnamed_addr constant [")
|
||||
emith(itoa(len(g_game_name) + 1)); emith(" x i8] c\""); emith(g_game_name); emith("\\00\"\n")
|
||||
emith(itoa(len(g_game_name) + 1)); emith(" x i8] c\""); emit_escaped(g_game_name); emith("\\00\"\n")
|
||||
emith("%LSlice = type { ptr, i32, i32 }\n")
|
||||
# property layouts — a %Cmp_ record of named fields, emitted here so `new`
|
||||
# works whether or not the program runs the ECS. The per-entity @S_/@H_ arrays
|
||||
|
|
|
|||
|
|
@ -293,14 +293,28 @@ function emit_secure_rand_prelude() -> void {
|
|||
emith("@.ludic_rbmode = private unnamed_addr constant [3 x i8] c\"rb\\00\"\n")
|
||||
emith("@.ludic_b64tab = private unnamed_addr constant [64 x i8] c\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"\n")
|
||||
|
||||
# fill %n bytes at %out from the OS CSPRNG. If /dev/urandom cannot be opened the
|
||||
# buffer is zeroed (documented degraded mode — e.g. wasm), never left uninit.
|
||||
emith("define void @lp_secure_bytes(ptr %out, i64 %n) {\n")
|
||||
emith("entry:\n call ptr @memset(ptr %out, i32 0, i64 %n)\n")
|
||||
emith(" %fp = call ptr @fopen(ptr @.ludic_urandom, ptr @.ludic_rbmode)\n")
|
||||
emith(" %isnull = icmp eq ptr %fp, null\n br i1 %isnull, label %fail, label %ok\n")
|
||||
emith("ok:\n %rd = call i64 @fread(ptr %out, i64 1, i64 %n, ptr %fp)\n %cl = call i32 @fclose(ptr %fp)\n ret void\n")
|
||||
emith("fail:\n ret void\n}\n")
|
||||
# Windows has no /dev/urandom (fopen fails, and every byte came back zero): there the
|
||||
# bytes come from RtlGenRandom (advapi32's SystemFunction036), the system CSPRNG,
|
||||
# in chunks that fit its ULONG length. main.ludic links advapi32 for it.
|
||||
if g_target_win {
|
||||
emith("declare i8 @SystemFunction036(ptr, i32)\n")
|
||||
emith("define void @lp_secure_bytes(ptr %out, i64 %n) {\n")
|
||||
emith("entry:\n call ptr @memset(ptr %out, i32 0, i64 %n)\n %op = alloca i64\n store i64 0, ptr %op\n br label %c\n")
|
||||
emith("c:\n %o = load i64, ptr %op\n %left = sub i64 %n, %o\n %more = icmp sgt i64 %left, 0\n br i1 %more, label %b, label %d\n")
|
||||
emith("b:\n %big = icmp sgt i64 %left, 65536\n %k = select i1 %big, i64 65536, i64 %left\n %k32 = trunc i64 %k to i32\n")
|
||||
emith(" %p = getelementptr i8, ptr %out, i64 %o\n %ok = call i8 @SystemFunction036(ptr %p, i32 %k32)\n")
|
||||
emith(" %on = add i64 %o, %k\n store i64 %on, ptr %op\n br label %c\n")
|
||||
emith("d:\n ret void\n}\n")
|
||||
} else {
|
||||
# fill %n bytes at %out from the OS CSPRNG. If /dev/urandom cannot be opened the
|
||||
# buffer is zeroed (documented degraded mode — e.g. wasm), never left uninit.
|
||||
emith("define void @lp_secure_bytes(ptr %out, i64 %n) {\n")
|
||||
emith("entry:\n call ptr @memset(ptr %out, i32 0, i64 %n)\n")
|
||||
emith(" %fp = call ptr @fopen(ptr @.ludic_urandom, ptr @.ludic_rbmode)\n")
|
||||
emith(" %isnull = icmp eq ptr %fp, null\n br i1 %isnull, label %fail, label %ok\n")
|
||||
emith("ok:\n %rd = call i64 @fread(ptr %out, i64 1, i64 %n, ptr %fp)\n %cl = call i32 @fclose(ptr %fp)\n ret void\n")
|
||||
emith("fail:\n ret void\n}\n")
|
||||
}
|
||||
|
||||
# %n secure bytes -> a fresh 2n-char lowercase hex string
|
||||
emith("define ptr @lp_random_hex(i64 %n) {\n")
|
||||
|
|
|
|||
33403
selfhost/ludicc.seed.ll
33403
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -135,6 +135,7 @@ entry {
|
|||
var save = false # --save-temps: keep the intermediate .ll
|
||||
var run = false # compile then execute the result
|
||||
var target = null # --target <triple>: null means the host
|
||||
var title = null # --title <name>: game_title() - the window's first title - instead of the program name
|
||||
var gui = false # --gui: a Windows GUI-subsystem program (no console behind the window)
|
||||
var links = new []pointer # --link <file>: an extra input to the link (a compiled resource)
|
||||
g_coverage = false # --coverage: instrument each statement with a per-line hit counter
|
||||
|
|
@ -153,6 +154,7 @@ entry {
|
|||
else if a == "--coverage" { g_coverage = true }
|
||||
else if a == "--target" { ai += 1; if ai < arg_count() { target = arg(ai) } }
|
||||
else if a == "--gui" { gui = true }
|
||||
else if a == "--title" { ai += 1; if ai < arg_count() { title = arg(ai) } }
|
||||
else if a == "--link" { ai += 1; if ai < arg_count() { push(links, arg(ai)) } }
|
||||
else if a == "-o" { ai += 1; if ai < arg_count() { out = arg(ai) } }
|
||||
else if a[0] == '-' {
|
||||
|
|
@ -178,7 +180,10 @@ entry {
|
|||
g_parse_file = path # for the compiler's own file:line diagnostics
|
||||
lex(src)
|
||||
parse_program()
|
||||
g_prog_user_end = len(prog) # decls from the user's source; runtime splice appends after
|
||||
g_prog_user_end = len(prog)
|
||||
# `ludic build` passes the package's `app name`, so the window opens under the name the
|
||||
# player knows rather than the `program` name and is not retitled a moment later
|
||||
if (title != null) and (len(title) > 0) { g_game_name = title } # decls from the user's source; runtime splice appends after
|
||||
|
||||
# --fmt is the doc-check gate: reaching here means it lexed and parsed. A parse
|
||||
# error would already have exited nonzero, so a clean parse exits 0. (Canonical
|
||||
|
|
@ -281,6 +286,8 @@ entry {
|
|||
}
|
||||
# Process.* links the child-process layer: posix_spawn from libSystem on macOS,
|
||||
# CreateProcessW from kernel32 on Windows.
|
||||
# Crypto.random_* / Uuid.* read RtlGenRandom on Windows, which advapi32 exports
|
||||
if g_target_win and g_uses_cryptort { cmd = `{cmd} -ladvapi32` }
|
||||
if g_uses_process {
|
||||
if g_target_win { cmd = `{cmd} {join_path(home, "runtime/native/process_win.ll")}` }
|
||||
else { cmd = `{cmd} {join_path(home, "runtime/native/process.ll")}` }
|
||||
|
|
|
|||
|
|
@ -43,17 +43,17 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool
|
|||
var flags = "--windowed"
|
||||
if mode == 2 { flags = "--headless" }
|
||||
if save { flags = flags + " --save-temps" }
|
||||
return shq(`{ludicc()} {flags} {src} -o {out}`)
|
||||
return shq(`{ludicc()} {flags}{title_flag()} {src} -o {out}`)
|
||||
}
|
||||
|
||||
if mode == 2 {
|
||||
if not shq(`{ludicc()} --headless {src} --emit-llvm -o {ll}`) { return false }
|
||||
if not shq(`{ludicc()} --headless{title_flag()} {src} --emit-llvm -o {ll}`) { return false }
|
||||
if not shq(`{cc()} -O2 {ll}{gl_link_flags(ll)}{vk_link_flags(ll)}{http_link_flags(ll)}{udp_link_flags(ll)}{process_link_flags(ll)}{threads_link_flags(ll)}{pbf} -o {out}`) { return false }
|
||||
if not save { shell(`rm -f {ll}`) }
|
||||
return true
|
||||
}
|
||||
|
||||
if not shq(`{ludicc()} --windowed {src} --emit-llvm -o {ll}`) { return false }
|
||||
if not shq(`{ludicc()} --windowed{title_flag()} {src} --emit-llvm -o {ll}`) { return false }
|
||||
# audio.ll (#22) is always linked here — unused snd_* are dead-stripped; the
|
||||
# canonical `ludicc -o` path links it only when Audio.* is used.
|
||||
let cocoa = `{home}runtime/native/cocoa.ll`
|
||||
|
|
@ -63,6 +63,21 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool
|
|||
return true
|
||||
}
|
||||
|
||||
# ` --title "<app name>"` when the project's package.ludic names the app, so the window
|
||||
# opens under that name rather than the `program` name; "" otherwise, and for a name the
|
||||
# shell line could not carry safely.
|
||||
function title_flag() -> pointer {
|
||||
let name = manifest_app(read_root_manifest(), "name")
|
||||
if name == "" { return "" }
|
||||
var i = 0
|
||||
while name[i] != 0 {
|
||||
let c = name[i]
|
||||
if c == '"' or c == '`' or c == '$' or c == 92 or c == '%' or c < ' ' { return "" }
|
||||
i += 1
|
||||
}
|
||||
return ` --title "{name}"`
|
||||
}
|
||||
|
||||
# A program that uses Gl.* references the @lgl_* thunks; link the OpenGL backend
|
||||
# (gl.ll + gl_thunks.ll + OpenGL.framework) only then, so other builds are untouched.
|
||||
function gl_link_flags(ll: pointer) -> pointer {
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ function cmd_bundle_windows() -> int {
|
|||
let staged = tmp_path("bundle_exe")
|
||||
var flags = "--windowed --gui"
|
||||
if res != "" { flags = flags + ` --link "{res}"` }
|
||||
if not shq(`{ludicc()} {flags} {entry} -o {staged}`) { return 1 }
|
||||
if not shq(`{ludicc()} {flags}{title_flag()} {entry} -o {staged}`) { return 1 }
|
||||
if not shq(`cp "{staged}.exe" "{root}/{name}.exe"`) {
|
||||
err("ludic bundle: could not place the executable\n")
|
||||
return 1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue