fix(render): accept PowerShell camera captures
This commit is contained in:
@@ -1278,8 +1278,13 @@ fn load_legacy_camera_capture(path: &std::path::Path) -> Result<VulkanStaticCame
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_legacy_camera_capture(bytes: &[u8]) -> Result<VulkanStaticCamera, String> {
|
fn parse_legacy_camera_capture(bytes: &[u8]) -> Result<VulkanStaticCamera, String> {
|
||||||
let capture: LegacyCameraCapture = serde_json::from_slice(bytes)
|
// Windows PowerShell 5.1 writes redirected text as UTF-16LE with a BOM.
|
||||||
.map_err(|err| format!("invalid legacy camera JSON: {err}"))?;
|
// `capture-original-camera.ps1` intentionally emits plain JSON, so accept
|
||||||
|
// that normal hand-off format as well as UTF-8 without making the caller
|
||||||
|
// re-encode a read-only capture file.
|
||||||
|
let json = decode_legacy_camera_capture_json(bytes)?;
|
||||||
|
let capture: LegacyCameraCapture =
|
||||||
|
serde_json::from_str(&json).map_err(|err| format!("invalid legacy camera JSON: {err}"))?;
|
||||||
if capture.schema != "fparkan-legacy-camera-v1" {
|
if capture.schema != "fparkan-legacy-camera-v1" {
|
||||||
return Err("unsupported legacy camera capture schema".to_string());
|
return Err("unsupported legacy camera capture schema".to_string());
|
||||||
}
|
}
|
||||||
@@ -1297,6 +1302,43 @@ fn parse_legacy_camera_capture(bytes: &[u8]) -> Result<VulkanStaticCamera, Strin
|
|||||||
.ok_or_else(|| "legacy camera capture contains an invalid D3D7 camera".to_string())
|
.ok_or_else(|| "legacy camera capture contains an invalid D3D7 camera".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decode_legacy_camera_capture_json(bytes: &[u8]) -> Result<String, String> {
|
||||||
|
let decode_utf16 = |words: Vec<u16>| {
|
||||||
|
String::from_utf16(&words)
|
||||||
|
.map_err(|err| format!("invalid UTF-16 legacy camera JSON: {err}"))
|
||||||
|
};
|
||||||
|
match bytes {
|
||||||
|
[0xff, 0xfe, rest @ ..] => {
|
||||||
|
let chunks = rest.chunks_exact(2);
|
||||||
|
if !chunks.remainder().is_empty() {
|
||||||
|
return Err("invalid UTF-16LE legacy camera JSON length".to_string());
|
||||||
|
}
|
||||||
|
decode_utf16(
|
||||||
|
chunks
|
||||||
|
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
[0xfe, 0xff, rest @ ..] => {
|
||||||
|
let chunks = rest.chunks_exact(2);
|
||||||
|
if !chunks.remainder().is_empty() {
|
||||||
|
return Err("invalid UTF-16BE legacy camera JSON length".to_string());
|
||||||
|
}
|
||||||
|
decode_utf16(
|
||||||
|
chunks
|
||||||
|
.map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]]))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
[0xef, 0xbb, 0xbf, rest @ ..] => std::str::from_utf8(rest)
|
||||||
|
.map(str::to_owned)
|
||||||
|
.map_err(|err| format!("invalid UTF-8 legacy camera JSON: {err}")),
|
||||||
|
_ => std::str::from_utf8(bytes)
|
||||||
|
.map(str::to_owned)
|
||||||
|
.map_err(|err| format!("invalid UTF-8 legacy camera JSON: {err}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn json_string(value: &str) -> String {
|
fn json_string(value: &str) -> String {
|
||||||
let mut out = String::with_capacity(value.len() + 2);
|
let mut out = String::with_capacity(value.len() + 2);
|
||||||
out.push('"');
|
out.push('"');
|
||||||
@@ -1634,6 +1676,14 @@ mod tests {
|
|||||||
|
|
||||||
assert!(camera.is_finite());
|
assert!(camera.is_finite());
|
||||||
assert_eq!(camera.clip_from_world[0], 0.65_f32.cos());
|
assert_eq!(camera.clip_from_world[0], 0.65_f32.cos());
|
||||||
|
let mut utf16le = vec![0xff, 0xfe];
|
||||||
|
utf16le.extend(json.encode_utf16().flat_map(u16::to_le_bytes));
|
||||||
|
assert_eq!(
|
||||||
|
parse_legacy_camera_capture(&utf16le)
|
||||||
|
.expect("PowerShell UTF-16LE capture must be accepted")
|
||||||
|
.clip_from_world,
|
||||||
|
camera.clip_from_world
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_legacy_camera_capture(br#"{"schema":"unknown","selector0_words":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"viewport":[0,0,1,1],"near_plane":0.1,"far_plane":1.0,"field_of_view_radians":1.0}"#),
|
parse_legacy_camera_capture(br#"{"schema":"unknown","selector0_words":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"viewport":[0,0,1,1],"near_plane":0.1,"far_plane":1.0,"field_of_view_radians":1.0}"#),
|
||||||
Err("unsupported legacy camera capture schema".to_string())
|
Err("unsupported legacy camera capture schema".to_string())
|
||||||
|
|||||||
@@ -1909,6 +1909,28 @@ the licensed GOG `MISSIONS/Autodemo.00/data.tma` run with all eight roots,
|
|||||||
mesh components, 75,543 clip-visible vertices, a 1280×720 / 3,686,400-byte
|
mesh components, 75,543 clip-visible vertices, a 1280×720 / 3,686,400-byte
|
||||||
readback hash `4638497144561211935`, and validation warnings/errors `0/0`.
|
readback hash `4638497144561211935`, and validation warnings/errors `0/0`.
|
||||||
|
|
||||||
|
### Windows PowerShell camera-capture hand-off
|
||||||
|
|
||||||
|
`tools/capture-original-camera.ps1` is an observer: it opens the original
|
||||||
|
process only with `PROCESS_QUERY_INFORMATION | PROCESS_VM_READ`, samples the
|
||||||
|
active Terrain camera and emits `fparkan-legacy-camera-v1` JSON. Windows
|
||||||
|
PowerShell 5.1 commonly redirects or pipes that text as UTF-16LE with a BOM.
|
||||||
|
The Vulkan viewer now accepts BOM-marked UTF-16LE and UTF-16BE in addition to
|
||||||
|
UTF-8 (including an UTF-8 BOM), so a normal PowerShell capture can be used
|
||||||
|
directly as `--legacy-camera-capture` without a lossy manual re-encoding step.
|
||||||
|
Malformed byte lengths and invalid Unicode remain explicit load errors.
|
||||||
|
|
||||||
|
Fresh read-only evidence from the running licensed GOG AutoDemo sampled outer
|
||||||
|
camera `0x0B368FD8` in `Terrain.dll` at base `0x02510000`; selector-0
|
||||||
|
translation was `(548.020752, 559.672852, 3.00516248)`. Passing the captured
|
||||||
|
UTF-16LE file to the native Vulkan viewer rendered all eight mission roots
|
||||||
|
with `camera_mode="legacy-d3d7-capture"`, 66 mesh components, 6,787
|
||||||
|
clip-visible vertices, 71 material descriptors, a 1280x720 / 3,686,400-byte
|
||||||
|
format-50 readback hash `7904810245714997753`, and validation warnings/errors
|
||||||
|
`0/0`. This proves the live-camera hand-off and projection path; it does not
|
||||||
|
establish frame-by-frame camera-selection parity, terrain shading or pixel
|
||||||
|
parity with the original renderer.
|
||||||
|
|
||||||
### Explicit MAT0 phase preview
|
### Explicit MAT0 phase preview
|
||||||
|
|
||||||
`fparkan-game --backend static-vulkan --static-material-phase <u16>` now
|
`fparkan-game --backend static-vulkan --static-material-phase <u16>` now
|
||||||
|
|||||||
Reference in New Issue
Block a user