#define _LARGEFILE64_SOURCE #include #include #include #include #include #include #include "MPQCryptography.h" #include "mpq-bios.h" #include "mpq-errors.h" #include "mpq-misc.h" #include "int-bios.h" #include "errors.h" #include "inttypes.h" typedef struct { /* basic version of the header. */ char magic[4]; uint32_t header_size; uint32_t archive_size; uint16_t format_version; uint16_t sector_size; uint32_t hash_table_offset; uint32_t block_table_offset; uint32_t hash_table_entries; uint32_t block_table_entries; /* extended version of header - Burning Crusade. */ uint64_t extended_block_table_offset; uint16_t hash_table_offset_high; uint16_t block_table_offset_high; } __attribute__ ((packed)) mpq_header_t; typedef struct { uint32_t file_path_hasha; uint32_t file_path_hashb; uint16_t language; uint16_t platform; uint32_t file_block_index; } __attribute__ ((packed)) mpq_hash_t; typedef struct { uint32_t block_offset; uint32_t block_size; uint32_t file_size; uint32_t flags; } __attribute__ ((packed)) mpq_block_t; typedef struct { uint32_t file_path_hasha; uint32_t file_path_hashb; uint16_t language; uint16_t platform; uint32_t file_block_index; } hash_t; /* Defined in int-bios.h typedef struct { uint64_t block_offset; uint32_t block_size; uint32_t file_size; uint32_t flags; } block_t; */ struct mpq_archive_t { int fd; int closeit; uint32_t header_size; uint32_t archive_size; uint16_t format_version; uint32_t sector_size; uint64_t hash_table_offset; uint64_t block_table_offset; uint32_t hash_table_entries; uint32_t block_table_entries; uint64_t extended_block_table_offset; hash_t * hashs; block_t * blocks; }; #ifndef O_BINARY #define O_BINARY 0 #endif struct mpq_archive_t * mpqlib_open_archive(const char * fname) { int fd; struct mpq_archive_t * r; if ((fd = open(fname, O_RDONLY | O_LARGEFILE | O_BINARY)) == -1) { __mpqlib_errno = MPQLIB_ERROR_OPEN; return NULL; } r = mpqlib_reopen_archive(fd); if (r) { r->closeit = 1; } else { close(fd); } return r; } #define STD_HEADER_SIZE 0x20 #define EXT_HEADER_SIZE 0x0C #define no_mpq() { \ __mpqlib_errno = MPQLIB_ERROR_NOT_MPQ_ARCHIVE; \ free(mpq_a); \ return NULL; \ } #define read_error() { \ __mpqlib_errno = MPQLIB_ERROR_READ; \ free_archive(mpq_a); \ free(mpq_hashs); \ free(mpq_blocks); \ return NULL; \ } static void free_archive(struct mpq_archive_t * mpq_a) { free(mpq_a->hashs); free(mpq_a->blocks); free(mpq_a); } static int read_data(struct mpq_archive_t * mpq_a, void * buf, size_t l) { if (read(mpq_a->fd, buf, l) != l) return 0; return 1; } void mpqlib_init() { __mpqlib_init_cryptography(); } void mpqlib_close_archive(struct mpq_archive_t * mpq_a) { if (mpq_a) { if (mpq_a->closeit) { close(mpq_a->fd); } free_archive(mpq_a); } } struct mpq_archive_t * mpqlib_reopen_archive(int fd) { struct mpq_archive_t * mpq_a; mpq_header_t mpq_h; mpq_hash_t * mpq_hashs; mpq_block_t * mpq_blocks; uint16_t * mpq_extblocks = NULL; int i; /****TODO****/ /* Implement endianess a bit everywhere */ if (!(mpq_a = (struct mpq_archive_t *) malloc(sizeof(struct mpq_archive_t)))) { __mpqlib_errno = MPQLIB_ERROR_MEMORY; return NULL; } mpq_a->closeit = 0; mpq_a->fd = fd; if (read(fd, &mpq_h, STD_HEADER_SIZE) != STD_HEADER_SIZE) no_mpq(); if (strncmp(mpq_h.magic, "MPQ\x1a", 4)) no_mpq(); if ((mpq_h.format_version | 1) != 1) no_mpq(); if (mpq_h.format_version == 1) { if (!read_data(mpq_a, ((char *)(&mpq_h)) + STD_HEADER_SIZE, EXT_HEADER_SIZE)) no_mpq(); } else { mpq_h.extended_block_table_offset = 0; mpq_h.hash_table_offset_high = 0; mpq_h.block_table_offset_high = 0; } mpq_a->header_size = mpq_h.header_size; mpq_a->archive_size = mpq_h.archive_size; mpq_a->format_version = mpq_h.format_version; mpq_a->sector_size = 0x200 << mpq_h.sector_size; mpq_a->hash_table_offset = (uint64_t) mpq_h.hash_table_offset + (((uint64_t) mpq_h.hash_table_offset_high) << 32); mpq_a->block_table_offset = (uint64_t) mpq_h.block_table_offset + (((uint64_t) mpq_h.block_table_offset_high) << 32); mpq_a->hash_table_entries = mpq_h.hash_table_entries; mpq_a->block_table_entries = mpq_h.block_table_entries; mpq_a->extended_block_table_offset = mpq_h.extended_block_table_offset; if (!(mpq_a->hashs = (hash_t *) malloc(mpq_a->hash_table_entries * sizeof(hash_t)))) { __mpqlib_errno = MPQLIB_ERROR_MEMORY; free(mpq_a); return NULL; } if (!(mpq_a->blocks = (block_t *) malloc(mpq_a->block_table_entries * sizeof(block_t)))) { __mpqlib_errno = MPQLIB_ERROR_MEMORY; free(mpq_a->hashs); free(mpq_a); return NULL; } if (!(mpq_hashs = (mpq_hash_t *) malloc(mpq_a->hash_table_entries * sizeof(mpq_hash_t)))) { __mpqlib_errno = MPQLIB_ERROR_MEMORY; free_archive(mpq_a); return NULL; } if (!(mpq_blocks = (mpq_block_t *) malloc(mpq_a->block_table_entries * sizeof(mpq_block_t)))) { __mpqlib_errno = MPQLIB_ERROR_MEMORY; free(mpq_hashs); free_archive(mpq_a); return NULL; } if (lseek64(fd, mpq_a->hash_table_offset, SEEK_SET) != mpq_a->hash_table_offset) read_error(); if (!read_data(mpq_a, mpq_hashs, mpq_a->hash_table_entries * sizeof(mpq_hash_t))) read_error(); if (lseek64(fd, mpq_a->block_table_offset, SEEK_SET) != mpq_a->block_table_offset) read_error(); if (!read_data(mpq_a, mpq_blocks, mpq_a->block_table_entries * sizeof(mpq_block_t))) read_error(); if (mpq_a->extended_block_table_offset) { if (lseek64(fd, mpq_a->block_table_offset, SEEK_SET)) read_error(); if (!(mpq_extblocks = (uint16_t *) malloc(mpq_a->block_table_entries * sizeof(uint16_t)))) { __mpqlib_errno = MPQLIB_ERROR_MEMORY; free_archive(mpq_a); free(mpq_hashs); free(mpq_blocks); return NULL; } if (!read_data(mpq_a, mpq_blocks, mpq_a->block_table_entries * sizeof(mpq_block_t))) { free(mpq_extblocks); read_error(); } } __mpqlib_decrypt(mpq_hashs, mpq_a->hash_table_entries * sizeof(mpq_block_t), __mpqlib_hash_cstring("(hash table)", 3), 1); __mpqlib_decrypt(mpq_blocks, mpq_a->block_table_entries * sizeof(mpq_block_t), __mpqlib_hash_cstring("(block table)", 3), 1); for (i = 0; i < mpq_a->hash_table_entries; i++) { /****TODO****/ /* Implement various checks of the hash table. */ mpq_a->hashs[i].file_path_hasha = mpq_hashs[i].file_path_hasha; mpq_a->hashs[i].file_path_hashb = mpq_hashs[i].file_path_hashb; mpq_a->hashs[i].language = mpq_hashs[i].language; mpq_a->hashs[i].platform = mpq_hashs[i].platform; mpq_a->hashs[i].file_block_index = mpq_hashs[i].file_block_index; } for (i = 0; i < mpq_a->block_table_entries; i++) { /****TODO****/ /* Implement various checks of the block table. */ mpq_a->blocks[i].block_offset = mpq_blocks[i].block_offset + (mpq_extblocks ? (((uint64_t) mpq_extblocks[i]) << 32): 0); mpq_a->blocks[i].block_size = mpq_blocks[i].block_size; mpq_a->blocks[i].file_size = mpq_blocks[i].file_size; mpq_a->blocks[i].flags = mpq_blocks[i].flags; } if (mpq_extblocks) free(mpq_extblocks); free(mpq_blocks); free(mpq_hashs); return mpq_a; } void mpqlib_printtables(struct mpq_archive_t * mpq_a) { int i; printf("Archive details:\n"); printf("Header size: %8d\n", mpq_a->header_size); printf("Archive size: %8d\n", mpq_a->archive_size); printf("Format version: %8d\n", mpq_a->format_version); printf("Sector size: %8d\n\n", mpq_a->sector_size); printf("Hash table offset: %016llX\n", mpq_a->hash_table_offset); printf("Block table offset: %016llX\n", mpq_a->block_table_offset); printf("Hash table entries: %8d\n", mpq_a->hash_table_entries); printf("Block table entries: %8d\n", mpq_a->block_table_entries); printf("Extended block table offset: %016llX\n\n\n", mpq_a->extended_block_table_offset); printf("Hash table dump.\n"); printf("HashA HashB language platform index\n"); printf("-------- -------- -------- -------- -----\n"); for (i = 0; i < mpq_a->hash_table_entries; i++) { printf("%08X %08X %08X %08X %d\n", mpq_a->hashs[i].file_path_hasha, mpq_a->hashs[i].file_path_hashb, mpq_a->hashs[i].language, mpq_a->hashs[i].platform, mpq_a->hashs[i].file_block_index); } printf("\n\nBlock table dump.\n"); printf("Entry Block offset size filesize flags\n"); printf("-------- ---------------- -------- -------- --------\n"); for (i = 0; i < mpq_a->block_table_entries; i++) { printf("%8d %016llX %8d %8d %08X\n", i, mpq_a->blocks[i].block_offset, mpq_a->blocks[i].block_size, mpq_a->blocks[i].file_size, mpq_a->blocks[i].flags); } } int mpqlib_find_hash_entry_by_name(struct mpq_archive_t * mpq_a, const char * name, uint32_t language, uint32_t platform) { uint32_t hA, hB; hA = mpqlib_hashA_filename(name); hB = mpqlib_hashB_filename(name); return mpqlib_find_hash_entry_by_hash(mpq_a, hA, hB, language, platform); } int mpqlib_find_hash_entry_by_hash(struct mpq_archive_t * mpq_a, uint32_t hA, uint32_t hB, uint32_t language, uint32_t platform) { int i; for (i = 0; i < mpq_a->hash_table_entries; i++) { if ((mpq_a->hashs[i].file_path_hasha == hA) && (mpq_a->hashs[i].file_path_hashb == hB) && (mpq_a->hashs[i].language == language) && (mpq_a->hashs[i].platform == platform)) { return mpq_a->hashs[i].file_block_index; } } return -1; } const block_t * __mpqlib_get_block_entry(struct mpq_archive_t * mpq_a, int entry) { if ((entry >= mpq_a->block_table_entries) || (entry < 0)) return NULL; return mpq_a->blocks + entry; } int __mpqlib_seek(struct mpq_archive_t * mpq_a, off_t off) { if (lseek64(mpq_a->fd, off, SEEK_SET) != off) return 0; return 1; } int __mpqlib_read(struct mpq_archive_t * mpq_a, void * buffer, size_t size) { return read_data(mpq_a, buffer, size); } uint64_t mpqlib_ioctl(struct mpq_archive_t * mpq_a, enum mpqlib_ioctl_t command, int entry) { switch(command) { case MPQLIB_IOCTL_NO_ACTION: return 0; case MPQLIB_IOCTL_GET_FORMAT_VERSION: return mpq_a->format_version; case MPQLIB_IOCTL_GET_SECTOR_SIZE: return mpq_a->sector_size; case MPQLIB_IOCTL_GET_BLOCK_OFFSET: case MPQLIB_IOCTL_GET_BLOCK_SIZE: case MPQLIB_IOCTL_GET_FILE_SIZE: case MPQLIB_IOCTL_GET_FLAGS: if (entry >= mpq_a->block_table_entries) { __mpqlib_errno = MPQLIB_ERROR_IOCTL_INVALID_ENTRY; return -1; } switch (command) { case MPQLIB_IOCTL_GET_BLOCK_OFFSET: return mpq_a->blocks[entry].block_offset; case MPQLIB_IOCTL_GET_BLOCK_SIZE: return mpq_a->blocks[entry].block_size; case MPQLIB_IOCTL_GET_FILE_SIZE: return mpq_a->blocks[entry].file_size; case MPQLIB_IOCTL_GET_FLAGS: return mpq_a->blocks[entry].flags; default: // should never get there. __mpqlib_errno = MPQLIB_ERROR_UNKNOWN; return -1; } default: __mpqlib_errno = MPQLIB_ERROR_INVALID_IOCTL; return -1; } }