Skip to content

Commit a626096

Browse files
Merge pull request #35 from wowemulation-dev/fix/encoding-lookup-and-toc-hash
fix: encoding page lookup and archive group TOC hash
2 parents 21261da + 5d6efeb commit a626096

3 files changed

Lines changed: 438 additions & 23 deletions

File tree

crates/cascette-formats/src/archive/archive_group.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,18 @@ impl ArchiveGroupBuilder {
198198
let total_data_size = entry_count * bytes_per_entry;
199199
let chunk_count = total_data_size.div_ceil(0x1000); // 4KB chunks
200200

201-
// Write entries in chunks (archive-groups don't use TOC)
201+
// Write entries in chunks, collecting first keys for TOC hash
202+
let entries_per_chunk = 0x1000 / bytes_per_entry;
203+
let mut toc_keys: Vec<Vec<u8>> = Vec::with_capacity(chunk_count);
204+
202205
for chunk_idx in 0..chunk_count {
203-
let start_idx = chunk_idx * (0x1000 / bytes_per_entry);
204-
let end_idx = ((chunk_idx + 1) * (0x1000 / bytes_per_entry)).min(entry_count);
206+
let start_idx = chunk_idx * entries_per_chunk;
207+
let end_idx = ((chunk_idx + 1) * entries_per_chunk).min(entry_count);
208+
209+
// Record first key of each chunk for TOC hash
210+
if let Some(first_entry) = entries.get(start_idx) {
211+
toc_keys.push(first_entry.encoding_key.clone());
212+
}
205213

206214
// Prepare chunk data
207215
let mut chunk_data = Vec::with_capacity(0x1000);
@@ -223,10 +231,11 @@ impl ArchiveGroupBuilder {
223231
writer.write_all(&chunk_data)?;
224232
}
225233

226-
// Write footer
234+
// Write footer with computed TOC hash
235+
let toc_hash = super::index::calculate_toc_hash(&toc_keys);
227236
let footer = IndexFooter {
228-
toc_hash: [0; 8], // No TOC hash for archive-groups
229-
version: 1, // Standard version
237+
toc_hash,
238+
version: 1, // Standard version
230239
reserved: [0, 0],
231240
page_size_kb: 4,
232241
offset_bytes: 6, // 6-byte offsets for archive-groups!

crates/cascette-formats/src/encoding/file.rs

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -341,38 +341,77 @@ impl EncodingFile {
341341
}
342342

343343
/// Find encoding key for a content key
344+
///
345+
/// Uses binary search on the page index to find the candidate page,
346+
/// then linear scan within that page.
344347
pub fn find_encoding(&self, content_key: &ContentKey) -> Option<EncodingKey> {
345-
// Binary search through pages to find the right page
346-
for page in &self.ckey_pages {
347-
for entry in &page.entries {
348-
if entry.content_key == *content_key {
349-
return entry.encoding_keys.first().copied();
350-
}
348+
let key_bytes = *content_key.as_bytes();
349+
350+
// Binary search through the page index to find the right page.
351+
// Each page's first_key is the lowest key in that page, and pages
352+
// are sorted. We find the last page whose first_key <= our key.
353+
let page_idx = self
354+
.ckey_index
355+
.partition_point(|idx| idx.first_key <= key_bytes);
356+
357+
// partition_point returns the first index where first_key > key,
358+
// so the candidate page is at page_idx - 1
359+
if page_idx == 0 {
360+
return None;
361+
}
362+
363+
let page = &self.ckey_pages[page_idx - 1];
364+
for entry in &page.entries {
365+
if entry.content_key == *content_key {
366+
return entry.encoding_keys.first().copied();
351367
}
352368
}
353369
None
354370
}
355371

356372
/// Find all encoding keys for a given content key
373+
///
374+
/// Uses binary search on the page index to find the candidate page,
375+
/// then linear scan within that page.
357376
pub fn find_all_encodings(&self, content_key: &ContentKey) -> Vec<EncodingKey> {
358-
for page in &self.ckey_pages {
359-
for entry in &page.entries {
360-
if entry.content_key == *content_key {
361-
return entry.encoding_keys.clone();
362-
}
377+
let key_bytes = *content_key.as_bytes();
378+
379+
let page_idx = self
380+
.ckey_index
381+
.partition_point(|idx| idx.first_key <= key_bytes);
382+
383+
if page_idx == 0 {
384+
return Vec::new();
385+
}
386+
387+
let page = &self.ckey_pages[page_idx - 1];
388+
for entry in &page.entries {
389+
if entry.content_key == *content_key {
390+
return entry.encoding_keys.clone();
363391
}
364392
}
365393
Vec::new()
366394
}
367395

368396
/// Find `ESpec` for an encoding key
397+
///
398+
/// Uses binary search on the page index to find the candidate page,
399+
/// then linear scan within that page.
369400
pub fn find_espec(&self, encoding_key: &EncodingKey) -> Option<&str> {
370-
// Search through EKey pages
371-
for page in &self.ekey_pages {
372-
for entry in &page.entries {
373-
if entry.encoding_key == *encoding_key {
374-
return self.espec_table.get(entry.espec_index);
375-
}
401+
let key_bytes = *encoding_key.as_bytes();
402+
403+
let page_idx = self
404+
.ekey_index
405+
.partition_point(|idx| idx.first_key <= key_bytes);
406+
407+
if page_idx == 0 {
408+
return None;
409+
}
410+
411+
let page = &self.ekey_pages[page_idx - 1];
412+
for entry in &page.entries {
413+
if entry.encoding_key == *encoding_key {
414+
return self.espec_table.get(entry.espec_index);
376415
}
377416
}
378417
None

0 commit comments

Comments
 (0)