Parse CTLD initial effect group selector
Docs Deploy / Build and Deploy MkDocs (push) Successful in 37s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 37s
This commit is contained in:
3 files changed
+85
-2
No files matched your search
@@ -6363,6 +6363,7 @@ mod tests {
|
|||||||
}],
|
}],
|
||||||
node_data: None,
|
node_data: None,
|
||||||
effect_section: fparkan_assets::PreparedControlEffectSection::Absent,
|
effect_section: fparkan_assets::PreparedControlEffectSection::Absent,
|
||||||
|
initial_effect_group: None,
|
||||||
point_list: None,
|
point_list: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -949,6 +949,9 @@ pub struct PreparedComponentControl {
|
|||||||
pub node_data: Option<PreparedControlNodeDataTable>,
|
pub node_data: Option<PreparedControlNodeDataTable>,
|
||||||
/// Parsed native effect rows in this component's control resource.
|
/// Parsed native effect rows in this component's control resource.
|
||||||
pub effect_section: PreparedControlEffectSection,
|
pub effect_section: PreparedControlEffectSection,
|
||||||
|
/// Validated group selected by native Control's first effect-group
|
||||||
|
/// header word; `None` is the native no-group sentinel or an invalid index.
|
||||||
|
pub initial_effect_group: Option<usize>,
|
||||||
/// Component-local points selected for parsed effect rows, when present.
|
/// Component-local points selected for parsed effect rows, when present.
|
||||||
pub point_list: Option<PreparedControlPointList>,
|
pub point_list: Option<PreparedControlPointList>,
|
||||||
}
|
}
|
||||||
@@ -3647,6 +3650,7 @@ const CONTROL_EFFECT_ROW_BYTES: usize = 100;
|
|||||||
|
|
||||||
struct DecodedComponentEffectSection {
|
struct DecodedComponentEffectSection {
|
||||||
section: PreparedControlEffectSection,
|
section: PreparedControlEffectSection,
|
||||||
|
initial_effect_group: Option<usize>,
|
||||||
typed_records: Vec<PreparedControlTypedRecord>,
|
typed_records: Vec<PreparedControlTypedRecord>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3670,6 +3674,7 @@ where
|
|||||||
typed_record_tag: None,
|
typed_record_tag: None,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
initial_effect_group: None,
|
||||||
typed_records: Vec::new(),
|
typed_records: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -3690,6 +3695,7 @@ where
|
|||||||
typed_record_index: Some(record_index),
|
typed_record_index: Some(record_index),
|
||||||
typed_record_tag: Some(tag),
|
typed_record_tag: Some(tag),
|
||||||
},
|
},
|
||||||
|
initial_effect_group: None,
|
||||||
typed_records,
|
typed_records,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -3788,16 +3794,24 @@ where
|
|||||||
typed_record_tag: None,
|
typed_record_tag: None,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
initial_effect_group: None,
|
||||||
typed_records,
|
typed_records,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor = cursor
|
let group_header_end = cursor
|
||||||
.checked_add(CONTROL_EFFECT_GROUP_HEADER_BYTES)
|
.checked_add(CONTROL_EFFECT_GROUP_HEADER_BYTES)
|
||||||
.ok_or_else(|| invalid("effect group header end overflows"))?;
|
.ok_or_else(|| invalid("effect group header end overflows"))?;
|
||||||
if cursor > payload.len() {
|
if group_header_end > payload.len() {
|
||||||
return Err(invalid("effect group header extends past the payload"));
|
return Err(invalid("effect group header extends past the payload"));
|
||||||
}
|
}
|
||||||
|
let initial_effect_group = usize::try_from(
|
||||||
|
control_u32_at(payload, cursor)
|
||||||
|
.expect("validated effect group header contains its initial selector"),
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
.filter(|group| *group < effect_group_count);
|
||||||
|
cursor = group_header_end;
|
||||||
|
|
||||||
let mut effect_rows = Vec::new();
|
let mut effect_rows = Vec::new();
|
||||||
for table_index in 0..effect_group_count {
|
for table_index in 0..effect_group_count {
|
||||||
@@ -3863,11 +3877,13 @@ where
|
|||||||
typed_record_index: None,
|
typed_record_index: None,
|
||||||
typed_record_tag: None,
|
typed_record_tag: None,
|
||||||
},
|
},
|
||||||
|
initial_effect_group: None,
|
||||||
typed_records,
|
typed_records,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(DecodedComponentEffectSection {
|
Ok(DecodedComponentEffectSection {
|
||||||
section: PreparedControlEffectSection::Parsed(effect_rows),
|
section: PreparedControlEffectSection::Parsed(effect_rows),
|
||||||
|
initial_effect_group,
|
||||||
typed_records,
|
typed_records,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -4016,6 +4032,7 @@ fn decode_component_control_payload(
|
|||||||
typed_records: decoded_effect.typed_records,
|
typed_records: decoded_effect.typed_records,
|
||||||
node_data: None,
|
node_data: None,
|
||||||
effect_section: decoded_effect.section,
|
effect_section: decoded_effect.section,
|
||||||
|
initial_effect_group: decoded_effect.initial_effect_group,
|
||||||
point_list: None,
|
point_list: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -4507,6 +4524,58 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn component_control_uses_validated_initial_effect_group_header_word() {
|
||||||
|
let source = ResourceKey {
|
||||||
|
archive: parse_path("controls.rlb").expect("archive"),
|
||||||
|
name: resource_name(b"parts.ctl"),
|
||||||
|
type_id: None,
|
||||||
|
};
|
||||||
|
let mut payload = component_control_payload(&[(1, 0.0, 1.0, 0.25, 0)]);
|
||||||
|
payload[12..16].copy_from_slice(&1_u32.to_le_bytes());
|
||||||
|
payload[16..20].copy_from_slice(&9_u32.to_le_bytes());
|
||||||
|
append_typed_record(&mut payload, 21, 0);
|
||||||
|
let group_header = payload.len();
|
||||||
|
payload.resize(group_header + CONTROL_EFFECT_GROUP_HEADER_BYTES, 0);
|
||||||
|
payload[group_header..group_header + 4].copy_from_slice(&8_u32.to_le_bytes());
|
||||||
|
for group in 0..9 {
|
||||||
|
if group == 8 {
|
||||||
|
append_effect_table(
|
||||||
|
&mut payload,
|
||||||
|
&[effect_row(
|
||||||
|
4,
|
||||||
|
[0; 3],
|
||||||
|
[0; 3],
|
||||||
|
17,
|
||||||
|
"effects.rlb",
|
||||||
|
"f_smalllight_y",
|
||||||
|
)],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
append_effect_table(&mut payload, &[]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let control = decode_component_control_payload(&payload, source.clone(), 2, 3)
|
||||||
|
.expect("nine declared effect groups parse");
|
||||||
|
assert_eq!(control.initial_effect_group, Some(8));
|
||||||
|
let PreparedControlEffectSection::Parsed(rows) = control.effect_section else {
|
||||||
|
panic!("effect groups should parse");
|
||||||
|
};
|
||||||
|
assert_eq!(rows.len(), 1);
|
||||||
|
assert_eq!(rows[0].table_index, 8);
|
||||||
|
|
||||||
|
payload[group_header..group_header + 4].copy_from_slice(&u32::MAX.to_le_bytes());
|
||||||
|
let no_default = decode_component_control_payload(&payload, source.clone(), 2, 3)
|
||||||
|
.expect("native no-group sentinel is accepted");
|
||||||
|
assert_eq!(no_default.initial_effect_group, None);
|
||||||
|
|
||||||
|
payload[group_header..group_header + 4].copy_from_slice(&9_u32.to_le_bytes());
|
||||||
|
let out_of_range = decode_component_control_payload(&payload, source, 2, 3)
|
||||||
|
.expect("out-of-range selector does not invalidate tables");
|
||||||
|
assert_eq!(out_of_range.initial_effect_group, None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tag21_gate_provider_comes_from_validated_explicit_ndp() {
|
fn tag21_gate_provider_comes_from_validated_explicit_ndp() {
|
||||||
let source = ResourceKey {
|
let source = ResourceKey {
|
||||||
|
|||||||
@@ -454,6 +454,19 @@ C0 равен 0, поэтому найденные offsets 104–107, 119–121
|
|||||||
условий, загрузка FX materials, activation, выполнение FX opcodes и видимый
|
условий, загрузка FX materials, activation, выполнение FX opcodes и видимый
|
||||||
рендер эффектов пока не реализованы.
|
рендер эффектов пока не реализованы.
|
||||||
|
|
||||||
|
После типизированных записей CTLD хранит 21 ссылку на группу эффектов. В
|
||||||
|
`fr_l_plant.ctl` первая ссылка равна `8`, остальные 20 содержат `u32::MAX`; за
|
||||||
|
ними расположены 13 таблиц групп. При разборе по адресу `+0x8B10` к каждой
|
||||||
|
ссылке, кроме `u32::MAX`, прибавляется число групп, зарегистрированных до
|
||||||
|
таблиц этого CTL; получившиеся индексы сохраняются в control mapping. При
|
||||||
|
событии `0x80000020` код по адресу `+0x7830` сразу выходит, если поле дескриптора
|
||||||
|
`+0xC0` равно нулю. Разбор по адресу `+0x8B10` вызывает диспетчер групп `+0x2800`
|
||||||
|
с индексом группы по умолчанию только при ненулевом `+0xC0`. Назначение поля
|
||||||
|
дескриптора и условия его заполнения на стороне producer пока неизвестны.
|
||||||
|
`initial_effect_group` хранит проверенную ссылку на группу по умолчанию. Это
|
||||||
|
поле описывает данные CTL и не выполняет activation, загрузку FX materials,
|
||||||
|
FX opcodes или рендер эффектов.
|
||||||
|
|
||||||
CPU-only обход `MISSIONS/Autodemo.00/data.tma` подготовил четыре CTL с 80
|
CPU-only обход `MISSIONS/Autodemo.00/data.tma` подготовил четыре CTL с 80
|
||||||
строками type 4 и четыре явных CPT-списка (всего 322 записи). Среди пар были
|
строками type 4 и четыре явных CPT-списка (всего 322 записи). Среди пар были
|
||||||
`fortif.rlb/fr_l_plant.ctl` и `fortif.rlb/fr_l_plant.cpt` (155 точек), а также
|
`fortif.rlb/fr_l_plant.ctl` и `fortif.rlb/fr_l_plant.cpt` (155 точек), а также
|
||||||
|
|||||||
Reference in new issue
Block a user