use crate::player::PPtr;
use crate::reader::Reader;
use crate::version::UnityVersion;
use serde::Serialize;
use std::collections::BTreeMap;
use crate::{Result, invalid};
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct UnityUrpSettings {
pub renderers: Vec<PPtr>,
pub default_renderer: i64,
pub require_depth_texture: bool,
pub require_opaque_texture: bool,
pub msaa: i64,
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;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum UrpAssetLayout {
Urp17_3,
}
const URP_17_3_PLAYERS: [&str; 2] = ["6000.3.12f1", "6000.3.15f1"];
impl UrpAssetLayout {
pub fn of(unity: UnityVersion) -> Result<Self> {
for v in URP_17_3_PLAYERS {
if UnityVersion::parse(v)? == unity {
return Ok(UrpAssetLayout::Urp17_3);
}
}
Err(invalid(format!(
"a Universal Render Pipeline asset of a Unity {unity} player: its layout is verified only on the players \
of Unity {} (the package's fields are not recorded in the asset, and other players store others)",
URP_17_3_PLAYERS.join(" and ")
)))
}
}
pub fn read_urp_settings(bytes: &[u8], layout: UrpAssetLayout) -> Result<UnityUrpSettings> {
match layout {
UrpAssetLayout::Urp17_3 => {}
}
let mut r = Reader::new(bytes, false);
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 = pptrs(&mut r, "m_RendererDataList")?;
let default_renderer = r.i32()?;
let require_depth_texture = flag(&mut r)?;
let require_opaque_texture = flag(&mut r)?;
r.i32()?; r.take(8)?; r.i32()?; let msaa = 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)?;
r.i32()?; r.i32()?; flag(&mut r)?; r.i32()?; r.i32()?; for _ in 0..4 {
flag(&mut r)?;
}
r.i32()?; r.f32()?; flag(&mut r)?; r.i32()?; flag(&mut r)?; r.take(12)?; r.i32()?; pptr(&mut r)?; strings(&mut r, "apvScenesData.obsoleteSceneBounds.m_Keys")?;
let n = list_len(&mut r, "apvScenesData.obsoleteSceneBounds.m_Values", 24)?;
for _ in 0..n {
r.take(24)?; }
strings(&mut r, "apvScenesData.obsoleteHasProbeVolumes.m_Keys")?;
let n = list_len(&mut r, "apvScenesData.obsoleteHasProbeVolumes.m_Values", 1)?;
for _ in 0..n {
r.bool()?;
}
r.align(4)?;
r.i32()?; r.i32()?; r.take(24)?; used_up(&r, "UniversalRenderPipelineAsset")?;
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))?;
in_range("m_MSAA", [1, 2, 4, 8].contains(&msaa))?;
in_range(
"m_DefaultRendererIndex",
usize::try_from(default_renderer).is_ok_and(|i| i < renderers.len()),
)?;
Ok(UnityUrpSettings {
renderers,
default_renderer: i64::from(default_renderer),
require_depth_texture,
require_opaque_texture,
msaa: i64::from(msaa),
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),
})
}
fn flag(r: &mut Reader) -> Result<bool> {
let b = r.bool()?;
r.align(4)?;
Ok(b)
}
fn strings(r: &mut Reader, what: &str) -> Result<Vec<String>> {
let n = list_len(r, what, 4)?;
(0..n)
.map(|_| {
let len = list_len(r, what, 1)?;
let s = std::str::from_utf8(r.take(len)?)
.map_err(|_| invalid(format!("{what}: a string that is not UTF-8")))?
.to_owned();
r.align(4)?;
Ok(s)
})
.collect()
}
fn pptr(r: &mut Reader) -> Result<PPtr> {
Ok(PPtr {
file_id: r.i32()?,
path_id: r.i64()?,
})
}
fn list_len(r: &mut Reader, what: &str, size: usize) -> Result<usize> {
let n = r.u32()?;
usize::try_from(n)
.ok()
.filter(|&n| n.checked_mul(size).is_some_and(|len| len <= r.remaining()))
.ok_or_else(|| {
invalid(format!(
"{what}: {n} entries of {size} bytes, {} bytes left",
r.remaining()
))
})
}
fn pptrs(r: &mut Reader, what: &str) -> Result<Vec<PPtr>> {
let n = list_len(r, what, 12)?;
(0..n).map(|_| pptr(r)).collect()
}
fn choice(r: &mut Reader, what: &str, values: &[i32]) -> Result<i64> {
let v = r.i32()?;
if values.contains(&v) {
Ok(i64::from(v))
} else {
Err(invalid(format!(
"{what} {v} (the values: {values:?}; an unverified layout)"
)))
}
}
fn first(n: i32) -> Vec<i32> {
(0..n).collect()
}
fn used_up(r: &Reader, what: &str) -> Result<()> {
match r.remaining() {
0 => Ok(()),
n => Err(invalid(format!(
"{what}: {n} bytes past the verified layout's {} (another layout)",
r.pos
))),
}
}
const RENDERER_ASSET_VERSION: i32 = 3;
const RENDERER_TAIL: usize = 96;
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct UnityStencilState {
pub override_stencil: bool,
pub reference: i64,
pub compare: i64,
pub pass: i64,
pub fail: i64,
pub z_fail: i64,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct UnityUrpRenderer {
pub features: Vec<PPtr>,
pub feature_map: Vec<i64>,
pub native_render_pass: bool,
pub xr_system_data: PPtr,
pub post_process_data: PPtr,
pub prepass_layer_mask: u32,
pub opaque_layer_mask: u32,
pub transparent_layer_mask: u32,
pub stencil: UnityStencilState,
pub shadow_transparent_receive: bool,
pub rendering_mode: i64,
pub depth_priming_mode: i64,
pub copy_depth_mode: i64,
pub depth_attachment_format: i64,
pub depth_texture_format: i64,
pub accurate_gbuffer_normals: bool,
pub intermediate_texture_mode: i64,
}
pub fn read_urp_renderer(bytes: &[u8]) -> Result<UnityUrpRenderer> {
const WHAT: &str = "Universal Render Pipeline renderer";
let mut r = Reader::new(bytes, false);
r.take(12 * 9)?;
let features = pptrs(&mut r, "m_RendererFeatures")?;
let n = list_len(&mut r, "m_RendererFeatureMap", 8)?;
let feature_map = (0..n).map(|_| r.i64()).collect::<Result<Vec<_>>>()?;
let native_render_pass = flag(&mut r)?;
if r.remaining() != RENDERER_TAIL {
return Err(invalid(format!(
"{WHAT}: {} bytes after m_UseNativeRenderPass, the verified layout stores {RENDERER_TAIL} (84 is the layout \
without xrSystemData, which is not verified)",
r.remaining()
)));
}
let xr_system_data = pptr(&mut r)?;
let post_process_data = pptr(&mut r)?;
let version = r.i32()?;
if version != RENDERER_ASSET_VERSION {
return Err(invalid(format!(
"{WHAT} asset version {version}: only version {RENDERER_ASSET_VERSION} has a verified layout"
)));
}
let prepass_layer_mask = r.u32()?;
let opaque_layer_mask = r.u32()?;
let transparent_layer_mask = r.u32()?;
let stencil = UnityStencilState {
override_stencil: flag(&mut r)?,
reference: i64::from(r.i32()?),
compare: choice(&mut r, "stencilCompareFunction", &first(9))?,
pass: choice(&mut r, "passOperation", &first(8))?,
fail: choice(&mut r, "failOperation", &first(8))?,
z_fail: choice(&mut r, "zFailOperation", &first(8))?,
};
let depth_formats = [0, 90, 91, 92, 93, 94, 151];
let renderer = UnityUrpRenderer {
features,
feature_map,
native_render_pass,
xr_system_data,
post_process_data,
prepass_layer_mask,
opaque_layer_mask,
transparent_layer_mask,
stencil,
shadow_transparent_receive: flag(&mut r)?,
rendering_mode: choice(&mut r, "m_RenderingMode", &first(4))?,
depth_priming_mode: choice(&mut r, "m_DepthPrimingMode", &first(3))?,
copy_depth_mode: choice(&mut r, "m_CopyDepthMode", &first(3))?,
depth_attachment_format: choice(&mut r, "m_DepthAttachmentFormat", &depth_formats)?,
depth_texture_format: choice(&mut r, "m_DepthTextureFormat", &depth_formats)?,
accurate_gbuffer_normals: flag(&mut r)?,
intermediate_texture_mode: choice(&mut r, "m_IntermediateTextureMode", &first(2))?,
};
used_up(&r, WHAT)?;
Ok(renderer)
}
pub fn read_urp_feature_active(bytes: &[u8]) -> Result<bool> {
flag(&mut Reader::new(bytes, false))
}
pub const URP_DECAL_FEATURE: &str = "UnityEngine.Rendering.Universal.DecalRendererFeature";
pub const URP_FULL_SCREEN_FEATURE: &str = "UnityEngine.Rendering.Universal.FullScreenPassRendererFeature";
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct UnityUrpDecalFeature {
pub active: bool,
pub technique: i64,
pub max_draw_distance: f32,
pub decal_layers: bool,
pub dbuffer_surface_data: i64,
pub screen_space_normal_blend: i64,
}
pub fn read_urp_decal_feature(bytes: &[u8]) -> Result<UnityUrpDecalFeature> {
let mut r = Reader::new(bytes, false);
let feature = UnityUrpDecalFeature {
active: flag(&mut r)?,
technique: choice(&mut r, "DecalSettings.technique", &first(3))?,
max_draw_distance: r.f32()?,
decal_layers: flag(&mut r)?,
dbuffer_surface_data: choice(&mut r, "DBufferSettings.surfaceData", &first(3))?,
screen_space_normal_blend: choice(&mut r, "DecalScreenSpaceSettings.normalBlend", &first(3))?,
};
used_up(&r, "Decal renderer feature")?;
Ok(feature)
}
const FULL_SCREEN_VERSION: i32 = 1;
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct UnityUrpFullScreenFeature {
pub active: bool,
pub injection_point: i64,
pub fetch_color_buffer: bool,
pub requirements: i64,
pub pass_material: PPtr,
pub pass_index: i64,
pub bind_depth_stencil_attachment: bool,
}
pub fn read_urp_full_screen_feature(bytes: &[u8]) -> Result<UnityUrpFullScreenFeature> {
const WHAT: &str = "Full Screen Pass renderer feature";
let mut r = Reader::new(bytes, false);
let active = flag(&mut r)?;
let injection_point = choice(&mut r, "injectionPoint", &[450, 550, 600])?;
let fetch_color_buffer = flag(&mut r)?;
let requirements = i64::from(r.i32()?);
let pass_material = pptr(&mut r)?;
let pass_index = i64::from(r.i32()?);
let bind_depth_stencil_attachment = flag(&mut r)?;
let version = r.i32()?;
if version != FULL_SCREEN_VERSION {
return Err(invalid(format!(
"{WHAT} m_Version {version}: only version {FULL_SCREEN_VERSION} is verified"
)));
}
used_up(&r, WHAT)?;
Ok(UnityUrpFullScreenFeature {
active,
injection_point,
fetch_color_buffer,
requirements,
pass_material,
pass_index,
bind_depth_stencil_attachment,
})
}
pub const URP_CAMERA_DATA: &str = "UnityEngine.Rendering.Universal.UniversalAdditionalCameraData";
const CAMERA_VERSION: i32 = 2;
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct UnityTaaSettings {
pub quality: i64,
pub frame_influence: f32,
pub jitter_scale: f32,
pub mip_bias: f32,
pub variance_clamp_scale: f32,
pub contrast_adaptive_sharpening: f32,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct UnityUrpCamera {
pub render_shadows: bool,
pub depth_texture_option: i64,
pub opaque_texture_option: i64,
pub camera_type: i64,
pub stack: Vec<PPtr>,
pub renderer_index: i64,
pub volume_layer_mask: u32,
pub volume_trigger: PPtr,
pub volume_update_mode: i64,
pub render_post_processing: bool,
pub antialiasing: i64,
pub antialiasing_quality: i64,
pub stop_nan: bool,
pub dithering: bool,
pub clear_depth: bool,
pub allow_xr_rendering: bool,
pub allow_hdr_output: bool,
pub use_screen_coord_override: bool,
pub screen_size_override: [f32; 4],
pub screen_coord_scale_bias: [f32; 4],
pub requires_depth_texture: bool,
pub requires_color_texture: bool,
pub taa: UnityTaaSettings,
}
pub fn read_urp_camera(bytes: &[u8]) -> Result<UnityUrpCamera> {
const WHAT: &str = "Universal Render Pipeline camera data";
let mut r = Reader::new(bytes, false);
let vec4 = |r: &mut Reader| -> Result<[f32; 4]> { Ok([r.f32()?, r.f32()?, r.f32()?, r.f32()?]) };
let camera = UnityUrpCamera {
render_shadows: flag(&mut r)?,
depth_texture_option: choice(&mut r, "m_RequiresDepthTextureOption", &first(3))?,
opaque_texture_option: choice(&mut r, "m_RequiresOpaqueTextureOption", &first(3))?,
camera_type: choice(&mut r, "m_CameraType", &first(2))?,
stack: pptrs(&mut r, "m_Cameras")?,
renderer_index: i64::from(r.i32()?),
volume_layer_mask: r.u32()?,
volume_trigger: pptr(&mut r)?,
volume_update_mode: choice(&mut r, "m_VolumeFrameworkUpdateModeOption", &first(3))?,
render_post_processing: flag(&mut r)?,
antialiasing: choice(&mut r, "m_Antialiasing", &first(4))?,
antialiasing_quality: choice(&mut r, "m_AntialiasingQuality", &first(3))?,
stop_nan: flag(&mut r)?,
dithering: flag(&mut r)?,
clear_depth: flag(&mut r)?,
allow_xr_rendering: flag(&mut r)?,
allow_hdr_output: flag(&mut r)?,
use_screen_coord_override: flag(&mut r)?,
screen_size_override: vec4(&mut r)?,
screen_coord_scale_bias: vec4(&mut r)?,
requires_depth_texture: flag(&mut r)?,
requires_color_texture: flag(&mut r)?,
taa: UnityTaaSettings {
quality: choice(&mut r, "TemporalAA.Settings.m_Quality", &first(5))?,
frame_influence: r.f32()?,
jitter_scale: r.f32()?,
mip_bias: r.f32()?,
variance_clamp_scale: r.f32()?,
contrast_adaptive_sharpening: r.f32()?,
},
};
let version = r.i32()?;
if version != CAMERA_VERSION {
return Err(invalid(format!(
"{WHAT} m_Version {version}: only version {CAMERA_VERSION} is verified"
)));
}
used_up(&r, WHAT)?;
Ok(camera)
}
pub const URP_GLOBAL_SETTINGS: &str = "UnityEngine.Rendering.Universal.UniversalRenderPipelineGlobalSettings";
const GLOBAL_SETTINGS_VERSION: i32 = 10;
const REFERENCES_VERSION: i32 = 2;
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct UnityUrpGlobalSettings {
pub rendering_layer_names: Vec<String>,
pub valid_rendering_layers: u32,
pub settings: Vec<UnityGraphicsSettings>,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct UnityGraphicsSettings {
pub rid: i64,
pub class: String,
pub assembly: String,
pub fields: Vec<(&'static str, UnityField)>,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum UnityField {
Int(i64),
Bool(bool),
Ref(PPtr),
Refs(Vec<PPtr>),
Colours(Vec<[f32; 4]>),
}
#[derive(Clone, Copy, Debug)]
enum FieldKind {
Version(i32),
Int,
Bool,
Ref,
Refs,
Colours,
}
use FieldKind as F;
const SHADER_STRIPPING_FIELDS: &[(&str, FieldKind)] = &[
("m_Version", F::Version(0)),
("m_ExportShaderVariants", F::Bool),
("m_ShaderVariantLogLevel", F::Int),
("m_StripRuntimeDebugShaders", F::Bool),
];
const GRAPHICS_SETTINGS: [(&str, &[(&str, FieldKind)]); 18] = [
(
"UnityEngine.Rendering.Universal.UniversalRenderPipelineRuntimeTerrainShaders",
&[
("m_Version", F::Version(0)),
("m_TerrainDetailLit", F::Ref),
("m_TerrainDetailGrassBillboard", F::Ref),
("m_TerrainDetailGrass", F::Ref),
],
),
(
"UnityEngine.Rendering.URPReflectionProbeSettings",
&[
("version", F::Version(1)),
("useReflectionProbeRotation", F::Bool),
],
),
(
"UnityEngine.Rendering.VrsRenderPipelineRuntimeResources",
&[
("m_TextureComputeShader", F::Ref),
("m_VisualizationShader", F::Ref),
("m_VisualizationLookupTable", F::Colours),
("m_ConversionLookupTable", F::Colours),
],
),
(
"UnityEngine.Rendering.LightmapSamplingSettings",
&[
("m_Version", F::Version(1)),
("m_UseBicubicLightmapSampling", F::Bool),
],
),
(
URP_RENDER_GRAPH_SETTINGS,
&[
("m_Version", F::Version(0)),
("m_EnableRenderCompatibilityMode", F::Bool),
],
),
(
URP_RENDERER_RESOURCES,
&[
("m_Version", F::Version(0)),
("m_CopyDepthPS", F::Ref),
("m_CameraMotionVector", F::Ref),
("m_StencilDeferredPS", F::Ref),
("m_ClusterDeferred", F::Ref),
("m_StencilDitherMaskSeedPS", F::Ref),
("m_DBufferClear", F::Ref),
],
),
(
"UnityEngine.Rendering.Universal.URPDefaultVolumeProfileSettings",
&[("m_Version", F::Version(0)), ("m_VolumeProfile", F::Ref)],
),
(
"UnityEngine.Rendering.Universal.UniversalRenderPipelineRuntimeShaders",
&[
("m_Version", F::Version(0)),
("m_FallbackErrorShader", F::Ref),
("m_BlitHDROverlay", F::Ref),
("m_CoreBlitPS", F::Ref),
("m_CoreBlitColorAndDepthPS", F::Ref),
("m_SamplingPS", F::Ref),
("m_TerrainDetailLit", F::Ref),
("m_TerrainDetailGrassBillboard", F::Ref),
("m_TerrainDetailGrass", F::Ref),
],
),
(
"UnityEngine.Rendering.Universal.UniversalRenderPipelineRuntimeXRResources",
&[
("m_xrOcclusionMeshPS", F::Ref),
("m_xrMirrorViewPS", F::Ref),
("m_xrMotionVector", F::Ref),
],
),
(
"UnityEngine.Rendering.Universal.UniversalRenderPipelineDebugShaders",
&[
("m_DebugReplacementPS", F::Ref),
("m_HdrDebugViewPS", F::Ref),
("m_ProbeVolumeSamplingDebugComputeShader", F::Ref),
],
),
(
"UnityEngine.Rendering.Universal.UniversalRenderPipelineRuntimeTextures",
&[
("m_Version", F::Version(1)),
("m_BlueNoise64LTex", F::Ref),
("m_BayerMatrixTex", F::Ref),
("m_DebugFontTex", F::Ref),
],
),
(
"UnityEngine.Rendering.RenderGraphGlobalSettings",
&[
("m_version", F::Version(0)),
("m_EnableCompilationCaching", F::Bool),
("m_EnableValidityChecks", F::Bool),
],
),
(
"UnityEngine.Rendering.RenderGraphModule.Util.RenderGraphUtilsResources",
&[("m_Version", F::Version(0)), ("m_CoreCopyPS", F::Ref)],
),
(
"UnityEngine.Rendering.ShaderStrippingSetting",
SHADER_STRIPPING_FIELDS,
),
(
"UnityEngine.Rendering.GPUResidentDrawerResources",
&[
("m_Version", F::Version(0)),
("m_InstanceDataBufferCopyKernels", F::Ref),
("m_InstanceDataBufferUploadKernels", F::Ref),
("m_TransformUpdaterKernels", F::Ref),
("m_WindDataUpdaterKernels", F::Ref),
("m_OccluderDepthPyramidKernels", F::Ref),
("m_InstanceOcclusionCullingKernels", F::Ref),
("m_OcclusionCullingDebugKernels", F::Ref),
("m_DebugOcclusionTestPS", F::Ref),
("m_DebugOccluderPS", F::Ref),
],
),
(
"UnityEngine.Rendering.STP/RuntimeResources",
&[("m_setupCS", F::Ref), ("m_preTaaCS", F::Ref), ("m_taaCS", F::Ref)],
),
(
"UnityEngine.Rendering.Universal.ScreenSpaceAmbientOcclusionPersistentResources",
&[("m_Shader", F::Ref), ("m_Version", F::Version(0))],
),
(
"UnityEngine.Rendering.Universal.ScreenSpaceAmbientOcclusionDynamicResources",
&[("m_BlueNoise256Textures", F::Refs), ("m_Version", F::Version(0))],
),
];
pub fn verified_graphics_settings() -> impl Iterator<Item = &'static str> {
GRAPHICS_SETTINGS.iter().map(|(c, _)| *c)
}
pub const URP_RENDER_GRAPH_SETTINGS: &str = "UnityEngine.Rendering.Universal.RenderGraphSettings";
pub const URP_RENDERER_RESOURCES: &str = "UnityEngine.Rendering.Universal.UniversalRendererResources";
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
pub struct UnityUrpRendererResources {
pub copy_depth: PPtr,
pub camera_motion_vector: PPtr,
pub stencil_deferred: PPtr,
pub cluster_deferred: PPtr,
pub stencil_dither_mask_seed: PPtr,
pub dbuffer_clear: PPtr,
}
impl UnityGraphicsSettings {
pub fn field(&self, name: &str) -> Option<&UnityField> {
self.fields.iter().find(|(n, _)| *n == name).map(|(_, v)| v)
}
}
impl UnityUrpGlobalSettings {
pub fn entry(&self, class: &str) -> Option<&UnityGraphicsSettings> {
self.settings.iter().find(|s| s.class == class)
}
pub fn renderer_resources(&self) -> Option<UnityUrpRendererResources> {
let e = self.entry(URP_RENDERER_RESOURCES)?;
let r = |name: &str| match e.field(name) {
Some(UnityField::Ref(p)) => Some(*p),
_ => None,
};
Some(UnityUrpRendererResources {
copy_depth: r("m_CopyDepthPS")?,
camera_motion_vector: r("m_CameraMotionVector")?,
stencil_deferred: r("m_StencilDeferredPS")?,
cluster_deferred: r("m_ClusterDeferred")?,
stencil_dither_mask_seed: r("m_StencilDitherMaskSeedPS")?,
dbuffer_clear: r("m_DBufferClear")?,
})
}
pub fn render_compatibility_mode(&self) -> Option<bool> {
match self
.entry(URP_RENDER_GRAPH_SETTINGS)?
.field("m_EnableRenderCompatibilityMode")
{
Some(UnityField::Bool(b)) => Some(*b),
_ => None,
}
}
}
fn string(r: &mut Reader, what: &str) -> Result<String> {
let len = list_len(r, what, 1)?;
let s = std::str::from_utf8(r.take(len)?)
.map_err(|_| invalid(format!("{what}: a string that is not UTF-8")))?
.to_owned();
r.align(4)?;
Ok(s)
}
fn settings_fields(
r: &mut Reader,
class: &str,
fields: &[(&'static str, FieldKind)],
) -> Result<Vec<(&'static str, UnityField)>> {
fields
.iter()
.map(|&(name, kind)| {
let what = format!("{class}.{name}");
let value = match kind {
FieldKind::Version(want) => {
let v = r.i32()?;
if v != want {
return Err(invalid(format!(
"{what} {v}: only version {want} has a verified layout"
)));
}
UnityField::Int(i64::from(v))
}
FieldKind::Int => UnityField::Int(i64::from(r.i32()?)),
FieldKind::Bool => UnityField::Bool(flag(r)?),
FieldKind::Ref => UnityField::Ref(pptr(r)?),
FieldKind::Refs => UnityField::Refs(pptrs(r, &what)?),
FieldKind::Colours => {
let n = list_len(r, &what, 16)?;
UnityField::Colours(
(0..n)
.map(|_| Ok([r.f32()?, r.f32()?, r.f32()?, r.f32()?]))
.collect::<Result<_>>()?,
)
}
};
Ok((name, value))
})
.collect()
}
fn empty_list(r: &mut Reader, what: &str) -> Result<()> {
match r.u32()? {
0 => Ok(()),
n => Err(invalid(format!(
"{what}: {n} entries, whose layout is not verified (only empty lists are read)"
))),
}
}
pub fn read_urp_global_settings(bytes: &[u8]) -> Result<UnityUrpGlobalSettings> {
const WHAT: &str = "Universal Render Pipeline global settings";
let mut r = Reader::new(bytes, false);
settings_fields(&mut r, "m_ShaderStrippingSetting", SHADER_STRIPPING_FIELDS)?;
settings_fields(
&mut r,
"m_URPShaderStrippingSetting",
&[
("m_Version", F::Version(0)),
("m_StripUnusedPostProcessingVariants", F::Bool),
("m_StripUnusedVariants", F::Bool),
("m_StripScreenCoordOverrideVariants", F::Bool),
],
)?;
settings_fields(
&mut r,
WHAT,
&[
("m_ShaderVariantLogLevel", F::Int),
("m_ExportShaderVariants", F::Bool),
("m_StripDebugVariants", F::Bool),
("m_StripUnusedPostProcessingVariants", F::Bool),
("m_StripUnusedVariants", F::Bool),
("m_StripScreenCoordOverrideVariants", F::Bool),
("supportRuntimeDebugDisplay", F::Bool),
("m_EnableRenderGraph", F::Bool),
],
)?;
let n = list_len(&mut r, "m_Settings.m_RuntimeSettings.m_List", 8)?;
let list: Vec<i64> = (0..n).map(|_| r.i64()).collect::<Result<_>>()?;
let version = r.i32()?;
if version != GLOBAL_SETTINGS_VERSION {
return Err(invalid(format!(
"{WHAT} m_AssetVersion {version}: only version {GLOBAL_SETTINGS_VERSION} has a verified layout"
)));
}
pptr(&mut r)?; let rendering_layer_names = strings(&mut r, "m_RenderingLayerNames")?;
let valid_rendering_layers = r.u32()?;
for i in 0..8 {
string(&mut r, &format!("lightLayerName{i}"))?;
}
for list in [
"obsoleteSceneBounds keys",
"obsoleteSceneBounds values",
"obsoleteHasProbeVolumes keys",
"obsoleteHasProbeVolumes values",
] {
empty_list(&mut r, &format!("apvScenesData.{list}"))?;
}
let registry = r.i32()?;
if registry != REFERENCES_VERSION {
return Err(invalid(format!(
"{WHAT}: managed-reference registry version {registry} (read: {REFERENCES_VERSION})"
)));
}
let n = list_len(&mut r, "the managed-reference registry", 20)?;
let mut entries: BTreeMap<i64, UnityGraphicsSettings> = BTreeMap::new();
for _ in 0..n {
let rid = r.i64()?;
let class = string(&mut r, "a managed reference's class")?;
let namespace = string(&mut r, "a managed reference's namespace")?;
let assembly = string(&mut r, "a managed reference's assembly")?;
let class = match namespace.as_str() {
"" => class,
ns => format!("{ns}.{class}"),
};
let (_, layout) = GRAPHICS_SETTINGS.iter().find(|(c, _)| *c == class).ok_or_else(|| {
invalid(format!(
"{WHAT}: a graphics settings entry of class {class} ({assembly}), whose layout is not verified"
))
})?;
let fields = settings_fields(&mut r, &class, layout)?;
let entry = UnityGraphicsSettings {
rid,
class,
assembly,
fields,
};
if let Some(first) = entries.insert(rid, entry) {
return Err(invalid(format!(
"{WHAT}: managed reference {rid} stored twice ({})",
first.class
)));
}
}
used_up(&r, WHAT)?;
let settings = list
.iter()
.map(|rid| {
entries.remove(rid).ok_or_else(|| {
invalid(format!(
"{WHAT}: the settings list names managed reference {rid}, which the registry does not hold"
))
})
})
.collect::<Result<Vec<_>>>()?;
if let Some(e) = entries.values().next() {
return Err(invalid(format!(
"{WHAT}: managed reference {} ({}) that the settings list does not name",
e.rid, e.class
)));
}
let mut classes = std::collections::BTreeSet::new();
for s in &settings {
if !classes.insert(s.class.as_str()) {
return Err(invalid(format!("{WHAT}: the class {} listed twice", s.class)));
}
}
Ok(UnityUrpGlobalSettings {
rendering_layer_names,
valid_rendering_layers,
settings,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn asset(cascades: i32, default_renderer: 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); int(&mut b, 0);
b.extend(6168i64.to_le_bytes());
int(&mut b, default_renderer);
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); int(&mut b, 0);
int(&mut b, 0);
bool4(&mut b, true);
int(&mut b, 0);
int(&mut b, 32);
for _ in 0..4 {
bool4(&mut b, true);
}
int(&mut b, 0);
float(&mut b, 0.0);
bool4(&mut b, false);
int(&mut b, 1);
bool4(&mut b, false);
for v in [256, 0, 256, 2] {
int(&mut b, v);
}
b.extend([0; 12]); int(&mut b, 1);
int(&mut b, 5);
b.extend(b"scene\0\0\0");
int(&mut b, 1);
b.extend([0; 24]);
int(&mut b, 1);
int(&mut b, 5);
b.extend(b"scene\0\0\0");
int(&mut b, 1);
b.extend([1, 0, 0, 0]);
int(&mut b, 0); int(&mut b, 1); b.extend([0; 24]); b
}
#[test]
fn a_urp_assets_settings_are_read_by_the_verified_layout() {
let read = |b: &[u8]| read_urp_settings(b, UrpAssetLayout::Urp17_3);
let s = read(&asset(4, 0)).unwrap();
let renderer = PPtr {
file_id: 0,
path_id: 6168,
};
assert_eq!((s.renderers.as_slice(), s.default_renderer), (&[renderer][..], 0));
assert!(s.require_depth_texture && !s.require_opaque_texture);
assert_eq!(s.msaa, 4);
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, 0);
other[0] = 12;
assert!(read(&other).is_err());
assert!(read(&asset(7, 0)).is_err());
let past = read(&asset(4, 1)).unwrap_err().to_string();
assert!(past.contains("m_DefaultRendererIndex"), "{past}");
let whole = asset(4, 0);
assert!(read(&whole[..whole.len() - 4]).is_err());
let mut longer = whole.clone();
longer.extend([0; 4]);
let longer = read(&longer).unwrap_err().to_string();
assert!(longer.contains("past the verified layout"), "{longer}");
}
#[test]
fn a_urp_assets_layout_is_identified_by_the_players_unity_version() {
let of = |v: &str| UrpAssetLayout::of(UnityVersion::parse(v).unwrap());
assert_eq!(of("6000.3.12f1").unwrap(), UrpAssetLayout::Urp17_3);
assert_eq!(of("6000.3.15f1").unwrap(), UrpAssetLayout::Urp17_3);
let refused = of("6000.3.17f1").unwrap_err().to_string();
assert!(refused.contains("Unity 6000.3.17f1 player"), "{refused}");
assert!(of("6000.6.4f1").is_err());
}
fn int(b: &mut Vec<u8>, v: i32) {
b.extend(v.to_le_bytes());
}
fn reference(b: &mut Vec<u8>, path_id: i64) {
int(b, 0);
b.extend(path_id.to_le_bytes());
}
fn renderer(version: i32, mode: i32) -> Vec<u8> {
let mut b = vec![0; 12 * 9]; int(&mut b, 2);
reference(&mut b, 6001);
reference(&mut b, 6002);
int(&mut b, 2);
b.extend(7i64.to_le_bytes());
b.extend((-8i64).to_le_bytes());
int(&mut b, 1); reference(&mut b, 0); reference(&mut b, 6170); int(&mut b, version);
for mask in [0x8a30_9437u32, 0x8a31_d437, u32::MAX] {
b.extend(mask.to_le_bytes());
}
for v in [1, 3, 8, 2, 0, 0] {
int(&mut b, v); }
int(&mut b, 1); int(&mut b, mode);
int(&mut b, 0); int(&mut b, 1); int(&mut b, 92);
int(&mut b, 0);
int(&mut b, 0);
int(&mut b, 1); b
}
#[test]
fn a_urp_renderer_is_read_by_the_verified_layout() {
let r = read_urp_renderer(&renderer(3, 2)).unwrap();
let ids: Vec<i64> = r.features.iter().map(|f| f.path_id).collect();
assert_eq!((ids, r.feature_map.clone()), (vec![6001, 6002], vec![7, -8]));
assert!(r.native_render_pass && r.shadow_transparent_receive && !r.accurate_gbuffer_normals);
assert_eq!((r.xr_system_data.path_id, r.post_process_data.path_id), (0, 6170));
assert_eq!(
(
r.prepass_layer_mask,
r.opaque_layer_mask,
r.transparent_layer_mask
),
(0x8a30_9437, 0x8a31_d437, u32::MAX)
);
assert_eq!(
r.stencil,
UnityStencilState {
override_stencil: true,
reference: 3,
compare: 8,
pass: 2,
fail: 0,
z_fail: 0
}
);
assert_eq!(
(r.rendering_mode, r.depth_priming_mode, r.copy_depth_mode),
(2, 0, 1)
);
assert_eq!(
(
r.depth_attachment_format,
r.depth_texture_format,
r.intermediate_texture_mode
),
(92, 0, 1)
);
let refusal = |b: &[u8]| read_urp_renderer(b).unwrap_err().to_string();
assert!(refusal(&renderer(2, 2)).contains("asset version 2"));
assert!(refusal(&renderer(3, 4)).contains("m_RenderingMode 4"));
let mut longer = renderer(3, 2);
longer.extend([0; 4]);
assert!(refusal(&longer).contains("100 bytes after m_UseNativeRenderPass"));
let mut no_xr = renderer(3, 2);
no_xr.drain(160..172);
assert!(refusal(&no_xr).contains("84 bytes after"), "{}", refusal(&no_xr));
let mut map = renderer(3, 2);
map[136] = 1; map.drain(148..156);
let short = read_urp_renderer(&map).unwrap();
assert_eq!((short.features.len(), short.feature_map), (2, vec![7]));
assert_eq!(short.post_process_data.path_id, 6170);
assert!(read_urp_renderer(&renderer(3, 2)[..150]).is_err());
}
#[test]
fn urp_features_are_read_by_the_verified_layout() {
assert!(read_urp_feature_active(&[1, 0, 0, 0, 9, 9]).unwrap());
assert!(!read_urp_feature_active(&[0, 0, 0, 0]).unwrap());
assert!(read_urp_feature_active(&[2, 0, 0, 0]).is_err());
let decal = |technique: i32| {
let mut b = Vec::new();
int(&mut b, 1);
int(&mut b, technique);
b.extend(500f32.to_le_bytes());
int(&mut b, 0);
int(&mut b, 2);
int(&mut b, 0);
b
};
assert_eq!(
read_urp_decal_feature(&decal(1)).unwrap(),
UnityUrpDecalFeature {
active: true,
technique: 1,
max_draw_distance: 500.0,
decal_layers: false,
dbuffer_surface_data: 2,
screen_space_normal_blend: 0
}
);
assert!(read_urp_decal_feature(&decal(3)).is_err());
let mut longer = decal(1);
longer.extend([0; 4]);
assert!(read_urp_decal_feature(&longer).is_err());
let full_screen = |injection: i32, version: i32| {
let mut b = Vec::new();
int(&mut b, 1);
int(&mut b, injection);
int(&mut b, 1);
int(&mut b, -1); reference(&mut b, 7);
int(&mut b, 0);
int(&mut b, 0);
int(&mut b, version);
b
};
let f = read_urp_full_screen_feature(&full_screen(600, 1)).unwrap();
assert!(f.active && f.fetch_color_buffer && !f.bind_depth_stencil_attachment);
assert_eq!(
(
f.injection_point,
f.requirements,
f.pass_material.path_id,
f.pass_index
),
(600, -1, 7, 0)
);
let refusal = |b: &[u8]| read_urp_full_screen_feature(b).unwrap_err().to_string();
assert!(refusal(&full_screen(300, 1)).contains("injectionPoint 300"));
assert!(refusal(&full_screen(600, 0)).contains("m_Version 0"));
}
fn camera(stack: i32, version: i32) -> Vec<u8> {
let mut b = Vec::new();
int(&mut b, 1); int(&mut b, 2);
int(&mut b, 2);
int(&mut b, 0); int(&mut b, stack);
for i in 0..stack {
reference(&mut b, i64::from(i) + 100);
}
int(&mut b, -1); b.extend(0x8000_0000u32.to_le_bytes());
reference(&mut b, 0);
int(&mut b, 2);
int(&mut b, 1); int(&mut b, 3); int(&mut b, 2);
for v in [0, 1, 1, 1, 1, 0] {
int(&mut b, v);
}
for v in [0f32, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0] {
b.extend(v.to_le_bytes());
}
int(&mut b, 0);
int(&mut b, 0);
int(&mut b, 3); for v in [0.1f32, 1.0, 0.0, 0.9, 0.0] {
b.extend(v.to_le_bytes());
}
int(&mut b, version);
b
}
#[test]
fn a_urp_camera_is_read_by_the_verified_layout() {
assert_eq!(camera(0, 2).len(), 148);
let c = read_urp_camera(&camera(2, 2)).unwrap();
let stack: Vec<i64> = c.stack.iter().map(|p| p.path_id).collect();
assert_eq!(stack, vec![100, 101]);
assert_eq!(
(
c.renderer_index,
c.depth_texture_option,
c.antialiasing,
c.taa.quality
),
(-1, 2, 3, 3)
);
assert!(c.render_shadows && c.render_post_processing && c.clear_depth && !c.stop_nan && c.dithering);
assert_eq!(
(c.volume_layer_mask, c.screen_coord_scale_bias),
(0x8000_0000, [1.0, 1.0, 0.0, 0.0])
);
assert_eq!(c.taa.variance_clamp_scale, 0.9);
assert!(
read_urp_camera(&camera(0, 1))
.unwrap_err()
.to_string()
.contains("m_Version 1")
);
let mut longer = camera(0, 2);
longer.extend([0; 4]);
assert!(read_urp_camera(&longer).is_err());
}
}