Built on grom's explicit say-so, after a round where they were withheld and the reason recorded. Both halves of that decision stay in the doc, because the reasoning is what makes the second half a decision rather than a drift. ReadCustomFile/ImportFile (one operation, two upstream names), WriteCustomFile/ExportFile, CustomFileExists, ExecuteCustomLuaFile, StartItemExport, EncryptPassword, EncryptedServerLogin. **The fence is app-side and it is not a `..` scan.** `local_state::custom_file_path` admits one relative name whose every component is an ordinary name, inside `benilla-config/CustomFiles/`, and refuses a leaf that is already a symlink. Each escape a string check misses has its own rule and its own test row: an absolute path (`join` *replaces* the base rather than appending), a Windows prefix, a `..` nested inside a longer path, and a planted symlink — the one no amount of sanitising the string will catch. A subdirectory rather than the config root, because a verb that could create `config.toml` or `autologin.toml` by name would let an addon rewrite the client's own settings; that is a different power from "keep a file of your own". The VM side holds three closures and no paths, so no spelling of a `filename` reaches outside that directory. **The credential pair's deviation is forced, not chosen.** Both are registered by nampower under "Registering *Glue* Lua functions" and this client has no glue Lua VM at all — login, character-select and character-create are Rust. `EncryptPassword` is pure and works the same anywhere. `EncryptedServerLogin` cannot mean "submit the login form" in the only VM there is, so it stores the credential for the next login: `enabled` on, the plaintext field cleared, the blob kept, and `AutoLogin::effective_password` reads it back at the refill. What that protects is an `autologin.toml` or a SavedVariables file read by someone else or pasted into a forum: the blob is authenticated and keyed on 32 random bytes in a different file, mode 0600. What it does not protect is anything running as this user, which can read the key as easily as the blob — there is no user secret in the chain and there cannot be, because the point is logging in without typing one. Both halves are at the module. Encrypt-then-MAC with HMAC-SHA1 as the PRF for both halves, from the two primitives the tree already had (sha1 moves from a dev-dependency to a real one). The HMAC is tested against RFC 2202's own vectors, because a hand-written one still round-trips with itself when it is wrong — and a first draft of the key reader did exactly that kind of silent wrong: it returned raw bytes on creation and hex text on read, and the caller guessed between them with "32 bytes and not all hex digits". It guessed wrong the moment a random key came out all-hex. Returning one thing is the fix, and the note is at the function. **The existing plaintext path is untouched.** Storing the password in plaintext was grom's own explicit choice when the alternatives were a keyring or nothing, so this is additive and nothing migrates by itself. The machinery to make the Remember Password tick-box write a blob instead is now all here — one line — and that is his call. Base64 was missing from the whole tree; it is in `benilla-ui`'s `script/encoding.rs` with RFC 4648's own vectors, and `C_EncodingUtil.EncodeBase64` will want the same one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
124 lines
5.7 KiB
TOML
124 lines
5.7 KiB
TOML
[package]
|
|
name = "benilla-app"
|
|
version.workspace = true
|
|
edition.workspace = true
|
|
license.workspace = true
|
|
description = "The Bevy app as a library: everything but the build-id stamp, which lives in the `benilla` launcher so a new commit does not rebuild this crate"
|
|
|
|
[lints]
|
|
workspace = true
|
|
|
|
[features]
|
|
# `dev` compiles in the instruments; the player build (`scripts/gates.sh`) has no defaults.
|
|
default = ["dev"]
|
|
# The asset, format and world crates default `dev` on, so they come in without it, forwarded here.
|
|
dev = ["benilla-assets/dev", "benilla-formats/dev", "benilla-world/dev", "dep:bevy_egui"]
|
|
tracy = ["bevy/trace_tracy"]
|
|
# Writes every system's per-run span to `trace-<ts>.json` in the cwd.
|
|
trace_chrome = ["bevy/trace_chrome"]
|
|
|
|
[dependencies]
|
|
anyhow.workspace = true
|
|
# The EditBox clipboard, on the main thread as NSPasteboard requires; bevy_egui's 3.x.
|
|
arboard = "3"
|
|
benilla-ui = { path = "../benilla-ui" }
|
|
# The chunk cache's key: a load hashes 70 MiB of addon Lua, so the hash has to be the cheap part.
|
|
ahash = "0.8.12"
|
|
bevy.workspace = true
|
|
wgpu.workspace = true
|
|
bevy_egui = { version = "0.39", optional = true }
|
|
bytemuck = { version = "1", features = ["derive"] }
|
|
# `f16` decode for the `WOW_BOOTH_DUMP` dump of the `Rgba16Float` booth targets.
|
|
half = "2"
|
|
# Compiles `assets/ui` into the binary, so the interface loads on a machine without the source.
|
|
include_dir = "0.7"
|
|
# The 1.12 chat filters (`text_filter`), with one `RegexSet` for the any-match pass per line.
|
|
regex = "1"
|
|
# `getrusage` for perf CPU time; the audio clock and render thread's realtime policy on macOS.
|
|
libc = "0.2"
|
|
# PNG encoding only: the macOS cursor's NSImage data and screenshots.
|
|
image = { version = "0.25", default-features = false, features = ["png"] }
|
|
# Thread-to-ECS channels: crossbeam's `Receiver` is `Sync`, so it is a resource without a mutex.
|
|
crossbeam-channel = "0.5"
|
|
benilla-assets = { path = "../benilla-assets", default-features = false }
|
|
benilla-formats = { path = "../benilla-formats", default-features = false }
|
|
benilla-protocol = { path = "../benilla-protocol" }
|
|
benilla-world = { path = "../benilla-world", default-features = false }
|
|
# The CVar diff persisted in `benilla-config/config.toml`.
|
|
serde = { version = "1", features = ["derive"] }
|
|
# The item cache's on-disk form (`item_persist.rs`); already in the tree through bevy.
|
|
serde_json = "1"
|
|
toml = "0.8"
|
|
# Collision for our kinematic character controller; 0.6 targets Bevy 0.18, 0.7 moves to 0.19.
|
|
avian3d = "0.6"
|
|
# Shapes and rasterizes `FontString` text standalone (`ui_text`), at bevy_text's own 0.16.
|
|
cosmic-text = "0.16"
|
|
# `cosmic-text`'s font database, named by `ui_text`; must stay the version it depends on.
|
|
fontdb = "0.23"
|
|
# The mixer. Its `cpal` backend stays off on every target: `sound::output` is our own backend.
|
|
# `ogg`/`vorbis` are the container and the codec, and both are needed for one `.ogg`: addon audio
|
|
# on this install is full of them (75 files across BigWigs, DoiteAuras, unitscan, HCWarn,
|
|
# TWThreat and Cleanup), and `PlaySoundFile` on one decoded to an error nobody saw — the play
|
|
# site logs at `debug!` — so every one of those alerts was simply silent. Stock 1.12 plays only
|
|
# wav and mp3; the Turtle/OctoWoW client those addons are written for plays ogg, which is why
|
|
# they ship it. `docs/ADDON_COMPAT.md` carries the row.
|
|
kira = { version = "0.12", default-features = false, features = [
|
|
"mp3",
|
|
"wav",
|
|
"pcm",
|
|
"adpcm",
|
|
"ogg",
|
|
"vorbis",
|
|
] }
|
|
# Aborts a debug build on an allocation in the audio IO callback or render pass; off in release.
|
|
assert_no_alloc = "1.1"
|
|
# `MediaSource`: our source raises kira's decode thread's QoS; must stay kira's version.
|
|
symphonia = { version = "0.5", default-features = false }
|
|
# The audio threads' lock-free SPSC rings (output, mix tap); kira's version, so the graph has one.
|
|
rtrb = "0.3"
|
|
mint = "0.5"
|
|
# The gossip greeting's weighted draw, re-rolled per open as in the reference.
|
|
rand = "0.8"
|
|
# HMAC-SHA1 for `credential.rs`, the one place a password is kept as a blob rather than as
|
|
# itself. Was a dev-dependency only (the fake realmd's SRP6 half); now a real one.
|
|
sha1.workspace = true
|
|
|
|
# Wayland clipboard over the standard `wl_data_device`; arboard's speaks `wlr-data-control`,
|
|
# which GNOME does not implement.
|
|
[target.'cfg(all(unix, not(any(target_os = "macos", target_os = "android"))))'.dependencies]
|
|
raw-window-handle = "0.6"
|
|
smithay-clipboard = "0.7"
|
|
|
|
# A native NSCursor: winit's cursor rects revert to the arrow under a continuous render loop.
|
|
[target.'cfg(target_os = "macos")'.dependencies]
|
|
# The audio device layer (`sound::output::coreaudio`), at the versions and features cpal uses.
|
|
coreaudio-rs = { version = "0.13", default-features = false, features = ["audio_unit", "core_audio"] }
|
|
objc2-core-audio = { version = "0.3", default-features = false, features = ["std", "AudioHardware"] }
|
|
objc2-core-audio-types = { version = "0.3", default-features = false, features = ["std", "CoreAudioBaseTypes"] }
|
|
objc2 = "0.5"
|
|
objc2-app-kit = { version = "0.2", features = [
|
|
"NSApplication",
|
|
"NSResponder",
|
|
"NSRunningApplication",
|
|
"NSWindow",
|
|
"NSView",
|
|
"NSImage",
|
|
"NSCursor",
|
|
"NSEvent",
|
|
] }
|
|
objc2-foundation = { version = "0.2", features = ["NSArray", "NSData", "NSGeometry"] }
|
|
|
|
# The audio device layer off macOS (`sound::output::cpal`); on macOS cpal cannot rebuild on a
|
|
# lost default device and drops the IO-cycle timestamp.
|
|
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
|
cpal = "0.17"
|
|
|
|
# Windows CPU clocks for `perf::clock`.
|
|
[target.'cfg(windows)'.dependencies]
|
|
windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_System_Threading"] }
|
|
|
|
# The login cycle's fake realmd runs the server half of SRP6 (`net::io::cycle_tests`).
|
|
[dev-dependencies]
|
|
benilla-srp = { path = "../benilla-srp" }
|
|
num-bigint = "0.4.6"
|