#ifndef _UBI_SB_STREAMFILE_H_ #define _UBI_SB_STREAMFILE_H_ #include "../streamfile.h" #include "ubi_sb_garbage_streamfile.h" typedef struct { /* config */ off_t stream_offset; off_t stream_size; int layer_number; int layer_count; int layer_max; int big_endian; int layer_hijack; /* internal config */ off_t header_next_start; /* offset to header field */ off_t header_sizes_start; /* offset to header table */ off_t header_data_start; /* offset to header data */ off_t block_next_start; /* offset to block field */ off_t block_sizes_start; /* offset to block table */ off_t block_data_start; /* offset to block data */ size_t header_size; /* derived */ /* state */ off_t logical_offset; /* fake offset */ off_t physical_offset; /* actual offset */ size_t block_size; /* current size */ size_t next_block_size; /* next size */ size_t skip_size; /* size from block start to reach data */ size_t data_size; /* usable size in a block */ size_t logical_size; } ubi_sb_io_data; static size_t ubi_sb_io_read(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, ubi_sb_io_data* data) { uint32_t(*read_u32)(off_t, STREAMFILE*) = data->big_endian ? read_u32be : read_u32le; size_t total_read = 0; int i; /* re-start when previous offset (can't map logical<>physical offsets) */ if (data->logical_offset < 0 || offset < data->logical_offset) { data->physical_offset = data->stream_offset; data->logical_offset = 0x00; data->data_size = 0; /* process header block (slightly different and data size may be 0) */ { data->block_size = data->header_size; data->next_block_size = read_u32(data->physical_offset + data->header_next_start, sf); if (data->header_sizes_start) { data->skip_size = data->header_data_start; for (i = 0; i < data->layer_number; i++) { data->skip_size += read_u32(data->physical_offset + data->header_sizes_start + i*0x04, sf); } data->data_size = read_u32(data->physical_offset + data->header_sizes_start + data->layer_number*0x04, sf); } if (data->data_size == 0) { data->physical_offset += data->block_size; } } } /* read blocks */ while (length > 0) { /* ignore EOF */ if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { break; } /* process new block */ if (data->data_size == 0) { data->block_size = data->next_block_size; if (data->block_next_start) /* not set when fixed block size */ data->next_block_size = read_u32(data->physical_offset + data->block_next_start, sf); data->skip_size = data->block_data_start; for (i = 0; i < data->layer_number; i++) { data->skip_size += read_u32(data->physical_offset + data->block_sizes_start + i*0x04, sf); } data->data_size = read_u32(data->physical_offset + data->block_sizes_start + data->layer_number*0x04, sf); } /* move to next block */ if (offset >= data->logical_offset + data->data_size) { if (data->block_size == 0 || data->block_size == 0xFFFFFFFF) break; data->physical_offset += data->block_size; data->logical_offset += data->data_size; data->data_size = 0; continue; } /* read data */ { size_t bytes_consumed, bytes_done, to_read; bytes_consumed = offset - data->logical_offset; to_read = data->data_size - bytes_consumed; if (to_read > length) to_read = length; bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, sf); total_read += bytes_done; dest += bytes_done; offset += bytes_done; length -= bytes_done; if (bytes_done != to_read || bytes_done == 0) { break; /* error/EOF */ } } } return total_read; } static size_t ubi_sb_io_size(STREAMFILE* sf, ubi_sb_io_data* data) { uint8_t buf[1]; if (data->logical_size) return data->logical_size; /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ ubi_sb_io_read(sf, buf, 0x7FFFFFFF, 1, data); data->logical_size = data->logical_offset; return data->logical_size; } static int ubi_sb_io_init(STREAMFILE* sf, ubi_sb_io_data* data) { uint32_t(*read_u32)(off_t, STREAMFILE*) = data->big_endian ? read_u32be : read_u32le; off_t offset = data->stream_offset; uint32_t version; int i; if (data->stream_offset + data->stream_size > get_streamfile_size(sf)) { VGM_LOG("UBI SB: bad size\n"); goto fail; } /* Layers have a main header, then headered blocks with data. * We configure stuff to unify parsing of all variations. */ version = read_u32(offset+0x00, sf); /* it was bound to happen... orz */ if (data->layer_hijack == 1 && version == 0x000B0008) version = 0xFFFF0007; switch(version) { case 0x00000002: /* Splinter Cell */ /* - layer header * 0x04: layer count * 0x08: stream size * 0x0c: block header size * 0x10: block size (fixed) * 0x14: min layer size? * - block header * 0x00: block number * 0x04: block offset * 0x08+(04*N): layer size per layer * 0xNN: layer data per layer */ data->layer_max = read_u32(offset+0x04, sf); data->header_next_start = 0x10; data->header_sizes_start = 0; data->header_data_start = 0x18; data->block_next_start = 0; data->block_sizes_start = 0x08; data->block_data_start = 0x08 + data->layer_max*0x04; break; case 0x00000003: /* Rainbow Six 3 */ /* - layer header * 0x04: layer count * 0x08: stream size * 0x0c: block header size * 0x10: block size (fixed) * 0x14: min layer data? * 0x18: size of header sizes and headers * 0x1c+(04*N): header size per layer * - block header * 0x00: block number * 0x04: block offset * 0x08+(04*N): layer size per layer * 0xNN: layer data per layer */ data->layer_max = read_u32(offset+0x04, sf); data->header_next_start = 0x10; data->header_sizes_start = 0x1c; data->header_data_start = 0x1c + data->layer_max*0x04; data->block_next_start = 0; data->block_sizes_start = 0x08; data->block_data_start = 0x08 + data->layer_max*0x04; break; case 0x00000004: /* Prince of Persia: Sands of Time, Batman: Rise of Sin Tzu */ /* - layer header * 0x04: layer count * 0x08: stream size * 0x0c: block count * 0x10: block header size * 0x14: block size (fixed) * 0x18: min layer data? * 0x1c: size of header sizes and headers * 0x20+(04*N): header size per layer * - block header * 0x00: block number * 0x04: block offset * 0x08: always 0x03 * 0x0c+(04*N): layer size per layer * 0xNN: layer data per layer */ data->layer_max = read_u32(offset+0x04, sf); data->header_next_start = 0x14; data->header_sizes_start = 0x20; data->header_data_start = 0x20 + data->layer_max*0x04; data->block_next_start = 0; data->block_sizes_start = 0x0c; data->block_data_start = 0x0c + data->layer_max*0x04; break; case 0x00000007: /* Splinter Cell: Essentials, Splinter Cell 3D */ /* - layer header * 0x04: config? * 0x08: layer count * 0x0c: stream size * 0x10: block count * 0x14: block header size * 0x18: block size (fixed) * 0x1c+(04*8): min layer data? for 8 layers (-1 after layer count) * 0x3c: size of header sizes and headers * 0x40+(04*N): header size per layer * 0xNN: header data per layer * - block header * 0x00: block number * 0x04: block offset * 0x08: always 0x03 * 0x0c+(04*N): layer size per layer * 0xNN: layer data per layer */ data->layer_max = read_u32(offset+0x08, sf); data->header_next_start = 0x18; data->header_sizes_start = 0x40; data->header_data_start = 0x40 + data->layer_max*0x04; data->block_next_start = 0; data->block_sizes_start = 0x0c; data->block_data_start = 0x0c + data->layer_max*0x04; break; case 0xFFFF0007: /* Ghost Recon Advanced Warfighter (X360) */ /* - layer header * 0x04: config? * 0x08: layer count * 0x0c: stream size * 0x10: block count * 0x14: block header size * 0x18: block size (fixed) * 0x1c+(04*11): min layer data? for 11 layers (-1 after layer count) * 0x48: size of header sizes and headers * 0x4c+(04*N): header size per layer * 0xNN: header data per layer * - block header * 0x00: block number * 0x04: block offset * 0x08: always 0x03 * 0x0c+(04*N): layer size per layer * 0xNN: layer data per layer */ data->layer_max = read_u32(offset+0x08, sf); data->header_next_start = 0x18; data->header_sizes_start = 0x4c; data->header_data_start = 0x4c + data->layer_max*0x04; data->block_next_start = 0; data->block_sizes_start = 0x0c; data->block_data_start = 0x0c + data->layer_max*0x04; break; case 0x00040008: /* Assassin's Creed */ case 0x000B0008: /* Open Season, Surf's Up, TMNT, Splinter Cell HD */ case 0x000C0008: /* Splinter Cell: Double Agent */ case 0x00100008: /* Rainbow Six 2 */ /* - layer header * 0x04: config? * 0x08: layer count * 0x0c: blocks count * 0x10: block header size * 0x14: size of header sizes and headers/data * 0x18: next block size * 0x1c+(04*N): layer header size * 0xNN: header data per layer * - block header: * 0x00: always 0x03 * 0x04: next block size * 0x08+(04*N): layer size per layer * 0xNN: layer data per layer */ data->layer_max = read_u32(offset+0x08, sf); data->header_next_start = 0x18; data->header_sizes_start = 0x1c; data->header_data_start = 0x1c + data->layer_max*0x04; data->block_next_start = 0x04; data->block_sizes_start = 0x08; data->block_data_start = 0x08 + data->layer_max*0x04; break; case 0x00100009: /* Splinter Cell: Pandora Tomorrow HD, Prince of Persia 2008, Scott Pilgrim */ /* - layer header * 0x04: config? * 0x08: layer count * 0x0c: blocks count * 0x10: block header size * 0x14: size of header sizes and headers/data * 0x18: next block size * 0x1c+(04*10): usable size per layer * 0x5c+(04*N): layer header size * 0xNN: header data per layer * - block header: * 0x00: always 0x03 * 0x04: next block size * 0x08+(04*N): layer size per layer * 0xNN: layer data per layer */ data->layer_max = read_u32(offset+0x08, sf); data->header_next_start = 0x18; data->header_sizes_start = 0x5c; data->header_data_start = 0x5c + data->layer_max*0x04; data->block_next_start = 0x04; data->block_sizes_start = 0x08; data->block_data_start = 0x08 + data->layer_max*0x04; break; default: VGM_LOG("UBI SB: unknown layer header %08x\n", version); goto fail; } /* get base size to simplify later parsing */ data->header_size = data->header_data_start; if (data->header_sizes_start) { for (i = 0; i < data->layer_max; i++) { data->header_size += read_u32(offset + data->header_sizes_start + i*0x04, sf); } } /* force read header block */ data->logical_offset = -1; /* just in case some headers may use less layers that stream has */ VGM_ASSERT(data->layer_count != data->layer_max, "UBI SB: non-matching layer counts\n"); if (data->layer_count > data->layer_max) { VGM_LOG("UBI SB: layer count bigger than layer max\n"); goto fail; } /* Common layer quirks: * - layer format depends on its own version and not on platform or DARE engine version * - codec header may be in the layer header, or in the first block * - stream size doesn't include padding * - block number goes from 1 to block_count * - block offset is relative to layer start * - blocks data size varies between blocks and between layers in the same block * - "config?" is a small value that varies between streams of the same game * - next block size is 0 at last block * - both Ubi SB and Ubi BAO use same-version layers */ return 1; fail: return 0; } /* Handles deinterleaving of Ubisoft's headered+blocked 'multitrack' streams */ static STREAMFILE* setup_ubi_sb_streamfile(STREAMFILE* sf, off_t stream_offset, size_t stream_size, int layer_number, int layer_count, int big_endian, int layer_hijack) { STREAMFILE* new_sf = NULL; ubi_sb_io_data io_data = {0}; io_data.stream_offset = stream_offset; io_data.stream_size = stream_size; io_data.layer_number = layer_number; io_data.layer_count = layer_count; io_data.big_endian = big_endian; io_data.layer_hijack = layer_hijack; if (!ubi_sb_io_init(sf, &io_data)) goto fail; /* setup subfile */ new_sf = open_wrap_streamfile(sf); if (layer_hijack == 2 && is_garbage_stream(sf)) { new_sf = setup_ubi_sb_garbage_streamfile_f(new_sf); //io_data.stream_size = get_garbage_stream_size(stream_offset, stream_size); //todo must do relative calcs, doesn't seem needed } new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(ubi_sb_io_data), ubi_sb_io_read,ubi_sb_io_size); new_sf = open_buffer_streamfile_f(new_sf,0); return new_sf; fail: return NULL; } #endif /* _UBI_SB_STREAMFILE_H_ */