/* * Copyright 2002-2009, Ingo Weinhold, ingo_weinhold@gmx.de. * Distributed under the terms of the MIT License. */ /*! \file ResourceFile.cpp ResourceFile implementation. */ #include #include #include #include #include #include #include #include #include #include #include #include //#include namespace BPrivate { namespace Storage { // ELF defs static const uint32 kMaxELFHeaderSize = std::max(sizeof(Elf32_Ehdr), sizeof(Elf64_Ehdr)) + 32; static const char kELFFileMagic[4] = { 0x7f, 'E', 'L', 'F' }; // sanity bounds static const uint32 kMaxResourceCount = 10000; static const uint32 kELFMaxResourceAlignment = 1024 * 1024 * 10; // 10 MB // recognized file types (indices into kFileTypeNames) enum { FILE_TYPE_UNKNOWN = 0, FILE_TYPE_X86_RESOURCE = 1, FILE_TYPE_PPC_RESOURCE = 2, FILE_TYPE_ELF = 3, FILE_TYPE_PEF = 4, FILE_TYPE_EMPTY = 5, }; const char* kFileTypeNames[] = { "unknown", "x86 resource file", "PPC resource file", "ELF object file", "PEF object file", "empty file", }; // debugging //#define DBG(x) x #define DBG(x) #define OUT printf #define B_VERSION_INFO_TYPE 'APPV' static const uint32 kVersionInfoIntCount = 5; // #pragma mark - helper functions/classes static void read_exactly(BPositionIO& file, off_t position, void* buffer, size_t size, const char* errorMessage = NULL) { ssize_t read = file.ReadAt(position, buffer, size); if (read < 0) throw Exception(read, errorMessage); else if ((size_t)read != size) { if (errorMessage) { throw Exception("%s Read too few bytes (%ld/%lu).", errorMessage, read, size); } else throw Exception("Read too few bytes (%ld/%lu).", read, size); } } static void write_exactly(BPositionIO& file, off_t position, const void* buffer, size_t size, const char* errorMessage = NULL) { ssize_t written = file.WriteAt(position, buffer, size); if (written < 0) throw Exception(written, errorMessage); else if ((size_t)written != size) { if (errorMessage) { throw Exception("%s Wrote too few bytes (%ld/%lu).", errorMessage, written, size); } else throw Exception("Wrote too few bytes (%ld/%lu).", written, size); } } template static inline TV align_value(const TV& value, const TA& alignment) { return ((value + alignment - 1) / alignment) * alignment; } static uint32 calculate_checksum(const void* data, uint32 size) { uint32 checkSum = 0; const uint8* csData = (const uint8*)data; const uint8* dataEnd = csData + size; const uint8* current = csData; for (; current < dataEnd; current += 4) { uint32 word = 0; int32 bytes = std::min((int32)4, (int32)(dataEnd - current)); for (int32 i = 0; i < bytes; i++) word = (word << 8) + current[i]; checkSum += word; } return checkSum; } static inline const void* skip_bytes(const void* buffer, int32 offset) { return (const char*)buffer + offset; } static inline void* skip_bytes(void* buffer, int32 offset) { return (char*)buffer + offset; } static void fill_pattern(uint32 byteOffset, void* _buffer, uint32 count) { uint32* buffer = (uint32*)_buffer; for (uint32 i = 0; i < count; i++) buffer[i] = kUnusedResourceDataPattern[(byteOffset / 4 + i) % 3]; } static void fill_pattern(const void* dataBegin, void* buffer, uint32 count) { fill_pattern((char*)buffer - (const char*)dataBegin, buffer, count); } static void fill_pattern(const void* dataBegin, void* buffer, const void* bufferEnd) { fill_pattern(dataBegin, buffer, ((const char*)bufferEnd - (char*)buffer) / 4); } static bool check_pattern(uint32 byteOffset, void* _buffer, uint32 count, bool hostEndianess) { bool result = true; uint32* buffer = (uint32*)_buffer; for (uint32 i = 0; result && i < count; i++) { uint32 value = buffer[i]; if (!hostEndianess) value = B_SWAP_INT32(value); result = (value == kUnusedResourceDataPattern[(byteOffset / 4 + i) % 3]); } return result; } // #pragma mark - struct MemArea { MemArea(const void* data, uint32 size) : data(data), size(size) {} inline bool check(const void* _current, uint32 skip = 0) const { const char* start = (const char*)data; const char* current = (const char*)_current; return (start <= current && start + size >= current + skip); } const void* data; uint32 size; }; struct resource_parse_info { off_t file_size; int32 resource_count; ResourcesContainer* container; char* info_table; uint32 info_table_offset; uint32 info_table_size; }; // #pragma mark - ResourceFile::ResourceFile() : fFile(), fFileType(FILE_TYPE_UNKNOWN), fHostEndianess(true), fEmptyResources(true) { } ResourceFile::~ResourceFile() { Unset(); } status_t ResourceFile::SetTo(BFile* file, bool clobber) { status_t error = (file ? B_OK : B_BAD_VALUE); Unset(); if (error == B_OK) { try { _InitFile(*file, clobber); } catch (Exception& exception) { Unset(); if (exception.Error() != B_OK) error = exception.Error(); else error = B_ERROR; } } return error; } void ResourceFile::Unset() { fFile.Unset(); fFileType = FILE_TYPE_UNKNOWN; fHostEndianess = true; fEmptyResources = true; } status_t ResourceFile::InitCheck() const { return fFile.InitCheck(); } status_t ResourceFile::InitContainer(ResourcesContainer& container) { container.MakeEmpty(); status_t error = InitCheck(); if (error == B_OK && !fEmptyResources) { resource_parse_info parseInfo; parseInfo.file_size = 0; parseInfo.resource_count = 0; parseInfo.container = &container; parseInfo.info_table = NULL; parseInfo.info_table_offset = 0; parseInfo.info_table_size = 0; try { // get the file size error = fFile.GetSize(&parseInfo.file_size); if (error != B_OK) throw Exception(error, "Failed to get the file size."); _ReadHeader(parseInfo); _ReadIndex(parseInfo); _ReadInfoTable(parseInfo); container.SetModified(false); } catch (Exception& exception) { if (exception.Error() != B_OK) error = exception.Error(); else error = B_ERROR; } delete[] parseInfo.info_table; } return error; } status_t ResourceFile::ReadResource(ResourceItem& resource, bool force) { status_t error = InitCheck(); size_t size = resource.DataSize(); if (error == B_OK && (force || !resource.IsLoaded())) { void* data = NULL; error = resource.SetSize(size); if (error == B_OK) { data = resource.Data(); ssize_t bytesRead = fFile.ReadAt(resource.Offset(), data, size); if (bytesRead < 0) error = bytesRead; else if ((size_t)bytesRead != size) error = B_IO_ERROR; } if (error == B_OK) { // convert the data, if necessary if (!fHostEndianess) { if (resource.Type() == B_VERSION_INFO_TYPE) { // Version info contains integers that need to be swapped swap_data(B_UINT32_TYPE, data, kVersionInfoIntCount * sizeof(uint32), B_SWAP_ALWAYS); } else swap_data(resource.Type(), data, size, B_SWAP_ALWAYS); } resource.SetLoaded(true); resource.SetModified(false); } } return error; } status_t ResourceFile::ReadResources(ResourcesContainer& container, bool force) { status_t error = InitCheck(); int32 count = container.CountResources(); for (int32 i = 0; error == B_OK && i < count; i++) { if (ResourceItem* resource = container.ResourceAt(i)) error = ReadResource(*resource, force); else error = B_ERROR; } return error; } status_t ResourceFile::WriteResources(ResourcesContainer& container) { status_t error = InitCheck(); if (error == B_OK && !fFile.File()->IsWritable()) error = B_NOT_ALLOWED; if (error == B_OK && fFileType == FILE_TYPE_EMPTY) error = _MakeEmptyResourceFile(); if (error == B_OK) error = _WriteResources(container); if (error == B_OK) fEmptyResources = false; return error; } void ResourceFile::_InitFile(BFile& file, bool clobber) { status_t error = B_OK; fFile.Unset(); // get the file size first off_t fileSize = 0; error = file.GetSize(&fileSize); if (error != B_OK) throw Exception(error, "Failed to get the file size."); // read the first four bytes, and check, if they identify a resource file char magic[4]; if (fileSize >= 4) read_exactly(file, 0, magic, 4, "Failed to read magic number."); else if (fileSize > 0 && !clobber) throw Exception(B_IO_ERROR, "File is not a resource file."); if (fileSize == 0) { // empty file fHostEndianess = true; fFileType = FILE_TYPE_EMPTY; fFile.SetTo(&file, 0); fEmptyResources = true; } else if (!memcmp(magic, kX86ResourceFileMagic, 4)) { // x86 resource file fHostEndianess = B_HOST_IS_LENDIAN; fFileType = FILE_TYPE_X86_RESOURCE; fFile.SetTo(&file, kX86ResourcesOffset); fEmptyResources = false; } else if (!memcmp(magic, kPEFFileMagic1, 4)) { PEFContainerHeader pefHeader; read_exactly(file, 0, &pefHeader, kPEFContainerHeaderSize, "Failed to read PEF container header."); if (!memcmp(pefHeader.tag2, kPPCResourceFileMagic, 4)) { // PPC resource file fHostEndianess = B_HOST_IS_BENDIAN; fFileType = FILE_TYPE_PPC_RESOURCE; fFile.SetTo(&file, kPPCResourcesOffset); fEmptyResources = false; } else if (!memcmp(pefHeader.tag2, kPEFFileMagic2, 4)) { // PEF file fFileType = FILE_TYPE_PEF; _InitPEFFile(file, pefHeader); } else throw Exception(B_IO_ERROR, "File is not a resource file."); } else if (!memcmp(magic, kELFFileMagic, 4)) { // ELF file fFileType = FILE_TYPE_ELF; _InitELFFile(file); } else if (!memcmp(magic, kX86ResourceFileMagic, 2)) { // x86 resource file with screwed magic? // Warnings::AddCurrentWarning("File magic is 0x%08lx. Should be 0x%08lx " // "for x86 resource file. Try anyway.", // ntohl(*(uint32*)magic), // ntohl(*(uint32*)kX86ResourceFileMagic)); fHostEndianess = B_HOST_IS_LENDIAN; fFileType = FILE_TYPE_X86_RESOURCE; fFile.SetTo(&file, kX86ResourcesOffset); fEmptyResources = true; } else { if (clobber) { // make it an x86 resource file fHostEndianess = true; fFileType = FILE_TYPE_EMPTY; fFile.SetTo(&file, 0); } else throw Exception(B_IO_ERROR, "File is not a resource file."); } error = fFile.InitCheck(); if (error != B_OK) throw Exception(error, "Failed to initialize resource file."); // clobber, if desired if (clobber) { // just write an empty resources container ResourcesContainer container; WriteResources(container); } } void ResourceFile::_InitELFFile(BFile& file) { status_t error = B_OK; // get the file size off_t fileSize = 0; error = file.GetSize(&fileSize); if (error != B_OK) throw Exception(error, "Failed to get the file size."); // read the ELF headers e_ident field unsigned char identification[EI_NIDENT]; read_exactly(file, 0, identification, EI_NIDENT, "Failed to read ELF identification."); // check version if (identification[EI_VERSION] != EV_CURRENT) throw Exception(B_UNSUPPORTED, "Unsupported ELF version."); // check data encoding (endianess) switch (identification[EI_DATA]) { case ELFDATA2LSB: fHostEndianess = B_HOST_IS_LENDIAN; break; case ELFDATA2MSB: fHostEndianess = B_HOST_IS_BENDIAN; break; default: case ELFDATANONE: throw Exception(B_UNSUPPORTED, "Unsupported ELF data encoding."); } // check class (32/64 bit) and call the respective method handling it switch (identification[EI_CLASS]) { case ELFCLASS32: _InitELFXFile(file, fileSize); break; case ELFCLASS64: _InitELFXFile(file, fileSize); break; default: throw Exception(B_UNSUPPORTED, "Unsupported ELF class."); } } template void ResourceFile::_InitELFXFile(BFile& file, uint64 fileSize) { // read ELF header ElfHeader fileHeader; read_exactly(file, 0, &fileHeader, sizeof(ElfHeader), "Failed to read ELF header."); // get the header values uint32 headerSize = _GetInt(fileHeader.e_ehsize); uint64 programHeaderTableOffset = _GetInt(fileHeader.e_phoff); uint32 programHeaderSize = _GetInt(fileHeader.e_phentsize); uint32 programHeaderCount = _GetInt(fileHeader.e_phnum); uint64 sectionHeaderTableOffset = _GetInt(fileHeader.e_shoff); uint32 sectionHeaderSize = _GetInt(fileHeader.e_shentsize); uint32 sectionHeaderCount = _GetInt(fileHeader.e_shnum); bool hasProgramHeaderTable = (programHeaderTableOffset != 0); bool hasSectionHeaderTable = (sectionHeaderTableOffset != 0); // check the sanity of the header values // ELF header size if (headerSize < sizeof(ElfHeader) || headerSize > kMaxELFHeaderSize) { throw Exception(B_IO_ERROR, "Invalid ELF header: invalid ELF header size: %" B_PRIu32 ".", headerSize); } uint64 resourceOffset = headerSize; uint64 resourceAlignment = 0; // program header table offset and entry count/size uint64 programHeaderTableSize = 0; if (hasProgramHeaderTable) { if (programHeaderTableOffset < headerSize || programHeaderTableOffset > fileSize) { throw Exception(B_IO_ERROR, "Invalid ELF header: invalid program " "header table offset: %lu.", programHeaderTableOffset); } programHeaderTableSize = (uint64)programHeaderSize * programHeaderCount; if (programHeaderSize < sizeof(ElfProgramHeader) || programHeaderTableOffset + programHeaderTableSize > fileSize) { throw Exception(B_IO_ERROR, "Invalid ELF header: program header " "table exceeds file: %lu.", programHeaderTableOffset + programHeaderTableSize); } resourceOffset = std::max(resourceOffset, programHeaderTableOffset + programHeaderTableSize); // load the program headers into memory uint8* programHeaders = (uint8*)malloc( programHeaderCount * programHeaderSize); if (programHeaders == NULL) throw Exception(B_NO_MEMORY); MemoryDeleter programHeadersDeleter(programHeaders); read_exactly(file, programHeaderTableOffset, programHeaders, programHeaderCount * programHeaderSize, "Failed to read ELF program headers."); // iterate through the program headers for (uint32 i = 0; i < programHeaderCount; i++) { ElfProgramHeader& programHeader = *(ElfProgramHeader*)(programHeaders + i * programHeaderSize); // get the header values uint32 type = _GetInt(programHeader.p_type); uint64 offset = _GetInt(programHeader.p_offset); uint64 size = _GetInt(programHeader.p_filesz); uint64 alignment = _GetInt(programHeader.p_align); // check the values // PT_NULL marks the header unused, if (type != PT_NULL) { if (/*offset < headerSize ||*/ offset > fileSize) { throw Exception(B_IO_ERROR, "Invalid ELF program header: " "invalid program offset: %lu.", offset); } uint64 segmentEnd = offset + size; if (segmentEnd > fileSize) { throw Exception(B_IO_ERROR, "Invalid ELF section header: " "segment exceeds file: %lu.", segmentEnd); } resourceOffset = std::max(resourceOffset, segmentEnd); resourceAlignment = std::max(resourceAlignment, alignment); } } } // section header table offset and entry count/size uint64 sectionHeaderTableSize = 0; if (hasSectionHeaderTable) { if (sectionHeaderTableOffset < headerSize || sectionHeaderTableOffset > fileSize) { throw Exception(B_IO_ERROR, "Invalid ELF header: invalid section " "header table offset: %lu.", sectionHeaderTableOffset); } sectionHeaderTableSize = (uint64)sectionHeaderSize * sectionHeaderCount; if (sectionHeaderSize < sizeof(ElfSectionHeader) || sectionHeaderTableOffset + sectionHeaderTableSize > fileSize) { throw Exception(B_IO_ERROR, "Invalid ELF header: section header " "table exceeds file: %lu.", sectionHeaderTableOffset + sectionHeaderTableSize); } resourceOffset = std::max(resourceOffset, sectionHeaderTableOffset + sectionHeaderTableSize); // load the section headers into memory uint8* sectionHeaders = (uint8*)malloc( sectionHeaderCount * sectionHeaderSize); if (sectionHeaders == NULL) throw Exception(B_NO_MEMORY); MemoryDeleter sectionHeadersDeleter(sectionHeaders); read_exactly(file, sectionHeaderTableOffset, sectionHeaders, sectionHeaderCount * sectionHeaderSize, "Failed to read ELF section headers."); // iterate through the section headers for (uint32 i = 0; i < sectionHeaderCount; i++) { ElfSectionHeader& sectionHeader = *(ElfSectionHeader*)(sectionHeaders + i * sectionHeaderSize); // get the header values uint32 type = _GetInt(sectionHeader.sh_type); uint64 offset = _GetInt(sectionHeader.sh_offset); uint64 size = _GetInt(sectionHeader.sh_size); // check the values // SHT_NULL marks the header unused, // SHT_NOBITS sections take no space in the file if (type != SHT_NULL && type != SHT_NOBITS) { if (offset < headerSize || offset > fileSize) { throw Exception(B_IO_ERROR, "Invalid ELF section header: " "invalid section offset: %lu.", offset); } uint64 sectionEnd = offset + size; if (sectionEnd > fileSize) { throw Exception(B_IO_ERROR, "Invalid ELF section header: " "section exceeds file: %lu.", sectionEnd); } resourceOffset = std::max(resourceOffset, sectionEnd); } } } // align the offset if (fileHeader.e_ident[EI_CLASS] == ELFCLASS64) { // For ELF64 binaries we use a different alignment behaviour. It is // not necessary to align the position of the resources in the file to // the maximum value of p_align, and in fact on x86_64 this behaviour // causes an undesirable effect: since the default segment alignment is // 2MB, aligning to p_align causes all binaries to be at least 2MB when // resources have been added. So, just align to an 8-byte boundary. resourceAlignment = 8; } else { // Retain previous alignment behaviour for compatibility. if (resourceAlignment < kELFMinResourceAlignment) resourceAlignment = kELFMinResourceAlignment; if (resourceAlignment > kELFMaxResourceAlignment) { throw Exception(B_IO_ERROR, "The ELF object file requires an " "invalid alignment: %lu.", resourceAlignment); } } resourceOffset = align_value(resourceOffset, resourceAlignment); if (resourceOffset >= fileSize) { // throw Exception("The ELF object file does not contain resources."); fEmptyResources = true; } else fEmptyResources = false; // fine, init the offset file fFile.SetTo(&file, resourceOffset); } void ResourceFile::_InitPEFFile(BFile& file, const PEFContainerHeader& pefHeader) { status_t error = B_OK; // get the file size off_t fileSize = 0; error = file.GetSize(&fileSize); if (error != B_OK) throw Exception(error, "Failed to get the file size."); // check architecture -- we support PPC only if (memcmp(pefHeader.architecture, kPEFArchitecturePPC, 4) != 0) throw Exception(B_IO_ERROR, "PEF file architecture is not PPC."); fHostEndianess = B_HOST_IS_BENDIAN; // get the section count uint16 sectionCount = _GetInt(pefHeader.sectionCount); // iterate through the PEF sections headers uint32 sectionHeaderTableOffset = kPEFContainerHeaderSize; uint32 sectionHeaderTableEnd = sectionHeaderTableOffset + sectionCount * kPEFSectionHeaderSize; uint32 resourceOffset = sectionHeaderTableEnd; for (int32 i = 0; i < (int32)sectionCount; i++) { uint32 shOffset = sectionHeaderTableOffset + i * kPEFSectionHeaderSize; PEFSectionHeader sectionHeader; read_exactly(file, shOffset, §ionHeader, kPEFSectionHeaderSize, "Failed to read PEF section header."); // get the header values uint32 offset = _GetInt(sectionHeader.containerOffset); uint32 size = _GetInt(sectionHeader.packedSize); // check the values if (offset < sectionHeaderTableEnd || offset > fileSize) { throw Exception(B_IO_ERROR, "Invalid PEF section header: invalid " "section offset: %" B_PRIu32 ".", offset); } uint32 sectionEnd = offset + size; if (sectionEnd > fileSize) { throw Exception(B_IO_ERROR, "Invalid PEF section header: section " "exceeds file: %" B_PRIu32 ".", sectionEnd); } resourceOffset = std::max(resourceOffset, sectionEnd); } if (resourceOffset >= fileSize) { // throw Exception("The PEF object file does not contain resources."); fEmptyResources = true; } else fEmptyResources = false; // init the offset file fFile.SetTo(&file, resourceOffset); } void ResourceFile::_ReadHeader(resource_parse_info& parseInfo) { // read the header resources_header header; read_exactly(fFile, 0, &header, kResourcesHeaderSize, "Failed to read the header."); // check the header // magic uint32 magic = _GetInt(header.rh_resources_magic); if (magic == kResourcesHeaderMagic) { // everything is fine } else if (B_SWAP_INT32(magic) == kResourcesHeaderMagic) { // const char* endianessStr[2] = { "little", "big" }; // int32 endianess // = (fHostEndianess == ((bool)B_HOST_IS_LENDIAN ? 0 : 1)); // Warnings::AddCurrentWarning("Endianess seems to be %s, although %s " // "was expected.", // endianessStr[1 - endianess], // endianessStr[endianess]); fHostEndianess = !fHostEndianess; } else throw Exception(B_IO_ERROR, "Invalid resources header magic."); // resource count uint32 resourceCount = _GetInt(header.rh_resource_count); if (resourceCount > kMaxResourceCount) throw Exception(B_IO_ERROR, "Bad number of resources."); // index section offset uint32 indexSectionOffset = _GetInt(header.rh_index_section_offset); if (indexSectionOffset != kResourceIndexSectionOffset) { throw Exception(B_IO_ERROR, "Unexpected resource index section " "offset. Is: %" B_PRIu32 ", should be: %" B_PRIu32 ".", indexSectionOffset, kResourceIndexSectionOffset); } // admin section size uint32 indexSectionSize = kResourceIndexSectionHeaderSize + kResourceIndexEntrySize * resourceCount; indexSectionSize = align_value(indexSectionSize, kResourceIndexSectionAlignment); uint32 adminSectionSize = _GetInt(header.rh_admin_section_size); if (adminSectionSize != indexSectionOffset + indexSectionSize) { throw Exception(B_IO_ERROR, "Unexpected resource admin section size. " "Is: %" B_PRIu32 ", should be: %" B_PRIu32 ".", adminSectionSize, indexSectionOffset + indexSectionSize); } // set the resource count parseInfo.resource_count = resourceCount; } void ResourceFile::_ReadIndex(resource_parse_info& parseInfo) { int32& resourceCount = parseInfo.resource_count; off_t& fileSize = parseInfo.file_size; BBufferIO buffer(&fFile, 2048, false); // read the header resource_index_section_header header; read_exactly(buffer, kResourceIndexSectionOffset, &header, kResourceIndexSectionHeaderSize, "Failed to read the resource index section header."); // check the header // index section offset uint32 indexSectionOffset = _GetInt(header.rish_index_section_offset); if (indexSectionOffset != kResourceIndexSectionOffset) { throw Exception(B_IO_ERROR, "Unexpected resource index section " "offset. Is: %" B_PRIu32 ", should be: %" B_PRIu32 ".", indexSectionOffset, kResourceIndexSectionOffset); } // index section size uint32 expectedIndexSectionSize = kResourceIndexSectionHeaderSize + kResourceIndexEntrySize * resourceCount; expectedIndexSectionSize = align_value(expectedIndexSectionSize, kResourceIndexSectionAlignment); uint32 indexSectionSize = _GetInt(header.rish_index_section_size); if (indexSectionSize != expectedIndexSectionSize) { throw Exception(B_IO_ERROR, "Unexpected resource index section size. " "Is: %" B_PRIu32 ", should be: %" B_PRIu32 ".", indexSectionSize, expectedIndexSectionSize); } // unknown section offset uint32 unknownSectionOffset = _GetInt(header.rish_unknown_section_offset); if (unknownSectionOffset != indexSectionOffset + indexSectionSize) { throw Exception(B_IO_ERROR, "Unexpected resource index section size. " "Is: %" B_PRIu32 ", should be: %" B_PRIu32 ".", unknownSectionOffset, indexSectionOffset + indexSectionSize); } // unknown section size uint32 unknownSectionSize = _GetInt(header.rish_unknown_section_size); if (unknownSectionSize != kUnknownResourceSectionSize) { throw Exception(B_IO_ERROR, "Unexpected resource index section " "offset. Is: %" B_PRIu32 ", should be: %" B_PRIu32 ".", unknownSectionOffset, kUnknownResourceSectionSize); } // info table offset and size uint32 infoTableOffset = _GetInt(header.rish_info_table_offset); uint32 infoTableSize = _GetInt(header.rish_info_table_size); if (infoTableOffset + infoTableSize > fileSize) throw Exception(B_IO_ERROR, "Invalid info table location."); parseInfo.info_table_offset = infoTableOffset; parseInfo.info_table_size = infoTableSize; // read the index entries uint32 indexTableOffset = indexSectionOffset + kResourceIndexSectionHeaderSize; int32 maxResourceCount = (unknownSectionOffset - indexTableOffset) / kResourceIndexEntrySize; int32 actualResourceCount = 0; bool tableEndReached = false; for (int32 i = 0; !tableEndReached && i < maxResourceCount; i++) { // read one entry tableEndReached = !_ReadIndexEntry(buffer, parseInfo, i, indexTableOffset, (i >= resourceCount)); if (!tableEndReached) actualResourceCount++; } // check resource count if (actualResourceCount != resourceCount) { if (actualResourceCount > resourceCount) { // Warnings::AddCurrentWarning("Resource index table contains " // "%ld entries, although it should be " // "%ld only.", actualResourceCount, // resourceCount); } resourceCount = actualResourceCount; } } bool ResourceFile::_ReadIndexEntry(BPositionIO& buffer, resource_parse_info& parseInfo, int32 index, uint32 tableOffset, bool peekAhead) { off_t& fileSize = parseInfo.file_size; bool result = true; resource_index_entry entry; // read one entry off_t entryOffset = tableOffset + index * kResourceIndexEntrySize; read_exactly(buffer, entryOffset, &entry, kResourceIndexEntrySize, "Failed to read a resource index entry."); // check, if the end is reached early if (result && check_pattern(entryOffset, &entry, kResourceIndexEntrySize / 4, fHostEndianess)) { result = false; } uint32 offset = _GetInt(entry.rie_offset); uint32 size = _GetInt(entry.rie_size); // check the location if (result && offset + size > fileSize) { if (!peekAhead) { throw Exception(B_IO_ERROR, "Invalid resource index entry: index: " "%" B_PRId32 ", offset: %" B_PRIu32 " (%" B_PRIx32 "), " "size: %" B_PRIu32 " (%" B_PRIx32 ").", index + 1, offset, offset, size, size); } result = false; } // add the entry if (result) { ResourceItem* item = new(std::nothrow) ResourceItem; if (!item) throw Exception(B_NO_MEMORY); item->SetLocation(offset, size); if (!parseInfo.container->AddResource(item, index, false)) { delete item; throw Exception(B_NO_MEMORY); } } return result; } void ResourceFile::_ReadInfoTable(resource_parse_info& parseInfo) { int32& resourceCount = parseInfo.resource_count; // read the info table // alloc memory for the table char* tableData = new(std::nothrow) char[parseInfo.info_table_size]; if (!tableData) throw Exception(B_NO_MEMORY); int32 dataSize = parseInfo.info_table_size; parseInfo.info_table = tableData; // freed by the info owner read_exactly(fFile, parseInfo.info_table_offset, tableData, dataSize, "Failed to read resource info table."); // bool* readIndices = new(std::nothrow) bool[resourceCount + 1]; // + 1 => always > 0 if (!readIndices) throw Exception(B_NO_MEMORY); ArrayDeleter readIndicesDeleter(readIndices); for (int32 i = 0; i < resourceCount; i++) readIndices[i] = false; MemArea area(tableData, dataSize); const void* data = tableData; // check the table end/check sum if (_ReadInfoTableEnd(data, dataSize)) dataSize -= kResourceInfoTableEndSize; // read the infos int32 resourceIndex = 1; uint32 minRemainderSize = kMinResourceInfoBlockSize + kResourceInfoSeparatorSize; while (area.check(data, minRemainderSize)) { // read a resource block if (!area.check(data, kMinResourceInfoBlockSize)) { throw Exception(B_IO_ERROR, "Unexpected end of resource info " "table at index %" B_PRId32 ".", resourceIndex); } const resource_info_block* infoBlock = (const resource_info_block*)data; type_code type = _GetInt(infoBlock->rib_type); // read the infos of this block const resource_info* info = infoBlock->rib_info; while (info) { data = _ReadResourceInfo(parseInfo, area, info, type, readIndices); // prepare for next iteration, if there is another info if (!area.check(data, kResourceInfoSeparatorSize)) { throw Exception(B_IO_ERROR, "Unexpected end of resource info " "table after index %" B_PRId32 ".", resourceIndex); } const resource_info_separator* separator = (const resource_info_separator*)data; if (_GetInt(separator->ris_value1) == 0xffffffff && _GetInt(separator->ris_value2) == 0xffffffff) { // info block ends info = NULL; data = skip_bytes(data, kResourceInfoSeparatorSize); } else { // another info follows info = (const resource_info*)data; } resourceIndex++; } // end of the info block } // handle special case: empty resource info table if (resourceIndex == 1) { if (!area.check(data, kResourceInfoSeparatorSize)) { throw Exception(B_IO_ERROR, "Unexpected end of resource info " "table."); } const resource_info_separator* tableTerminator = (const resource_info_separator*)data; if (_GetInt(tableTerminator->ris_value1) != 0xffffffff || _GetInt(tableTerminator->ris_value2) != 0xffffffff) { throw Exception(B_IO_ERROR, "The resource info table ought to be " "empty, but is not properly terminated."); } data = skip_bytes(data, kResourceInfoSeparatorSize); } // Check, if the correct number of bytes are remaining. uint32 bytesLeft = (const char*)tableData + dataSize - (const char*)data; if (bytesLeft != 0) { throw Exception(B_IO_ERROR, "Error at the end of the resource info " "table: %" B_PRIu32 " bytes are remaining.", bytesLeft); } // check, if all items have been initialized for (int32 i = resourceCount - 1; i >= 0; i--) { if (!readIndices[i]) { // Warnings::AddCurrentWarning("Resource item at index %ld " // "has no info. Item removed.", i + 1); if (ResourceItem* item = parseInfo.container->RemoveResource(i)) delete item; resourceCount--; } } } bool ResourceFile::_ReadInfoTableEnd(const void* data, int32 dataSize) { bool hasTableEnd = true; if ((uint32)dataSize < kResourceInfoSeparatorSize) throw Exception(B_IO_ERROR, "Info table is too short."); if ((uint32)dataSize < kResourceInfoTableEndSize) hasTableEnd = false; if (hasTableEnd) { const resource_info_table_end* tableEnd = (const resource_info_table_end*) skip_bytes(data, dataSize - kResourceInfoTableEndSize); if (_GetInt(tableEnd->rite_terminator) != 0) hasTableEnd = false; if (hasTableEnd) { dataSize -= kResourceInfoTableEndSize; // checksum uint32 checkSum = calculate_checksum(data, dataSize); uint32 fileCheckSum = _GetInt(tableEnd->rite_check_sum); if (checkSum != fileCheckSum) { throw Exception(B_IO_ERROR, "Invalid resource info table check" " sum: In file: %" B_PRIx32 ", calculated: %" B_PRIx32 ".", fileCheckSum, checkSum); } } } // if (!hasTableEnd) // Warnings::AddCurrentWarning("resource info table has no check sum."); return hasTableEnd; } const void* ResourceFile::_ReadResourceInfo(resource_parse_info& parseInfo, const MemArea& area, const resource_info* info, type_code type, bool* readIndices) { int32& resourceCount = parseInfo.resource_count; int32 id = _GetInt(info->ri_id); int32 index = _GetInt(info->ri_index); uint16 nameSize = _GetInt(info->ri_name_size); const char* name = info->ri_name; // check the values bool ignore = false; // index if (index < 1 || index > resourceCount) { // Warnings::AddCurrentWarning("Invalid index field in resource " // "info table: %lu.", index); ignore = true; } if (!ignore) { if (readIndices[index - 1]) { throw Exception(B_IO_ERROR, "Multiple resource infos with the " "same index field: %" B_PRId32 ".", index); } readIndices[index - 1] = true; } // name size if (!area.check(name, nameSize)) { throw Exception(B_IO_ERROR, "Invalid name size (%" B_PRIu16 ") " "for index %" B_PRId32 " in resource info table.", nameSize, index); } // check, if name is null terminated if (name[nameSize - 1] != 0) { // Warnings::AddCurrentWarning("Name for index %ld in " // "resource info table is not null " // "terminated.", index); } // set the values if (!ignore) { BString resourceName(name, nameSize); if (ResourceItem* item = parseInfo.container->ResourceAt(index - 1)) item->SetIdentity(type, id, resourceName.String()); else { throw Exception(B_IO_ERROR, "Unexpected error: No resource item " "at index %" B_PRId32 ".", index); } } return skip_bytes(name, nameSize); } status_t ResourceFile::_WriteResources(ResourcesContainer& container) { status_t error = B_OK; int32 resourceCount = container.CountResources(); char* buffer = NULL; try { // calculate sizes and offsets // header uint32 size = kResourcesHeaderSize; size_t bufferSize = size; // index section uint32 indexSectionOffset = size; uint32 indexSectionSize = kResourceIndexSectionHeaderSize + resourceCount * kResourceIndexEntrySize; indexSectionSize = align_value(indexSectionSize, kResourceIndexSectionAlignment); size += indexSectionSize; bufferSize = std::max((uint32)bufferSize, indexSectionSize); // unknown section uint32 unknownSectionOffset = size; uint32 unknownSectionSize = kUnknownResourceSectionSize; size += unknownSectionSize; bufferSize = std::max((uint32)bufferSize, unknownSectionSize); // data uint32 dataOffset = size; uint32 dataSize = 0; for (int32 i = 0; i < resourceCount; i++) { ResourceItem* item = container.ResourceAt(i); if (!item->IsLoaded()) throw Exception(B_IO_ERROR, "Resource is not loaded."); dataSize += item->DataSize(); bufferSize = std::max(bufferSize, item->DataSize()); } size += dataSize; // info table uint32 infoTableOffset = size; uint32 infoTableSize = 0; type_code type = 0; for (int32 i = 0; i < resourceCount; i++) { ResourceItem* item = container.ResourceAt(i); if (i == 0 || type != item->Type()) { if (i != 0) infoTableSize += kResourceInfoSeparatorSize; type = item->Type(); infoTableSize += kMinResourceInfoBlockSize; } else infoTableSize += kMinResourceInfoSize; const char* name = item->Name(); if (name && name[0] != '\0') infoTableSize += strlen(name) + 1; } infoTableSize += kResourceInfoSeparatorSize + kResourceInfoTableEndSize; size += infoTableSize; bufferSize = std::max((uint32)bufferSize, infoTableSize); // write... // set the file size fFile.SetSize(size); buffer = new(std::nothrow) char[bufferSize]; if (!buffer) throw Exception(B_NO_MEMORY); void* data = buffer; // header resources_header* resourcesHeader = (resources_header*)data; resourcesHeader->rh_resources_magic = kResourcesHeaderMagic; resourcesHeader->rh_resource_count = resourceCount; resourcesHeader->rh_index_section_offset = indexSectionOffset; resourcesHeader->rh_admin_section_size = indexSectionOffset + indexSectionSize; for (int32 i = 0; i < 13; i++) resourcesHeader->rh_pad[i] = 0; write_exactly(fFile, 0, buffer, kResourcesHeaderSize, "Failed to write resources header."); // index section data = buffer; // header resource_index_section_header* indexHeader = (resource_index_section_header*)data; indexHeader->rish_index_section_offset = indexSectionOffset; indexHeader->rish_index_section_size = indexSectionSize; indexHeader->rish_unknown_section_offset = unknownSectionOffset; indexHeader->rish_unknown_section_size = unknownSectionSize; indexHeader->rish_info_table_offset = infoTableOffset; indexHeader->rish_info_table_size = infoTableSize; fill_pattern(buffer - indexSectionOffset, &indexHeader->rish_unused_data1, 1); fill_pattern(buffer - indexSectionOffset, indexHeader->rish_unused_data2, 25); fill_pattern(buffer - indexSectionOffset, &indexHeader->rish_unused_data3, 1); // index table data = skip_bytes(data, kResourceIndexSectionHeaderSize); resource_index_entry* entry = (resource_index_entry*)data; uint32 entryOffset = dataOffset; for (int32 i = 0; i < resourceCount; i++, entry++) { ResourceItem* item = container.ResourceAt(i); uint32 entrySize = item->DataSize(); entry->rie_offset = entryOffset; entry->rie_size = entrySize; entry->rie_pad = 0; entryOffset += entrySize; } fill_pattern(buffer - indexSectionOffset, entry, buffer + indexSectionSize); write_exactly(fFile, indexSectionOffset, buffer, indexSectionSize, "Failed to write index section."); // unknown section fill_pattern(unknownSectionOffset, buffer, unknownSectionSize / 4); write_exactly(fFile, unknownSectionOffset, buffer, unknownSectionSize, "Failed to write unknown section."); // data uint32 itemOffset = dataOffset; for (int32 i = 0; i < resourceCount; i++) { data = buffer; ResourceItem* item = container.ResourceAt(i); const void* itemData = item->Data(); uint32 itemSize = item->DataSize(); if (!itemData && itemSize > 0) throw Exception(error, "Invalid resource item data."); if (itemData) { // swap data, if necessary if (!fHostEndianess) { memcpy(data, itemData, itemSize); if (item->Type() == B_VERSION_INFO_TYPE) { // Version info contains integers // that need to be swapped swap_data(B_UINT32_TYPE, data, kVersionInfoIntCount * sizeof(uint32), B_SWAP_ALWAYS); } else swap_data(item->Type(), data, itemSize, B_SWAP_ALWAYS); itemData = data; } write_exactly(fFile, itemOffset, itemData, itemSize, "Failed to write resource item data."); } item->SetOffset(itemOffset); itemOffset += itemSize; } // info table data = buffer; type = 0; for (int32 i = 0; i < resourceCount; i++) { ResourceItem* item = container.ResourceAt(i); resource_info* info = NULL; if (i == 0 || type != item->Type()) { if (i != 0) { resource_info_separator* separator = (resource_info_separator*)data; separator->ris_value1 = 0xffffffff; separator->ris_value2 = 0xffffffff; data = skip_bytes(data, kResourceInfoSeparatorSize); } type = item->Type(); resource_info_block* infoBlock = (resource_info_block*)data; infoBlock->rib_type = type; info = infoBlock->rib_info; } else info = (resource_info*)data; // info info->ri_id = item->ID(); info->ri_index = i + 1; info->ri_name_size = 0; data = info->ri_name; const char* name = item->Name(); if (name && name[0] != '\0') { uint32 nameLen = strlen(name); memcpy(info->ri_name, name, nameLen + 1); data = skip_bytes(data, nameLen + 1); info->ri_name_size = nameLen + 1; } } // separator resource_info_separator* separator = (resource_info_separator*)data; separator->ris_value1 = 0xffffffff; separator->ris_value2 = 0xffffffff; // table end data = skip_bytes(data, kResourceInfoSeparatorSize); resource_info_table_end* tableEnd = (resource_info_table_end*)data; tableEnd->rite_check_sum = calculate_checksum(buffer, infoTableSize - kResourceInfoTableEndSize); tableEnd->rite_terminator = 0; write_exactly(fFile, infoTableOffset, buffer, infoTableSize, "Failed to write info table."); } catch (Exception& exception) { if (exception.Error() != B_OK) error = exception.Error(); else error = B_ERROR; } delete[] buffer; return error; } status_t ResourceFile::_MakeEmptyResourceFile() { status_t error = fFile.InitCheck(); if (error == B_OK && !fFile.File()->IsWritable()) error = B_NOT_ALLOWED; if (error == B_OK) { try { BFile* file = fFile.File(); // make it an x86 resource file error = file->SetSize(4); if (error != B_OK) throw Exception(error, "Failed to set file size."); write_exactly(*file, 0, kX86ResourceFileMagic, 4, "Failed to write magic number."); fHostEndianess = B_HOST_IS_LENDIAN; fFileType = FILE_TYPE_X86_RESOURCE; fFile.SetTo(file, kX86ResourcesOffset); fEmptyResources = true; } catch (Exception& exception) { if (exception.Error() != B_OK) error = exception.Error(); else error = B_ERROR; } } return error; } }; // namespace Storage }; // namespace BPrivate