1use std::borrow::Cow;
33use std::collections::HashMap;
34
35use rakata_core::{ResRef, ResRefError};
36use rakata_core::{ResourceId, ResourceType, ResourceTypeCode};
37use rakata_formats::{
38 read_dds_from_bytes, read_mdl_from_bytes, read_tga_from_bytes, read_tpc_from_bytes, Bif, Key,
39 Mdl, MdlError, Tpc, TpcBinaryError,
40};
41use thiserror::Error;
42
43use crate::composite_module::{CompositeModule, CompositeModuleSource};
44use crate::keyfile::KeyFile;
45
46use crate::util::{cmp_ascii_case_insensitive, trace_debug};
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50pub enum ResolverSourceFamily {
51 Override,
53 CompositeModule,
55 KeyBif,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
61pub enum ResolutionProvenance {
62 Override,
64 Composite(CompositeModuleSource),
66 KeyBif {
68 bif_index: u32,
70 resource_id: ResourceId,
72 },
73}
74
75impl ResolutionProvenance {
76 pub fn family(self) -> ResolverSourceFamily {
78 match self {
79 Self::Override => ResolverSourceFamily::Override,
80 Self::Composite(_) => ResolverSourceFamily::CompositeModule,
81 Self::KeyBif { .. } => ResolverSourceFamily::KeyBif,
82 }
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct ResolverResult<'a> {
89 pub provenance: ResolutionProvenance,
91 pub resref: ResRef,
93 pub resource_type: ResourceTypeCode,
95 pub data: Cow<'a, [u8]>,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct TextureWithTxiResult<'a> {
102 pub texture: ResolverResult<'a>,
104 pub txi: Option<ResolverResult<'a>>,
107}
108
109#[derive(Debug, Clone, PartialEq)]
112pub struct TpcWithTxiHandoffResult<'a> {
113 pub resolved: TextureWithTxiResult<'a>,
115 pub tpc: Tpc,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
121pub enum TpcEmbeddedPayloadHint {
122 DdsByMagic,
124 LikelyTga,
126 Unknown,
128}
129
130impl<'a> TpcWithTxiHandoffResult<'a> {
131 pub fn embedded_payload_hint(&self) -> TpcEmbeddedPayloadHint {
133 classify_tpc_embedded_payload(&self.tpc.payload)
134 }
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct MdlWithMdxResult<'a> {
140 pub mdl: ResolverResult<'a>,
142 pub mdx: Option<ResolverResult<'a>>,
145}
146
147#[derive(Debug, Clone, PartialEq)]
149pub struct MdlWithMdxHandoffResult<'a> {
150 pub resolved: MdlWithMdxResult<'a>,
152 pub model: Mdl,
154}
155
156#[derive(Debug, Clone, PartialEq, Eq)]
158pub struct OverrideEntry {
159 pub resref: ResRef,
161 pub resource_type: ResourceTypeCode,
163 pub data: Vec<u8>,
165 pub source_label: String,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct OverrideInput {
172 pub resref: String,
174 pub resource_type: ResourceTypeCode,
176 pub data: Vec<u8>,
178 pub source_label: String,
180}
181
182#[derive(Debug, Clone, PartialEq, Eq)]
184pub struct OverrideCollision {
185 pub resref: ResRef,
187 pub resource_type: ResourceTypeCode,
189 pub kept_source_label: String,
191 pub ignored_source_label: String,
193}
194
195#[derive(Debug, Clone, Default, PartialEq, Eq)]
197pub struct OverrideSource {
198 entries: Vec<OverrideEntry>,
199 key_index: HashMap<(ResRef, ResourceTypeCode), usize>,
200 collisions: Vec<OverrideCollision>,
201}
202
203impl OverrideSource {
204 pub fn new() -> Self {
206 Self::default()
207 }
208
209 #[cfg_attr(
214 feature = "tracing",
215 tracing::instrument(level = "debug", skip(inputs))
216 )]
217 pub fn from_inputs(mut inputs: Vec<OverrideInput>) -> Result<Self, ResolverError> {
218 trace_debug!(input_count = inputs.len(), "building override source");
219 inputs.sort_by(|a, b| {
220 cmp_ascii_case_insensitive(&a.source_label, &b.source_label)
221 .then(a.source_label.cmp(&b.source_label))
222 });
223
224 let mut source = Self::new();
225 for input in inputs {
226 source.add_entry(
227 &input.resref,
228 input.resource_type,
229 input.data,
230 input.source_label,
231 )?;
232 }
233 trace_debug!(
234 entry_count = source.entries.len(),
235 collision_count = source.collisions.len(),
236 "built override source"
237 );
238 Ok(source)
239 }
240
241 pub fn add_entry(
246 &mut self,
247 resref: &str,
248 resource_type: ResourceTypeCode,
249 data: Vec<u8>,
250 source_label: impl Into<String>,
251 ) -> Result<(), ResolverError> {
252 #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(this, data, source_label), fields(resref = resref, resource_type = resource_type.raw_id())))]
253 fn add_entry_inner(
254 this: &mut OverrideSource,
255 resref: &str,
256 resource_type: ResourceTypeCode,
257 data: Vec<u8>,
258 source_label: impl Into<String>,
259 ) -> Result<(), ResolverError> {
260 let source_label = source_label.into();
261 let canonical = ResRef::new(resref).map_err(|source| ResolverError::InvalidResRef {
262 value: resref.to_string(),
263 source,
264 })?;
265 let key = (canonical, resource_type);
266
267 if let Some(kept_index) = this.key_index.get(&key).copied() {
268 trace_debug!(
269 kept_source = this.entries[kept_index].source_label.as_str(),
270 ignored_source = source_label.as_str(),
271 "override collision: deterministic keep-first rule applied"
272 );
273 this.collisions.push(OverrideCollision {
274 resref: canonical,
275 resource_type,
276 kept_source_label: this.entries[kept_index].source_label.clone(),
277 ignored_source_label: source_label,
278 });
279 return Ok(());
280 }
281
282 let index = this.entries.len();
283 this.entries.push(OverrideEntry {
284 resref: canonical,
285 resource_type,
286 data,
287 source_label,
288 });
289 this.key_index.insert(key, index);
290 Ok(())
291 }
292 add_entry_inner(self, resref, resource_type, data, source_label)
293 }
294
295 pub fn collisions(&self) -> &[OverrideCollision] {
297 &self.collisions
298 }
299
300 #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(self), fields(resref = %resref, resource_type = resource_type.raw_id())))]
302 pub fn resolve(&self, resref: &ResRef, resource_type: ResourceTypeCode) -> Option<&[u8]> {
303 let resolved = self
304 .key_index
305 .get(&(*resref, resource_type))
306 .and_then(|index| self.entries.get(*index))
307 .map(|entry| entry.data.as_slice());
308 trace_debug!(resolved = resolved.is_some(), "override lookup complete");
309 resolved
310 }
311}
312
313#[derive(Debug, Clone, Copy)]
315pub struct KeyBifBinding<'a> {
316 pub bif_index: u32,
318 pub bif: &'a Bif,
320}
321
322#[derive(Debug, Clone)]
329pub struct KeyBifSource<'a> {
330 key: &'a Key,
331 bifs: Vec<KeyBifBinding<'a>>,
332 key_resource_index: HashMap<(ResRef, ResourceTypeCode), usize>,
334 bif_binding_index: HashMap<u32, usize>,
336}
337
338impl<'a> KeyBifSource<'a> {
339 pub fn new(key: &'a Key) -> Self {
341 let key_resource_index = build_key_resource_index_from_key(key);
342 Self {
343 key,
344 bifs: Vec::new(),
345 key_resource_index,
346 bif_binding_index: HashMap::new(),
347 }
348 }
349
350 pub fn key(&self) -> &'a Key {
352 self.key
353 }
354
355 pub fn bifs(&self) -> &[KeyBifBinding<'a>] {
357 &self.bifs
358 }
359
360 pub fn push_bif(&mut self, bif_index: u32, bif: &'a Bif) {
362 let vec_index = self.bifs.len();
363 self.bifs.push(KeyBifBinding { bif_index, bif });
364 self.bif_binding_index.entry(bif_index).or_insert(vec_index);
365 }
366
367 pub fn with_bif(mut self, bif_index: u32, bif: &'a Bif) -> Self {
369 self.push_bif(bif_index, bif);
370 self
371 }
372
373 fn bif_for_index(&self, bif_index: u32) -> Option<&'a Bif> {
374 let &vec_index = self.bif_binding_index.get(&bif_index)?;
375 self.bifs.get(vec_index).map(|binding| binding.bif)
376 }
377
378 #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(self), fields(resref = %resref, resource_type = resource_type.raw_id())))]
380 pub fn resolve(
381 &self,
382 resref: &ResRef,
383 resource_type: ResourceTypeCode,
384 ) -> Option<(u32, ResourceId, &'a [u8])> {
385 let &key_index = self.key_resource_index.get(&(*resref, resource_type))?;
386 let key_entry = self.key.resources.get(key_index)?;
387 let bif_index = key_entry.bif_index();
388 let bif = self.bif_for_index(bif_index)?;
389 let data = bif.resource_by_id(key_entry.resource_id)?;
390 trace_debug!(
391 bif_index,
392 resource_id = key_entry.resource_id.raw(),
393 data_len = data.len(),
394 "key/bif lookup resolved"
395 );
396 Some((bif_index, key_entry.resource_id, data))
397 }
398}
399
400#[derive(Debug, Clone, Copy)]
402pub enum ResolverSourceRef<'a> {
403 Override(&'a OverrideSource),
405 CompositeModule(&'a CompositeModule),
407 KeyBif(&'a KeyBifSource<'a>),
409 KeyFile(&'a KeyFile),
412}
413
414#[derive(Debug, Clone, Default)]
416pub struct Resolver<'a> {
417 sources: Vec<ResolverSourceRef<'a>>,
418}
419
420impl<'a> Resolver<'a> {
421 pub fn new() -> Self {
423 Self {
424 sources: Vec::new(),
425 }
426 }
427
428 pub fn sources(&self) -> &[ResolverSourceRef<'a>] {
430 &self.sources
431 }
432
433 pub fn push_source(&mut self, source: ResolverSourceRef<'a>) {
435 self.sources.push(source);
436 }
437
438 pub fn with_source(mut self, source: ResolverSourceRef<'a>) -> Self {
440 self.push_source(source);
441 self
442 }
443
444 fn resolve_from_source(
445 source: ResolverSourceRef<'a>,
446 resref: &ResRef,
447 resource_type: ResourceTypeCode,
448 ) -> Option<ResolverResult<'a>> {
449 match source {
450 ResolverSourceRef::Override(override_source) => {
451 let data = override_source.resolve(resref, resource_type)?;
452 trace_debug!(source = "override", data_len = data.len(), "resolver hit");
453 Some(ResolverResult {
454 provenance: ResolutionProvenance::Override,
455 resref: *resref,
456 resource_type,
457 data: Cow::Borrowed(data),
458 })
459 }
460 ResolverSourceRef::CompositeModule(composite) => {
461 let found = composite.resolve(resref, resource_type)?;
462 trace_debug!(
463 source = ?found.source,
464 data_len = found.data.len(),
465 "resolver hit"
466 );
467 Some(ResolverResult {
468 provenance: ResolutionProvenance::Composite(found.source),
469 resref: found.resref,
470 resource_type: found.resource_type,
471 data: Cow::Borrowed(found.data),
472 })
473 }
474 ResolverSourceRef::KeyBif(key_bif) => {
475 let (bif_index, resource_id, data) = key_bif.resolve(resref, resource_type)?;
476 trace_debug!(
477 source = "key_bif",
478 bif_index,
479 resource_id = resource_id.raw(),
480 data_len = data.len(),
481 "resolver hit"
482 );
483 Some(ResolverResult {
484 provenance: ResolutionProvenance::KeyBif {
485 bif_index,
486 resource_id,
487 },
488 resref: *resref,
489 resource_type,
490 data: Cow::Borrowed(data),
491 })
492 }
493 ResolverSourceRef::KeyFile(key_file) => {
494 let entry = key_file.resource_entry(resref, resource_type)?;
495 let bif_index = entry.resource_id.bif_index();
496 let resource_id = entry.resource_id;
497 match key_file.read_resource_by_seek(bif_index, resource_id) {
498 Ok(data) => {
499 trace_debug!(
500 source = "key_file",
501 bif_index,
502 resource_id = resource_id.raw(),
503 data_len = data.len(),
504 "resolver hit"
505 );
506 Some(ResolverResult {
507 provenance: ResolutionProvenance::KeyBif {
508 bif_index,
509 resource_id,
510 },
511 resref: *resref,
512 resource_type,
513 data: Cow::Owned(data),
514 })
515 }
516 Err(err) => {
517 #[cfg(feature = "tracing")]
518 tracing::debug!(
519 error = %err,
520 "key_file BIF seek read failed, treating as miss"
521 );
522 let _ = err;
523 None
524 }
525 }
526 }
527 }
528 }
529
530 fn resolve_with_source_index(
531 &self,
532 resref: &ResRef,
533 resource_type: ResourceTypeCode,
534 ) -> Option<(usize, ResolverResult<'a>)> {
535 for (index, source) in self.sources.iter().copied().enumerate() {
536 if let Some(result) = Self::resolve_from_source(source, resref, resource_type) {
537 return Some((index, result));
538 }
539 }
540 None
541 }
542
543 #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(self), fields(resref = %resref, resource_type = resource_type.raw_id())))]
545 pub fn resolve(
546 &self,
547 resref: &ResRef,
548 resource_type: ResourceTypeCode,
549 ) -> Option<ResolverResult<'_>> {
550 let resolved = self.resolve_with_source_index(resref, resource_type);
551 if resolved.is_none() {
552 trace_debug!("resolver miss");
553 }
554 resolved.map(|(_, result)| result)
555 }
556
557 #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(self), fields(resref = %resref, texture_type = texture_type.raw_id())))]
566 pub fn resolve_texture_with_txi(
567 &self,
568 resref: &ResRef,
569 texture_type: ResourceTypeCode,
570 ) -> Option<TextureWithTxiResult<'_>> {
571 let texture = self.resolve(resref, texture_type)?;
572 let txi_type = ResourceTypeCode::from(ResourceType::Txi);
573 let txi = self.resolve(resref, txi_type);
574 trace_debug!(
575 texture_source = ?texture.provenance.family(),
576 txi_resolved = txi.is_some(),
577 "texture+txi resolution complete"
578 );
579 Some(TextureWithTxiResult { texture, txi })
580 }
581
582 #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(self), fields(resref = resref, resource_type = resource_type_id)))]
584 #[doc(hidden)]
585 pub fn resolve_raw(
586 &self,
587 resref: &str,
588 resource_type_id: u16,
589 ) -> Result<Option<ResolverResult<'_>>, ResolverError> {
590 let resref = ResRef::new(resref).map_err(|source| ResolverError::InvalidResRef {
591 value: resref.to_string(),
592 source,
593 })?;
594 Ok(self.resolve(&resref, ResourceTypeCode::from_raw_id(resource_type_id)))
595 }
596
597 #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(self), fields(resref = resref, texture_type = texture_type_id)))]
599 pub fn resolve_texture_with_txi_raw(
600 &self,
601 resref: &str,
602 texture_type_id: u16,
603 ) -> Result<Option<TextureWithTxiResult<'_>>, ResolverError> {
604 let resref = ResRef::new(resref).map_err(|source| ResolverError::InvalidResRef {
605 value: resref.to_string(),
606 source,
607 })?;
608 Ok(self.resolve_texture_with_txi(&resref, ResourceTypeCode::from_raw_id(texture_type_id)))
609 }
610
611 #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(self), fields(resref = %resref)))]
614 pub fn resolve_tpc_with_txi_handoff(
615 &self,
616 resref: &ResRef,
617 ) -> Result<Option<TpcWithTxiHandoffResult<'_>>, TpcHandoffError> {
618 let resolved = match self
619 .resolve_texture_with_txi(resref, ResourceTypeCode::from(ResourceType::Tpc))
620 {
621 Some(resolved) => resolved,
622 None => {
623 trace_debug!("tpc handoff miss");
624 return Ok(None);
625 }
626 };
627 let tpc = read_tpc_from_bytes(&resolved.texture.data)
628 .map_err(|source| TpcHandoffError::ParseTpc { source })?;
629 trace_debug!(
630 payload_len = tpc.payload.len(),
631 txi_footer_len = tpc.txi_footer.len(),
632 "tpc handoff parse complete"
633 );
634 Ok(Some(TpcWithTxiHandoffResult { resolved, tpc }))
635 }
636
637 #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(self), fields(resref = resref)))]
640 pub fn resolve_tpc_with_txi_handoff_raw(
641 &self,
642 resref: &str,
643 ) -> Result<Option<TpcWithTxiHandoffResult<'_>>, TpcHandoffError> {
644 let resref = ResRef::new(resref).map_err(|source| TpcHandoffError::InvalidResRef {
645 value: resref.to_string(),
646 source,
647 })?;
648 self.resolve_tpc_with_txi_handoff(&resref)
649 }
650
651 #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(self), fields(resref = %resref)))]
660 pub fn resolve_mdl_with_mdx(&self, resref: &ResRef) -> Option<MdlWithMdxResult<'_>> {
661 let mdl = self.resolve(resref, ResourceTypeCode::from(ResourceType::Mdl))?;
662 let mdx_type = ResourceTypeCode::from(ResourceType::Mdx);
663 let mdx = self.resolve(resref, mdx_type);
664 trace_debug!(
665 mdl_source = ?mdl.provenance.family(),
666 mdx_resolved = mdx.is_some(),
667 "mdl+mdx resolution complete"
668 );
669 Some(MdlWithMdxResult { mdl, mdx })
670 }
671
672 #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(self), fields(resref = resref)))]
674 pub fn resolve_mdl_with_mdx_raw(
675 &self,
676 resref: &str,
677 ) -> Result<Option<MdlWithMdxResult<'_>>, ResolverError> {
678 let resref = ResRef::new(resref).map_err(|source| ResolverError::InvalidResRef {
679 value: resref.to_string(),
680 source,
681 })?;
682 Ok(self.resolve_mdl_with_mdx(&resref))
683 }
684
685 #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(self), fields(resref = %resref)))]
688 pub fn resolve_mdl_with_mdx_handoff(
689 &self,
690 resref: &ResRef,
691 ) -> Result<Option<MdlWithMdxHandoffResult<'_>>, MdlHandoffError> {
692 let resolved = match self.resolve_mdl_with_mdx(resref) {
693 Some(resolved) => resolved,
694 None => {
695 trace_debug!("mdl handoff miss");
696 return Ok(None);
697 }
698 };
699 let mdx_data = resolved.mdx.as_ref().map(|r| r.data.as_ref());
700 let model = read_mdl_from_bytes(&resolved.mdl.data, mdx_data)
701 .map_err(|source| MdlHandoffError::ParseMdl { source })?;
702 trace_debug!(
703 node_count = model.node_count,
704 root_name = model.root_node.name.as_str(),
705 has_mdx = mdx_data.is_some(),
706 "mdl handoff parse complete"
707 );
708 Ok(Some(MdlWithMdxHandoffResult { resolved, model }))
709 }
710
711 #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(self), fields(resref = resref)))]
714 pub fn resolve_mdl_with_mdx_handoff_raw(
715 &self,
716 resref: &str,
717 ) -> Result<Option<MdlWithMdxHandoffResult<'_>>, MdlHandoffError> {
718 let resref = ResRef::new(resref).map_err(|source| MdlHandoffError::InvalidResRef {
719 value: resref.to_string(),
720 source,
721 })?;
722 self.resolve_mdl_with_mdx_handoff(&resref)
723 }
724}
725
726#[derive(Debug, Error)]
728pub enum ResolverError {
729 #[error("invalid resref `{value}`: {source}")]
731 InvalidResRef {
732 value: String,
734 #[source]
736 source: ResRefError,
737 },
738}
739
740#[derive(Debug, Error)]
742pub enum TpcHandoffError {
743 #[error("invalid resref `{value}`: {source}")]
745 InvalidResRef {
746 value: String,
748 #[source]
750 source: ResRefError,
751 },
752 #[error("failed to parse resolved TPC: {source}")]
754 ParseTpc {
755 #[source]
757 source: TpcBinaryError,
758 },
759}
760
761#[derive(Debug, Error)]
763pub enum MdlHandoffError {
764 #[error("invalid resref `{value}`: {source}")]
766 InvalidResRef {
767 value: String,
769 #[source]
771 source: ResRefError,
772 },
773 #[error("failed to parse resolved MDL: {source}")]
775 ParseMdl {
776 #[source]
778 source: MdlError,
779 },
780}
781
782fn build_key_resource_index_from_key(key: &Key) -> HashMap<(ResRef, ResourceTypeCode), usize> {
786 let mut index = HashMap::with_capacity(key.resources.len());
787 for (i, entry) in key.resources.iter().enumerate() {
788 index
789 .entry((entry.resref, entry.resource_type))
790 .or_insert(i);
791 }
792 index
793}
794
795fn classify_tpc_embedded_payload(payload: &[u8]) -> TpcEmbeddedPayloadHint {
796 if payload.starts_with(b"DDS ") && read_dds_from_bytes(payload).is_ok() {
797 return TpcEmbeddedPayloadHint::DdsByMagic;
798 }
799 if read_tga_from_bytes(payload).is_ok() {
800 return TpcEmbeddedPayloadHint::LikelyTga;
801 }
802 TpcEmbeddedPayloadHint::Unknown
803}
804
805#[cfg(test)]
806mod tests {
807 use rakata_core::ResRef;
808 use rakata_core::{ResourceId, ResourceType, ResourceTypeCode};
809 use rakata_formats::ErfFileType;
810
811 use crate::composite_module::{CompositeModule, CompositeModuleSource};
812
813 use super::{
814 classify_tpc_embedded_payload, KeyBifSource, OverrideInput, OverrideSource,
815 ResolutionProvenance, Resolver, ResolverError, ResolverSourceRef, TpcEmbeddedPayloadHint,
816 TpcHandoffError,
817 };
818
819 fn sample_composite(payload: &[u8]) -> CompositeModule {
820 let mut erf = rakata_formats::Erf::new(ErfFileType::Erf);
821 erf.push_resource(
822 ResRef::new("module").expect("valid resref"),
823 ResourceTypeCode::from(ResourceType::Dlg),
824 payload.to_vec(),
825 );
826 CompositeModule::from_archives("m01aa", None, None, Some(erf)).expect("composite")
827 }
828
829 fn sample_key_bif(payload: &[u8]) -> (rakata_formats::Key, rakata_formats::Bif) {
830 let mut key = rakata_formats::Key::new();
831 key.push_bif_entry("chitin\\test.bif", 0, 0);
832 let resource_id = ResourceId::from_parts(0, 1).expect("resource id");
833 key.push_resource(
834 ResRef::new("MoDuLe").expect("valid resref"),
835 ResourceTypeCode::from(ResourceType::Dlg),
836 resource_id,
837 );
838
839 let mut bif = rakata_formats::Bif::new();
840 bif.push_resource(
841 resource_id,
842 ResourceTypeCode::from(ResourceType::Dlg),
843 payload.to_vec(),
844 );
845
846 (key, bif)
847 }
848
849 fn sample_tpc_bytes(payload: &[u8], txi_footer: &[u8]) -> Vec<u8> {
850 let header = rakata_formats::TpcHeader {
851 data_size: 0,
852 alpha_test: 0.5,
853 width: 2,
854 height: 2,
855 pixel_type: 4,
856 mipmap_count: 1,
857 reserved: [0_u8; 114],
858 };
859 let tpc = rakata_formats::Tpc::new(header, payload.to_vec(), txi_footer.to_vec());
860 rakata_formats::write_tpc_to_vec(&tpc).expect("tpc bytes")
861 }
862
863 fn sample_tpc_bytes_exact_payload(payload: &[u8]) -> Vec<u8> {
864 let width = u16::try_from(payload.len()).expect("payload len fits u16");
865 let header = rakata_formats::TpcHeader {
866 data_size: 0,
867 alpha_test: 0.0,
868 width,
869 height: 1,
870 pixel_type: 1,
871 mipmap_count: 1,
872 reserved: [0_u8; 114],
873 };
874 let tpc = rakata_formats::Tpc::new(header, payload.to_vec(), Vec::new());
875 rakata_formats::write_tpc_to_vec(&tpc).expect("tpc bytes")
876 }
877
878 fn sample_dds_bytes() -> Vec<u8> {
879 let mut dds = rakata_formats::Dds::new_d3d(rakata_formats::DdsNewD3dParams {
880 height: 4,
881 width: 4,
882 depth: None,
883 format: rakata_formats::DdsD3dFormat::DXT1,
884 mipmap_levels: Some(1),
885 caps2: None,
886 })
887 .expect("valid dds");
888 dds.data.fill(0x11);
889 rakata_formats::write_dds_to_vec(&dds).expect("dds bytes")
890 }
891
892 fn sample_tga_bytes() -> Vec<u8> {
893 let tga =
894 rakata_formats::Tga::new_rgba(1, 1, vec![0x11, 0x22, 0x33, 0x44]).expect("valid tga");
895 rakata_formats::write_tga_to_vec(&tga).expect("tga bytes")
896 }
897
898 #[test]
899 fn resolver_prefers_sources_in_configured_order() {
900 let composite = sample_composite(b"composite");
901 let override_source = OverrideSource::from_inputs(vec![OverrideInput {
902 resref: "module".into(),
903 resource_type: ResourceTypeCode::from(ResourceType::Dlg),
904 data: b"override".to_vec(),
905 source_label: "override/Module.dlg".into(),
906 }])
907 .expect("override source");
908
909 let resolver = Resolver::new()
910 .with_source(ResolverSourceRef::Override(&override_source))
911 .with_source(ResolverSourceRef::CompositeModule(&composite));
912
913 let resref = ResRef::new("module").expect("resref");
914 let resolved = resolver
915 .resolve(&resref, ResourceTypeCode::from(ResourceType::Dlg))
916 .expect("must resolve");
917 assert_eq!(resolved.provenance, ResolutionProvenance::Override);
918 assert_eq!(&*resolved.data, b"override");
919 }
920
921 #[test]
922 fn resolver_falls_back_to_key_bif_when_higher_sources_miss() {
923 let composite = sample_composite(b"composite");
924 let override_source = OverrideSource::new();
925 let mut key = rakata_formats::Key::new();
926 key.push_bif_entry("chitin\\test.bif", 0, 0);
927 let resource_id = ResourceId::from_parts(0, 2).expect("resource id");
928 key.push_resource(
929 ResRef::new("keyonly").expect("valid resref"),
930 ResourceTypeCode::from(ResourceType::Dlg),
931 resource_id,
932 );
933 let mut bif = rakata_formats::Bif::new();
934 bif.push_resource(
935 resource_id,
936 ResourceTypeCode::from(ResourceType::Dlg),
937 b"keybif".to_vec(),
938 );
939 let key_bif = KeyBifSource::new(&key).with_bif(0, &bif);
940
941 let resolver = Resolver::new()
942 .with_source(ResolverSourceRef::Override(&override_source))
943 .with_source(ResolverSourceRef::CompositeModule(&composite))
944 .with_source(ResolverSourceRef::KeyBif(&key_bif));
945
946 let resref = ResRef::new("keyonly").expect("resref");
947 let resolved = resolver
948 .resolve(&resref, ResourceTypeCode::from(ResourceType::Dlg))
949 .expect("must resolve");
950 assert_eq!(
951 resolved.provenance,
952 ResolutionProvenance::KeyBif {
953 bif_index: 0,
954 resource_id,
955 }
956 );
957 assert_eq!(&*resolved.data, b"keybif");
958
959 let missing_resref = ResRef::new("only_in_key").expect("resref");
960 assert!(resolver
961 .resolve(&missing_resref, ResourceTypeCode::from(ResourceType::Dlg))
962 .is_none());
963 }
964
965 #[test]
966 fn resolver_falls_back_to_composite_when_override_missing() {
967 let composite = sample_composite(b"composite");
968 let override_source = OverrideSource::new();
969
970 let resolver = Resolver::new()
971 .with_source(ResolverSourceRef::Override(&override_source))
972 .with_source(ResolverSourceRef::CompositeModule(&composite));
973
974 let resref = ResRef::new("module").expect("resref");
975 let resolved = resolver
976 .resolve(&resref, ResourceTypeCode::from(ResourceType::Dlg))
977 .expect("must resolve");
978 assert_eq!(
979 resolved.provenance,
980 ResolutionProvenance::Composite(CompositeModuleSource::DialogErf)
981 );
982 assert_eq!(&*resolved.data, b"composite");
983 }
984
985 #[test]
986 fn key_bif_lookup_is_case_insensitive_for_resref() {
987 let (key, bif) = sample_key_bif(b"keybif");
988 let key_bif = KeyBifSource::new(&key).with_bif(0, &bif);
989 let resolver = Resolver::new().with_source(ResolverSourceRef::KeyBif(&key_bif));
990
991 let resref = ResRef::new("module").expect("resref");
992 let resolved = resolver
993 .resolve(&resref, ResourceTypeCode::from(ResourceType::Dlg))
994 .expect("must resolve");
995 assert_eq!(
996 resolved.provenance,
997 ResolutionProvenance::KeyBif {
998 bif_index: 0,
999 resource_id: ResourceId::from_parts(0, 1).expect("resource id"),
1000 }
1001 );
1002 assert_eq!(&*resolved.data, b"keybif");
1003 }
1004
1005 #[test]
1006 fn key_bif_lookup_returns_none_when_bif_binding_missing() {
1007 let mut key = rakata_formats::Key::new();
1008 key.push_bif_entry("chitin\\missing.bif", 0, 0);
1009 let resource_id = ResourceId::from_parts(1, 0).expect("resource id");
1010 key.push_resource(
1011 ResRef::new("module").expect("valid resref"),
1012 ResourceTypeCode::from(ResourceType::Dlg),
1013 resource_id,
1014 );
1015 let bif = rakata_formats::Bif::new();
1016 let key_bif = KeyBifSource::new(&key).with_bif(0, &bif);
1017 let resolver = Resolver::new().with_source(ResolverSourceRef::KeyBif(&key_bif));
1018
1019 let resref = ResRef::new("module").expect("resref");
1020 assert!(resolver
1021 .resolve(&resref, ResourceTypeCode::from(ResourceType::Dlg))
1022 .is_none());
1023 }
1024
1025 #[test]
1026 fn key_bif_lookup_prefers_first_duplicate_key_entry() {
1027 let mut key = rakata_formats::Key::new();
1028 key.push_bif_entry("chitin\\a.bif", 0, 0);
1029 key.push_bif_entry("chitin\\b.bif", 0, 0);
1030 let first_id = ResourceId::from_parts(0, 1).expect("resource id");
1031 let second_id = ResourceId::from_parts(1, 2).expect("resource id");
1032 key.push_resource(
1033 ResRef::new("module").expect("valid resref"),
1034 ResourceTypeCode::from(ResourceType::Dlg),
1035 first_id,
1036 );
1037 key.push_resource(
1038 ResRef::new("module").expect("valid resref"),
1039 ResourceTypeCode::from(ResourceType::Dlg),
1040 second_id,
1041 );
1042
1043 let mut bif_a = rakata_formats::Bif::new();
1044 bif_a.push_resource(
1045 first_id,
1046 ResourceTypeCode::from(ResourceType::Dlg),
1047 b"first".to_vec(),
1048 );
1049 let mut bif_b = rakata_formats::Bif::new();
1050 bif_b.push_resource(
1051 second_id,
1052 ResourceTypeCode::from(ResourceType::Dlg),
1053 b"second".to_vec(),
1054 );
1055
1056 let key_bif = KeyBifSource::new(&key)
1057 .with_bif(0, &bif_a)
1058 .with_bif(1, &bif_b);
1059 let resolver = Resolver::new().with_source(ResolverSourceRef::KeyBif(&key_bif));
1060 let resref = ResRef::new("module").expect("resref");
1061 let resolved = resolver
1062 .resolve(&resref, ResourceTypeCode::from(ResourceType::Dlg))
1063 .expect("must resolve");
1064
1065 assert_eq!(
1066 resolved.provenance,
1067 ResolutionProvenance::KeyBif {
1068 bif_index: 0,
1069 resource_id: first_id,
1070 }
1071 );
1072 assert_eq!(&*resolved.data, b"first");
1073 }
1074
1075 #[test]
1076 fn resolver_prefers_override_then_composite_then_key_bif() {
1077 let composite = sample_composite(b"composite");
1078 let (key, bif) = sample_key_bif(b"keybif");
1079 let key_bif = KeyBifSource::new(&key).with_bif(0, &bif);
1080
1081 let override_source = OverrideSource::from_inputs(vec![OverrideInput {
1082 resref: "module".into(),
1083 resource_type: ResourceTypeCode::from(ResourceType::Dlg),
1084 data: b"override".to_vec(),
1085 source_label: "override/module.dlg".into(),
1086 }])
1087 .expect("override source");
1088 let resolver = Resolver::new()
1089 .with_source(ResolverSourceRef::Override(&override_source))
1090 .with_source(ResolverSourceRef::CompositeModule(&composite))
1091 .with_source(ResolverSourceRef::KeyBif(&key_bif));
1092 let resref = ResRef::new("module").expect("resref");
1093 let resolved = resolver
1094 .resolve(&resref, ResourceTypeCode::from(ResourceType::Dlg))
1095 .expect("must resolve");
1096 assert_eq!(&*resolved.data, b"override");
1097 assert_eq!(resolved.provenance, ResolutionProvenance::Override);
1098
1099 let resolver_without_override = Resolver::new()
1100 .with_source(ResolverSourceRef::CompositeModule(&composite))
1101 .with_source(ResolverSourceRef::KeyBif(&key_bif));
1102 let resolved = resolver_without_override
1103 .resolve(&resref, ResourceTypeCode::from(ResourceType::Dlg))
1104 .expect("must resolve");
1105 assert_eq!(&*resolved.data, b"composite");
1106 assert_eq!(
1107 resolved.provenance,
1108 ResolutionProvenance::Composite(CompositeModuleSource::DialogErf)
1109 );
1110
1111 let resolver_key_only = Resolver::new().with_source(ResolverSourceRef::KeyBif(&key_bif));
1112 let resolved = resolver_key_only
1113 .resolve(&resref, ResourceTypeCode::from(ResourceType::Dlg))
1114 .expect("must resolve");
1115 assert_eq!(&*resolved.data, b"keybif");
1116 assert_eq!(
1117 resolved.provenance,
1118 ResolutionProvenance::KeyBif {
1119 bif_index: 0,
1120 resource_id: ResourceId::from_parts(0, 1).expect("resource id"),
1121 }
1122 );
1123 }
1124
1125 #[test]
1126 fn resolver_raw_query_validates_resref() {
1127 let resolver = Resolver::new();
1128 let err = resolver
1132 .resolve_raw("名前", ResourceType::Dlg.type_id())
1133 .expect_err("invalid resref should fail");
1134 assert!(matches!(err, ResolverError::InvalidResRef { .. }));
1135 }
1136
1137 #[test]
1138 fn override_lookup_is_case_insensitive() {
1139 let override_source = OverrideSource::from_inputs(vec![OverrideInput {
1140 resref: "MoDuLe".into(),
1141 resource_type: ResourceTypeCode::from(ResourceType::Dlg),
1142 data: b"override".to_vec(),
1143 source_label: "override/Module.dlg".into(),
1144 }])
1145 .expect("override source");
1146 let resolver = Resolver::new().with_source(ResolverSourceRef::Override(&override_source));
1147
1148 let resref = ResRef::new("module").expect("resref");
1149 let resolved = resolver
1150 .resolve(&resref, ResourceTypeCode::from(ResourceType::Dlg))
1151 .expect("must resolve");
1152 assert_eq!(&*resolved.data, b"override");
1153 }
1154
1155 #[test]
1156 fn override_collisions_are_deterministic_from_unsorted_inputs() {
1157 let source = OverrideSource::from_inputs(vec![
1158 OverrideInput {
1159 resref: "module".into(),
1160 resource_type: ResourceTypeCode::from(ResourceType::Dlg),
1161 data: b"late".to_vec(),
1162 source_label: "z_override/module.dlg".into(),
1163 },
1164 OverrideInput {
1165 resref: "MoDuLe".into(),
1166 resource_type: ResourceTypeCode::from(ResourceType::Dlg),
1167 data: b"early".to_vec(),
1168 source_label: "a_override/MODULE.dlg".into(),
1169 },
1170 ])
1171 .expect("override source");
1172
1173 assert_eq!(source.collisions().len(), 1);
1174
1175 let resolver = Resolver::new().with_source(ResolverSourceRef::Override(&source));
1176 let resref = ResRef::new("module").expect("resref");
1177 let resolved = resolver
1178 .resolve(&resref, ResourceTypeCode::from(ResourceType::Dlg))
1179 .expect("must resolve");
1180 assert_eq!(&*resolved.data, b"early");
1181 }
1182
1183 #[test]
1184 fn texture_with_txi_returns_none_when_texture_is_missing() {
1185 let override_source = OverrideSource::from_inputs(vec![OverrideInput {
1186 resref: "module".into(),
1187 resource_type: ResourceTypeCode::from(ResourceType::Txi),
1188 data: b"mipmap 0".to_vec(),
1189 source_label: "override/module.txi".into(),
1190 }])
1191 .expect("override source");
1192 let resolver = Resolver::new().with_source(ResolverSourceRef::Override(&override_source));
1193
1194 let resref = ResRef::new("module").expect("resref");
1195 assert!(resolver
1196 .resolve_texture_with_txi(&resref, ResourceTypeCode::from(ResourceType::Tpc))
1197 .is_none());
1198 }
1199
1200 #[test]
1201 fn texture_with_txi_is_case_insensitive_for_pairing() {
1202 let override_source = OverrideSource::from_inputs(vec![
1203 OverrideInput {
1204 resref: "MoDuLe".into(),
1205 resource_type: ResourceTypeCode::from(ResourceType::Tpc),
1206 data: b"texture".to_vec(),
1207 source_label: "override/module.tpc".into(),
1208 },
1209 OverrideInput {
1210 resref: "module".into(),
1211 resource_type: ResourceTypeCode::from(ResourceType::Txi),
1212 data: b"mipmap 0".to_vec(),
1213 source_label: "override/module.txi".into(),
1214 },
1215 ])
1216 .expect("override source");
1217 let resolver = Resolver::new().with_source(ResolverSourceRef::Override(&override_source));
1218
1219 let resref = ResRef::new("MODULE").expect("resref");
1220 let resolved = resolver
1221 .resolve_texture_with_txi(&resref, ResourceTypeCode::from(ResourceType::Tpc))
1222 .expect("must resolve");
1223 assert_eq!(&*resolved.texture.data, b"texture");
1224 assert_eq!(&*resolved.txi.expect("txi").data, b"mipmap 0");
1225 }
1226
1227 #[test]
1228 fn texture_with_txi_uses_independent_global_source_order() {
1229 let mut dialog_erf = rakata_formats::Erf::new(ErfFileType::Erf);
1230 dialog_erf.push_resource(
1231 ResRef::new("module").expect("valid resref"),
1232 ResourceTypeCode::from(ResourceType::Tpc),
1233 b"composite-texture".to_vec(),
1234 );
1235 dialog_erf.push_resource(
1236 ResRef::new("module").expect("valid resref"),
1237 ResourceTypeCode::from(ResourceType::Txi),
1238 b"composite-txi".to_vec(),
1239 );
1240 let composite =
1241 CompositeModule::from_archives("m01aa", None, None, Some(dialog_erf)).expect("module");
1242 let override_source = OverrideSource::from_inputs(vec![OverrideInput {
1243 resref: "module".into(),
1244 resource_type: ResourceTypeCode::from(ResourceType::Tpc),
1245 data: b"override-texture".to_vec(),
1246 source_label: "override/module.tpc".into(),
1247 }])
1248 .expect("override source");
1249
1250 let resolver = Resolver::new()
1251 .with_source(ResolverSourceRef::Override(&override_source))
1252 .with_source(ResolverSourceRef::CompositeModule(&composite));
1253 let resref = ResRef::new("module").expect("resref");
1254 let resolved = resolver
1255 .resolve_texture_with_txi(&resref, ResourceTypeCode::from(ResourceType::Tpc))
1256 .expect("must resolve");
1257 assert_eq!(resolved.texture.provenance, ResolutionProvenance::Override);
1258 assert_eq!(&*resolved.texture.data, b"override-texture");
1259 let txi = resolved.txi.expect("txi");
1260 assert_eq!(
1261 txi.provenance,
1262 ResolutionProvenance::Composite(CompositeModuleSource::DialogErf)
1263 );
1264 assert_eq!(&*txi.data, b"composite-txi");
1265 }
1266
1267 #[test]
1268 fn texture_with_txi_supports_tpc_tga_and_dds_queries() {
1269 let mut inputs = Vec::new();
1270 for (label, ty) in [
1271 ("tpc", ResourceType::Tpc),
1272 ("tga", ResourceType::Tga),
1273 ("dds", ResourceType::Dds),
1274 ] {
1275 inputs.push(OverrideInput {
1276 resref: "module".into(),
1277 resource_type: ResourceTypeCode::from(ty),
1278 data: label.as_bytes().to_vec(),
1279 source_label: format!("override/module.{label}"),
1280 });
1281 }
1282 inputs.push(OverrideInput {
1283 resref: "module".into(),
1284 resource_type: ResourceTypeCode::from(ResourceType::Txi),
1285 data: b"mipmap 0".to_vec(),
1286 source_label: "override/module.txi".into(),
1287 });
1288
1289 let override_source = OverrideSource::from_inputs(inputs).expect("override");
1290 let resolver = Resolver::new().with_source(ResolverSourceRef::Override(&override_source));
1291 let resref = ResRef::new("module").expect("resref");
1292
1293 let resolved_tpc = resolver
1294 .resolve_texture_with_txi(&resref, ResourceTypeCode::from(ResourceType::Tpc))
1295 .expect("tpc");
1296 assert_eq!(&*resolved_tpc.texture.data, b"tpc");
1297 assert_eq!(&*resolved_tpc.txi.expect("txi").data, b"mipmap 0");
1298
1299 let resolved_tga = resolver
1300 .resolve_texture_with_txi(&resref, ResourceTypeCode::from(ResourceType::Tga))
1301 .expect("tga");
1302 assert_eq!(&*resolved_tga.texture.data, b"tga");
1303 assert_eq!(&*resolved_tga.txi.expect("txi").data, b"mipmap 0");
1304
1305 let resolved_dds = resolver
1306 .resolve_texture_with_txi(&resref, ResourceTypeCode::from(ResourceType::Dds))
1307 .expect("dds");
1308 assert_eq!(&*resolved_dds.texture.data, b"dds");
1309 assert_eq!(&*resolved_dds.txi.expect("txi").data, b"mipmap 0");
1310 }
1311
1312 #[test]
1313 fn tpc_handoff_parses_container_and_preserves_boundaries() {
1314 let payload = vec![0xAA; 16];
1315 let embedded_txi = b"cube 1\n".to_vec();
1316 let tpc_bytes = sample_tpc_bytes(&payload, &embedded_txi);
1317
1318 let override_source = OverrideSource::from_inputs(vec![
1319 OverrideInput {
1320 resref: "module".into(),
1321 resource_type: ResourceTypeCode::from(ResourceType::Tpc),
1322 data: tpc_bytes.clone(),
1323 source_label: "override/module.tpc".into(),
1324 },
1325 OverrideInput {
1326 resref: "module".into(),
1327 resource_type: ResourceTypeCode::from(ResourceType::Txi),
1328 data: b"external 1".to_vec(),
1329 source_label: "override/module.txi".into(),
1330 },
1331 ])
1332 .expect("override source");
1333 let resolver = Resolver::new().with_source(ResolverSourceRef::Override(&override_source));
1334
1335 let resref = ResRef::new("module").expect("resref");
1336 let handoff = resolver
1337 .resolve_tpc_with_txi_handoff(&resref)
1338 .expect("handoff parse")
1339 .expect("resolved");
1340 assert_eq!(&*handoff.resolved.texture.data, &*tpc_bytes);
1341 assert_eq!(handoff.tpc.payload, payload);
1342 assert_eq!(handoff.tpc.txi_footer, embedded_txi);
1343 assert_eq!(&*handoff.resolved.txi.expect("txi").data, b"external 1");
1344 }
1345
1346 #[test]
1347 fn tpc_handoff_reports_parse_errors_for_invalid_tpc_data() {
1348 let override_source = OverrideSource::from_inputs(vec![OverrideInput {
1349 resref: "module".into(),
1350 resource_type: ResourceTypeCode::from(ResourceType::Tpc),
1351 data: b"bad".to_vec(),
1352 source_label: "override/module.tpc".into(),
1353 }])
1354 .expect("override source");
1355 let resolver = Resolver::new().with_source(ResolverSourceRef::Override(&override_source));
1356 let resref = ResRef::new("module").expect("resref");
1357
1358 let err = resolver
1359 .resolve_tpc_with_txi_handoff(&resref)
1360 .expect_err("invalid tpc should fail");
1361 assert!(matches!(err, TpcHandoffError::ParseTpc { .. }));
1362 }
1363
1364 #[test]
1365 fn tpc_handoff_exposes_embedded_payload_hint() {
1366 let payload = sample_dds_bytes();
1367 let tpc_bytes = sample_tpc_bytes_exact_payload(&payload);
1368
1369 let override_source = OverrideSource::from_inputs(vec![OverrideInput {
1370 resref: "module".into(),
1371 resource_type: ResourceTypeCode::from(ResourceType::Tpc),
1372 data: tpc_bytes,
1373 source_label: "override/module.tpc".into(),
1374 }])
1375 .expect("override source");
1376 let resolver = Resolver::new().with_source(ResolverSourceRef::Override(&override_source));
1377 let resref = ResRef::new("module").expect("resref");
1378
1379 let handoff = resolver
1380 .resolve_tpc_with_txi_handoff(&resref)
1381 .expect("handoff parse")
1382 .expect("resolved");
1383 assert_eq!(
1384 handoff.embedded_payload_hint(),
1385 TpcEmbeddedPayloadHint::DdsByMagic
1386 );
1387 }
1388
1389 #[test]
1390 fn tpc_handoff_raw_query_validates_resref() {
1391 let resolver = Resolver::new();
1392 let err = resolver
1396 .resolve_tpc_with_txi_handoff_raw("名前")
1397 .expect_err("invalid resref should fail");
1398 assert!(matches!(err, TpcHandoffError::InvalidResRef { .. }));
1399 }
1400
1401 #[test]
1402 fn tpc_embedded_payload_hint_detects_dds_magic() {
1403 let payload = sample_dds_bytes();
1404 assert_eq!(
1405 classify_tpc_embedded_payload(payload.as_slice()),
1406 TpcEmbeddedPayloadHint::DdsByMagic
1407 );
1408 }
1409
1410 #[test]
1411 fn tpc_embedded_payload_hint_detects_likely_tga() {
1412 let payload = sample_tga_bytes();
1413 assert_eq!(
1414 classify_tpc_embedded_payload(payload.as_slice()),
1415 TpcEmbeddedPayloadHint::LikelyTga
1416 );
1417 }
1418
1419 #[test]
1420 fn tpc_embedded_payload_hint_is_unknown_when_no_signature_matches() {
1421 let payload = [0xFF_u8; 12];
1422 assert_eq!(
1423 classify_tpc_embedded_payload(&payload),
1424 TpcEmbeddedPayloadHint::Unknown
1425 );
1426 }
1427
1428 #[test]
1429 fn tpc_embedded_payload_hint_requires_valid_dds_parse() {
1430 let payload = b"DDS not-a-valid-dds-container";
1431 assert_eq!(
1432 classify_tpc_embedded_payload(payload),
1433 TpcEmbeddedPayloadHint::Unknown
1434 );
1435 }
1436}