git.y1.nz

SameBoy

Accurate GB/GBC emulator
download: https://git.y1.nz/archives/sameboy.tar.gz
README | Files | Log | Refs | LICENSE

iOS/GBZipReader.m

      1 #import "GBZipReader.h"
      2 #import <compression.h>
      3 #import <sys/mman.h>
      4 #import <mach/vm_region.h>
      5 #pragma clang diagnostic ignored "-Wimplicit-retain-self"
      6 
      7 @implementation GBZipReader
      8 {
      9     void (^_freeCallback)(void);
     10     const void *_buffer;
     11     size_t _size;
     12     
     13     const struct __attribute__((packed)) {
     14         uint32_t magic;
     15         uint8_t skip[6];
     16         uint16_t fileCount;
     17         uint32_t cdSize;
     18         uint32_t cdOffset;
     19         uint16_t commentSize;
     20     } *_eocd;
     21 }
     22 
     23 - (instancetype)initWithBuffer:(const void *)buffer size:(size_t)size free:(void (^)(void))callback
     24 {
     25     self = [super init];
     26     if (!self) return nil;
     27     
     28     _buffer = buffer;
     29     _size = size;
     30     _freeCallback = callback;
     31     
     32     if (_size < sizeof(*_eocd)) return nil;
     33     
     34     for (unsigned i = 0; i < 0x10000; i++) {
     35         _eocd = (void *)((uint8_t *)buffer + size - sizeof(*_eocd) - i);
     36         if ((void *)_eocd < buffer) return nil;
     37         if (_eocd->magic == htonl('PK\05\06')) {
     38             break;
     39         }
     40     }
     41     
     42     return self;
     43 }
     44 
     45 - (instancetype)initWithPath:(NSString *)path
     46 {
     47     int fd = open(path.UTF8String, O_RDONLY);
     48     if (fd < 0) return nil;
     49     size_t size = lseek(fd, 0, SEEK_END);
     50     size_t alignedSize = (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
     51     void *mapping = mmap(NULL, alignedSize, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0);
     52     close(fd);
     53     if (!mapping) return nil;
     54     
     55     return [self initWithBuffer:mapping size:size free:^{
     56         munmap(mapping, alignedSize);
     57     }];
     58 }
     59 
     60 - (void)iterateFiles:(bool (^)(NSString *filename, size_t uncompressedSize, bool (^getData)(void *), bool (^writeToPath)(NSString *)))callback {
     61     const struct __attribute__((packed)) {
     62         uint32_t magic;
     63         uint8_t skip[6];
     64         uint16_t compressionMethod;
     65         uint8_t skip2[8];
     66         uint32_t compressedSize;
     67         uint32_t uncompressedSize;
     68         uint16_t nameLength;
     69         uint16_t extraLength;
     70         uint16_t commentLength;
     71         uint8_t skip3[8];
     72         uint32_t localHeaderOffset;
     73         char name[0];
     74     } *entry = (void *)((uint8_t *)_buffer + _eocd->cdOffset);
     75     for (unsigned i = _eocd->fileCount; i--;) {
     76         if ((uint8_t *)entry + sizeof(*entry) - (uint8_t *)_buffer >= _size) return;
     77         if (entry->magic != htonl('PK\01\02')) return;
     78         
     79         typeof(entry) next = (void *)((uint8_t *)entry + sizeof(*entry) +
     80                                       entry->nameLength + entry->extraLength + entry->commentLength);
     81         if ((uint8_t *)next - (uint8_t *)_buffer >= _size) return;
     82         
     83         
     84         bool (^getData)(void *) = ^bool(void *output) {
     85             // Directory, no data
     86             if (entry->name[entry->nameLength - 1] == '/') return false;
     87             
     88             if (entry->uncompressedSize == 0xffffffff || entry->compressedSize == 0xffffffff) {
     89                 // ZIP64
     90                 return false;
     91             }
     92             
     93             const struct __attribute__((packed)) {
     94                 uint32_t magic;
     95                 uint8_t skip[4];
     96                 uint16_t compressionMethod;
     97                 uint8_t skip2[8];
     98                 uint32_t compressedSize;
     99                 uint32_t uncompressedSize;
    100                 uint16_t nameLength;
    101                 uint16_t extraLength;
    102                 char name[0];
    103             } *localEntry = (void *)((uint8_t *)_buffer + entry->localHeaderOffset);
    104             
    105             if ((uint8_t *)localEntry + sizeof(*localEntry) - (uint8_t *)_buffer >= _size) return nil;
    106             if ((uint8_t *)localEntry + sizeof(*localEntry) +
    107                 localEntry->nameLength + localEntry->extraLength +
    108                 entry->compressedSize - (uint8_t *)_buffer >= _size) {
    109                 return false;
    110             }
    111             
    112             if (localEntry->magic != htonl('PK\03\04')) return nil;
    113             if (entry->uncompressedSize != localEntry->uncompressedSize) return nil;
    114             
    115             const void *dataStart = &localEntry->name[localEntry->nameLength + localEntry->extraLength];
    116             if (localEntry->compressionMethod == 0) {
    117                 if (localEntry->uncompressedSize != entry->compressedSize) return false;
    118                 memcpy(output, dataStart, localEntry->uncompressedSize);
    119                 return true;
    120             }
    121             else if (localEntry->compressionMethod != 8) {
    122                 // Unsupported compression
    123                 return false;
    124             }
    125             if (compression_decode_buffer(output, localEntry->uncompressedSize,
    126                                           dataStart, entry->compressedSize,
    127                                           NULL, COMPRESSION_ZLIB) != localEntry->uncompressedSize) {
    128                 return false;
    129             }
    130             return true;
    131         };
    132         
    133         bool (^writeToPath)(NSString *) = ^bool(NSString *path) {
    134             int fd = open(path.UTF8String, O_CREAT | O_RDWR, 0644);
    135             if (!fd) return false;
    136             if (ftruncate(fd, entry->uncompressedSize) != 0) {
    137                 close(fd);
    138                 unlink(path.UTF8String);
    139                 return false;
    140             }
    141             size_t alignedSize = (entry->uncompressedSize + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
    142             void *mapping = mmap(NULL, alignedSize, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, 0);
    143             close(fd);
    144             if (!mapping) {
    145                 unlink(path.UTF8String);
    146                 return false;
    147             }
    148             
    149             bool ret = getData(mapping);
    150             if (!ret) {
    151                 unlink(path.UTF8String);
    152             }            
    153             munmap(mapping, alignedSize);
    154 
    155             return ret;
    156         };
    157 
    158         
    159         if (!callback([[NSString alloc] initWithBytes:entry->name
    160                                                length:entry->nameLength
    161                                              encoding:NSUTF8StringEncoding],
    162                       entry->uncompressedSize,
    163                       getData,
    164                       writeToPath)) {
    165             return;
    166         }
    167         
    168         entry = next;
    169     }
    170 }
    171 
    172 - (void)dealloc
    173 {
    174     if (_freeCallback) {
    175         _freeCallback();
    176     }
    177 }
    178 
    179 @end

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.