// 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"]; }