127 lines
4.6 KiB
Plaintext
127 lines
4.6 KiB
Plaintext
|
|
// Asura Engine archive format
|
||
|
|
// Used by Rebellion games: Sniper Elite series, AVP 2010, etc.
|
||
|
|
// Archives contain chunks like FCSR (resources), TSXT (textures), LFSR (lists), etc.
|
||
|
|
|
||
|
|
type asura_archive [display="Asura Archive"] byteorder BE
|
||
|
|
{
|
||
|
|
criteria {
|
||
|
|
// Match "Asura " signature (8 bytes with trailing spaces)
|
||
|
|
require ascii(bytesat(0, 8)) == "Asura ";
|
||
|
|
}
|
||
|
|
|
||
|
|
// Store base name for chunk naming (use full filename, not basename)
|
||
|
|
base_name = _name;
|
||
|
|
|
||
|
|
// Set display name for this archive
|
||
|
|
_name = base_name + ".asr";
|
||
|
|
|
||
|
|
// Asura signature
|
||
|
|
signature = ascii(read(8));
|
||
|
|
signature = signature [ui, readonly, display="Signature"];
|
||
|
|
|
||
|
|
// Track chunk index
|
||
|
|
chunk_index = 0;
|
||
|
|
|
||
|
|
// Parse chunks until end of file (need at least 16 bytes for header)
|
||
|
|
while (pos() + 16 <= size()) {
|
||
|
|
// Save chunk start position
|
||
|
|
chunk_start = pos();
|
||
|
|
|
||
|
|
// Peek at chunk header to get ID and size
|
||
|
|
peek_id = ascii(read(4));
|
||
|
|
u32 peek_size;
|
||
|
|
|
||
|
|
// Seek back to chunk start for proper parsing
|
||
|
|
seek(chunk_start);
|
||
|
|
|
||
|
|
// Validate chunk - if peek_size is 0 or invalid, stop parsing
|
||
|
|
if (peek_size == 0 || peek_size > size() - chunk_start) {
|
||
|
|
remaining = size() - pos();
|
||
|
|
if (remaining > 0) {
|
||
|
|
raw_tail = read(remaining);
|
||
|
|
raw_tail = raw_tail [ui, display="Raw Tail Data"];
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Set chunk name before parsing (child will inherit this)
|
||
|
|
_name = base_name + ".chunk" + chunk_index;
|
||
|
|
|
||
|
|
// Dispatch to appropriate chunk type based on ID and push to arrays
|
||
|
|
if (peek_id == "FCSR" || peek_id == "RSCF") {
|
||
|
|
chunk = parse_here("fcsr_chunk");
|
||
|
|
push("fcsr_chunks", chunk);
|
||
|
|
} else if (peek_id == "TSXT" || peek_id == "TXST") {
|
||
|
|
chunk = parse_here("tsxt_chunk");
|
||
|
|
push("tsxt_chunks", chunk);
|
||
|
|
} else if (peek_id == "LFSR" || peek_id == "RSFL") {
|
||
|
|
chunk = parse_here("lfsr_chunk");
|
||
|
|
push("lfsr_chunks", chunk);
|
||
|
|
} else if (peek_id == "STSA" || peek_id == "ASTS") {
|
||
|
|
chunk = parse_here("stsa_chunk");
|
||
|
|
push("stsa_chunks", chunk);
|
||
|
|
} else if (peek_id == "TXET" || peek_id == "TEXT") {
|
||
|
|
chunk = parse_here("txet_chunk");
|
||
|
|
push("txet_chunks", chunk);
|
||
|
|
} else if (peek_id == "GSMS" || peek_id == "SMSG") {
|
||
|
|
chunk = parse_here("gsms_chunk");
|
||
|
|
push("gsms_chunks", chunk);
|
||
|
|
} else if (peek_id == "ITNE" || peek_id == "ENTI") {
|
||
|
|
chunk = parse_here("itne_chunk");
|
||
|
|
push("itne_chunks", chunk);
|
||
|
|
} else if (peek_id == "STUC" || peek_id == "CUTS") {
|
||
|
|
chunk = parse_here("stuc_chunk");
|
||
|
|
push("stuc_chunks", chunk);
|
||
|
|
} else if (peek_id == "CATC") {
|
||
|
|
chunk = parse_here("catc_chunk");
|
||
|
|
push("catc_chunks", chunk);
|
||
|
|
} else if (peek_id == "RTTC") {
|
||
|
|
chunk = parse_here("rttc_chunk");
|
||
|
|
push("rttc_chunks", chunk);
|
||
|
|
} else if (peek_id == "VETC") {
|
||
|
|
chunk = parse_here("vetc_chunk");
|
||
|
|
push("vetc_chunks", chunk);
|
||
|
|
} else if (peek_id == "POSE") {
|
||
|
|
chunk = parse_here("pose_chunk");
|
||
|
|
push("pose_chunks", chunk);
|
||
|
|
} else if (peek_id == "PVET") {
|
||
|
|
chunk = parse_here("pvet_chunk");
|
||
|
|
push("pvet_chunks", chunk);
|
||
|
|
} else if (peek_id == "TATC") {
|
||
|
|
chunk = parse_here("tatc_chunk");
|
||
|
|
push("tatc_chunks", chunk);
|
||
|
|
} else if (peek_id == "TTAT") {
|
||
|
|
chunk = parse_here("ttat_chunk");
|
||
|
|
push("ttat_chunks", chunk);
|
||
|
|
} else if (peek_id == "TVET") {
|
||
|
|
chunk = parse_here("tvet_chunk");
|
||
|
|
push("tvet_chunks", chunk);
|
||
|
|
} else if (peek_id == "UCAT") {
|
||
|
|
chunk = parse_here("ucat_chunk");
|
||
|
|
push("ucat_chunks", chunk);
|
||
|
|
} else if (peek_id == "UVET") {
|
||
|
|
chunk = parse_here("uvet_chunk");
|
||
|
|
push("uvet_chunks", chunk);
|
||
|
|
} else {
|
||
|
|
// Unknown chunk type - parse as generic chunk
|
||
|
|
chunk = parse_here("asura_chunk");
|
||
|
|
push("unknown_chunks", chunk);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Increment chunk index
|
||
|
|
chunk_index = chunk_index + 1;
|
||
|
|
|
||
|
|
// Seek to next chunk (chunk_start + peek_size)
|
||
|
|
next_chunk = chunk_start + peek_size;
|
||
|
|
if (next_chunk > size()) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
seek(next_chunk);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Restore archive name and show total count
|
||
|
|
_name = base_name + ".asr";
|
||
|
|
chunk_count = chunk_index;
|
||
|
|
chunk_count = chunk_count [ui, readonly, display="Total Chunks"];
|
||
|
|
}
|