use crate::reader::Reader;
use serde::Serialize;
use crate::{Result, invalid};
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct UnityUrpSettings {
pub main_light_mode: i64,
pub main_light_shadows: bool,
pub additional_lights_mode: i64,
pub additional_light_shadows: bool,
pub reflection_probe_blending: bool,
pub reflection_probe_box_projection: bool,
pub reflection_probe_atlas: bool,
pub shadow_distance: f32,
pub shadow_cascades: u8,
pub soft_shadows: bool,
pub mixed_lighting: bool,
pub light_cookies: bool,
pub light_layers: bool,
pub lod_cross_fade: bool,
pub sh_eval_mode: i64,
pub light_probe_system: i64,
}
const ASSET_VERSION: i32 = 13;
pub fn read_urp_settings(bytes: &[u8]) -> Result<UnityUrpSettings> {
let mut r = Reader::new(bytes, false);
let flag = |r: &mut Reader| -> Result<bool> {
let b = r.bool()?;
r.align(4)?;
Ok(b)
};
let version = r.i32()?;
if version != ASSET_VERSION {
return Err(invalid(format!(
"Universal Render Pipeline asset version {version}: only version {ASSET_VERSION} has a verified layout"
)));
}
r.i32()?; r.i32()?; r.take(12)?; let renderers = r.u32()?;
let renderers_len = usize::try_from(renderers)
.ok()
.and_then(|n| n.checked_mul(12))
.ok_or_else(|| invalid(format!("renderer list of {renderers} entries too long")))?;
r.take(renderers_len)?;
r.i32()?; r.take(8)?; r.i32()?; r.take(8)?; r.i32()?; r.i32()?; r.f32()?; r.i32()?; r.take(4)?; r.f32()?; let lod_cross_fade = flag(&mut r)?;
r.i32()?; let sh_eval_mode = r.i32()?;
let light_probe_system = r.i32()?;
r.i32()?; r.i32()?; r.take(16)?; r.i32()?; let main_light_mode = r.i32()?;
let main_light_shadows = flag(&mut r)?;
r.i32()?; let additional_lights_mode = r.i32()?;
r.i32()?; let additional_light_shadows = flag(&mut r)?;
r.take(16)?; let reflection_probe_blending = flag(&mut r)?;
let reflection_probe_box_projection = flag(&mut r)?;
let reflection_probe_atlas = flag(&mut r)?;
let shadow_distance = r.f32()?;
let shadow_cascades = r.i32()?;
r.take(4 + 8 + 12 + 4 + 4 + 4)?; let soft_shadows = flag(&mut r)?;
r.take(4)?; r.i32()?; r.i32()?; r.i32()?; r.i32()?; r.take(8)?; let mixed_lighting = flag(&mut r)?;
let light_cookies = flag(&mut r)?;
let light_layers = flag(&mut r)?;
let in_range = |what: &str, ok: bool| -> Result<()> {
ok.then_some(()).ok_or_else(|| {
invalid(format!(
"Universal Render Pipeline asset: {what} out of range (an unverified layout)"
))
})
};
in_range("m_ShadowCascadeCount", (1..=4).contains(&shadow_cascades))?;
in_range(
"m_ShadowDistance",
shadow_distance.is_finite() && shadow_distance >= 0.0,
)?;
in_range("m_MainLightRenderingMode", (0..=2).contains(&main_light_mode))?;
in_range(
"m_AdditionalLightsRenderingMode",
(0..=2).contains(&additional_lights_mode),
)?;
in_range("m_ShEvalMode", (0..=3).contains(&sh_eval_mode))?;
Ok(UnityUrpSettings {
main_light_mode: i64::from(main_light_mode),
main_light_shadows,
additional_lights_mode: i64::from(additional_lights_mode),
additional_light_shadows,
reflection_probe_blending,
reflection_probe_box_projection,
reflection_probe_atlas,
shadow_distance,
shadow_cascades: u8::try_from(shadow_cascades).map_err(|_| {
invalid(format!(
"Universal Render Pipeline asset: m_ShadowCascadeCount {shadow_cascades} out of range"
))
})?,
soft_shadows,
mixed_lighting,
light_cookies,
light_layers,
lod_cross_fade,
sh_eval_mode: i64::from(sh_eval_mode),
light_probe_system: i64::from(light_probe_system),
})
}
#[cfg(test)]
mod tests {
use super::*;
fn asset(cascades: i32) -> Vec<u8> {
let mut b: Vec<u8> = Vec::new();
let int = |b: &mut Vec<u8>, v: i32| b.extend(v.to_le_bytes());
let float = |b: &mut Vec<u8>, v: f32| b.extend(v.to_le_bytes());
let bool4 = |b: &mut Vec<u8>, v: bool| b.extend(i32::from(v).to_le_bytes());
int(&mut b, 13); int(&mut b, 13);
int(&mut b, 1); b.extend([0; 12]); int(&mut b, 1); b.extend([0; 12]);
int(&mut b, 0); bool4(&mut b, true); bool4(&mut b, false); int(&mut b, 1);
bool4(&mut b, false); bool4(&mut b, true); int(&mut b, 0);
int(&mut b, 4); float(&mut b, 1.0);
int(&mut b, 0); bool4(&mut b, false);
float(&mut b, 0.92);
bool4(&mut b, true); int(&mut b, 1);
int(&mut b, 2); int(&mut b, 0); int(&mut b, 1024);
int(&mut b, 256);
for _ in 0..4 {
bool4(&mut b, false);
}
int(&mut b, 1); int(&mut b, 1); bool4(&mut b, true); int(&mut b, 4096);
int(&mut b, 1); int(&mut b, 8);
bool4(&mut b, true); for v in [1024, 256, 512, 1024] {
int(&mut b, v);
}
bool4(&mut b, true); bool4(&mut b, false); bool4(&mut b, true); float(&mut b, 400.0);
int(&mut b, cascades);
float(&mut b, 0.25);
for v in [0.1, 0.3, 0.123, 0.29, 0.54, 0.11, 0.1, 0.5] {
float(&mut b, v);
}
bool4(&mut b, true); bool4(&mut b, true); int(&mut b, 64);
int(&mut b, 3);
int(&mut b, 2048);
int(&mut b, 3);
bool4(&mut b, true);
bool4(&mut b, false);
bool4(&mut b, true); bool4(&mut b, false); bool4(&mut b, true); b
}
#[test]
fn a_urp_assets_settings_are_read_by_the_verified_layout() {
let s = read_urp_settings(&asset(4)).unwrap();
assert_eq!((s.shadow_cascades, s.shadow_distance), (4, 400.0));
assert!(s.main_light_shadows && s.soft_shadows && s.additional_light_shadows);
assert!(
s.reflection_probe_blending && !s.reflection_probe_box_projection && s.reflection_probe_atlas
);
assert!(s.mixed_lighting && !s.light_cookies && s.light_layers && s.lod_cross_fade);
assert_eq!(
(s.main_light_mode, s.additional_lights_mode, s.sh_eval_mode),
(1, 1, 2)
);
let mut other = asset(4);
other[0] = 12;
assert!(read_urp_settings(&other).is_err());
assert!(read_urp_settings(&asset(7)).is_err());
assert!(read_urp_settings(&asset(4)[..100]).is_err());
}
}