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.