Rev 7867 | Rev 7869 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
7843 | Boppan | 1 | #include |
2 | #include |
||
3 | #include |
||
4 | #include |
||
5 | #include |
||
6 | |||
7 | typedef struct { |
||
8 | size_t length; |
||
9 | size_t capacity; |
||
10 | char *data; |
||
11 | } String; |
||
12 | |||
13 | typedef struct { |
||
14 | char *image; |
||
15 | int imageSize; |
||
16 | const char *errorMessage; |
||
17 | int bytesPerSector; |
||
18 | int sectorsPerClaster; |
||
19 | int reservedSectorCount; |
||
20 | int numberOfFats; |
||
21 | int maxRootEntries; |
||
22 | int totalSectors; |
||
23 | int sectorsPerFat; |
||
24 | int firstFat; |
||
25 | int rootDirectory; |
||
26 | int dataRegion; |
||
27 | } Fat12; |
||
28 | |||
29 | typedef int (*ForEachCallback)(const char *, size_t, const uint8_t *, void *); |
||
30 | |||
31 | // system-dependent |
||
32 | static void mkdir(const char *name); |
||
33 | // misc |
||
34 | static void mkdir_p(const char *_name); // create folder creating its parents |
||
35 | static uint16_t get16(const void *_from, int index); // get uint16_t from array at offset |
||
36 | static uint32_t get32(const void *_from, int index); // get uint32_t from array at offset |
||
37 | // fat12 |
||
38 | static int fat12__getItemNameSize(const void *_folderEntry); |
||
39 | static void fat12__getItemName(const void *_folderEntry, void *_name); |
||
40 | static int fat12__getNextClaster(const Fat12 *this, int currentClaster); |
||
41 | static int fat12__getFile(const Fat12 *this, void *_buffer, int size, int claster); |
||
42 | static int fat12__getOffsetByClaster(const Fat12 *this, int claster); |
||
43 | static int fat12__forEachFile_handleFolderEntry(const Fat12 *this, int folderEntryOffset, String *name, |
||
44 | ForEachCallback callback, void *callbackParam); |
||
45 | static int fat12__forEachFile_handleFolder(const Fat12 *this, int claster, String *name, |
||
46 | ForEachCallback callback, void *callbackParam); |
||
47 | static int fat12__forEachFile(const Fat12 *this, ForEachCallback callback, void *callbackParam); |
||
48 | static int fat12__open(Fat12 *this, const char *img); |
||
49 | static int fat12__error(Fat12 *this, const char *errorMessage); |
||
50 | |||
51 | static void mkdir(const char *name) { |
||
52 | struct { |
||
53 | int fn; |
||
54 | int unused[4]; |
||
55 | char b; |
||
7868 | Boppan | 56 | const char *path; |
57 | } __attribute__((packed)) info; |
||
7843 | Boppan | 58 | memset(&info, 0, sizeof(info)); |
59 | info.fn = 9; |
||
60 | info.b = 0; |
||
61 | info.path = name; |
||
62 | asm volatile ("int $0x40"::"a"(70), "b"(&info)); |
||
63 | } |
||
64 | |||
65 | static void mkdir_p(const char *_name) { |
||
66 | char *name = calloc(strlen(_name) + 1, 1); |
||
67 | |||
68 | strcpy(name, _name); |
||
69 | char *ptr = name; |
||
70 | while (ptr) { |
||
71 | if (ptr != name) { *ptr = '/'; } |
||
72 | ptr = strchr(ptr + 1, '/'); |
||
73 | if (ptr) { *ptr = 0; } |
||
74 | mkdir(name); |
||
75 | } |
||
76 | } |
||
77 | |||
78 | static uint32_t get32(const void *_from, int index) { |
||
79 | const uint8_t *from = _from; |
||
80 | return from[index] | |
||
81 | (from[index + 1] << 8) | |
||
82 | (from[index + 2] << 16) | |
||
83 | (from[index + 3] << 24); |
||
84 | } |
||
85 | |||
86 | static uint16_t get16(const void *_from, int index) { |
||
87 | const uint8_t *from = _from; |
||
88 | |||
89 | return from[index] | (from[index + 1] << 8); |
||
90 | } |
||
91 | |||
92 | static int fat12__getNextClaster(const Fat12 *this, int currentClaster) { |
||
93 | int nextClasterOffset = this->firstFat + currentClaster + (currentClaster >> 1); |
||
94 | |||
95 | if (currentClaster % 2 == 0) { |
||
96 | return get16(this->image, nextClasterOffset) & 0xfff; |
||
97 | } else { |
||
98 | return get16(this->image, nextClasterOffset) >> 4; |
||
99 | } |
||
100 | } |
||
101 | |||
102 | static int fat12__getFile(const Fat12 *this, void *_buffer, int size, int claster) { |
||
103 | int offset = 0; |
||
104 | char *buffer = _buffer; |
||
105 | |||
106 | while (claster < 0xff7) { |
||
107 | int toCopy = this->bytesPerSector * this->sectorsPerClaster; |
||
108 | void *clasterPtr = &this->image[fat12__getOffsetByClaster(this, claster)]; |
||
109 | |||
110 | claster = fat12__getNextClaster(this, claster); |
||
111 | // if next claster is END OF FILE claster, copy only rest of file |
||
112 | if (claster >= 0xff7) { toCopy = size % toCopy; } |
||
113 | memcpy(&buffer[offset], clasterPtr, toCopy); |
||
114 | offset += toCopy; |
||
115 | } |
||
116 | return 1; |
||
117 | } |
||
118 | |||
119 | static int fat12__getOffsetByClaster(const Fat12 *this, int claster) { |
||
120 | return this->dataRegion + (claster - 2) |
||
121 | * this->bytesPerSector * this->sectorsPerClaster; |
||
122 | } |
||
123 | |||
124 | static int fat12__getItemNameSize(const void *_folderEntry) { |
||
125 | const uint8_t *folderEntry = _folderEntry; |
||
126 | |||
127 | // Long File Name entry, not a file itself |
||
128 | if ((folderEntry[11] & 0x0f) == 0x0f) { return 0; } |
||
129 | if ((folderEntry[11 - 32] & 0x0f) != 0x0f) { |
||
130 | // regular file "NAME8888" '.' "EXT" '\0' |
||
131 | int length = 13; |
||
132 | |||
133 | for (int i = 10; folderEntry[i] == ' ' && i != 7; i--) { length--; } |
||
134 | for (int i = 7; folderEntry[i] == ' ' && i != 0 - 1; i--) { length--; } |
||
135 | if (folderEntry[8] == ' ') { length--; } // no ext - no'.' |
||
136 | return length; |
||
137 | } else { |
||
138 | // file with long name |
||
139 | // format of Long File Name etries is described in fat12__getItemName |
||
140 | int length = 1; |
||
141 | |||
142 | for (int i = 1; i < 255 / 13; i++) { |
||
143 | //! TODO: Add UTF-16 support |
||
144 | length += 13; |
||
145 | if (folderEntry[i * -32] & 0x40) { |
||
146 | // if first char from back is 0xffff, this is stub after name |
||
147 | // otherwice is last character, so we can return calculated length |
||
148 | if (get16(folderEntry, i * -32 + 30) == 0xffff) { length--; } else { return length; } |
||
149 | if (get16(folderEntry, i * -32 + 28) == 0xffff) { length--; } else { return length; } |
||
150 | if (get16(folderEntry, i * -32 + 24) == 0xffff) { length--; } else { return length; } |
||
151 | if (get16(folderEntry, i * -32 + 22) == 0xffff) { length--; } else { return length; } |
||
152 | if (get16(folderEntry, i * -32 + 20) == 0xffff) { length--; } else { return length; } |
||
153 | if (get16(folderEntry, i * -32 + 18) == 0xffff) { length--; } else { return length; } |
||
154 | if (get16(folderEntry, i * -32 + 16) == 0xffff) { length--; } else { return length; } |
||
155 | if (get16(folderEntry, i * -32 + 14) == 0xffff) { length--; } else { return length; } |
||
156 | if (get16(folderEntry, i * -32 + 9) == 0xffff) { length--; } else { return length; } |
||
157 | if (get16(folderEntry, i * -32 + 7) == 0xffff) { length--; } else { return length; } |
||
158 | if (get16(folderEntry, i * -32 + 5) == 0xffff) { length--; } else { return length; } |
||
159 | if (get16(folderEntry, i * -32 + 3) == 0xffff) { length--; } else { return length; } |
||
160 | if (get16(folderEntry, i * -32 + 1) == 0xffff) { length--; } else { return length; } |
||
161 | return length; |
||
162 | } |
||
163 | } |
||
164 | } |
||
165 | return 0; // WAT? |
||
166 | } |
||
167 | |||
168 | static void fat12__getItemName(const void *_folderEntry, void *_name) { |
||
169 | const uint8_t *folderEntry = _folderEntry; |
||
170 | uint8_t *name = _name; |
||
171 | |||
172 | if ((folderEntry[11 - 32] & 0x0f) != 0x0f) { |
||
173 | int length = 8; |
||
174 | |||
175 | memset(name, 0, 13); |
||
176 | memcpy(name, folderEntry, 8); |
||
177 | while (name[length - 1] == ' ') { length--; } |
||
178 | if (folderEntry[9] != ' ') { |
||
179 | name[length++] = '.'; |
||
180 | memcpy(&name[length], &folderEntry[8], 3); |
||
181 | length += 3; |
||
182 | } |
||
183 | while (name[length - 1] == ' ') { length--; } |
||
184 | name[length] = '\0'; |
||
185 | } else { |
||
186 | // previous folder entries hold long name in format: |
||
187 | // 0 sequence nmber (in turn back to first Long File Name entry, from 1) |
||
188 | // 1 - 10 file name next characters in utf-16 |
||
189 | // 11 file attributes (0x0f - LFN entry) |
||
190 | // 12 reserved |
||
191 | // 13 checksum |
||
192 | // 14 - 25 file name next characters |
||
193 | // 26 - 27 reserved |
||
194 | // 28 - 31 file name next characters |
||
195 | // in these entries name placed in sequential order |
||
196 | // but first characters are located in first previous entry |
||
197 | // next characters - in next previous etc. |
||
198 | // if current entry is orificated by 0x40 - the entry is last (cinains last characters) |
||
199 | // unneed places for characters in the last entry are filled by 0xff |
||
200 | int length = 0; |
||
201 | |||
202 | for (int i = 1; i < 255 / 13; i++) { |
||
203 | //! TODO: Add unicode support |
||
204 | name[length++] = folderEntry[i * -32 + 1]; |
||
205 | name[length++] = folderEntry[i * -32 + 3]; |
||
206 | name[length++] = folderEntry[i * -32 + 5]; |
||
207 | name[length++] = folderEntry[i * -32 + 7]; |
||
208 | name[length++] = folderEntry[i * -32 + 9]; |
||
209 | name[length++] = folderEntry[i * -32 + 14]; |
||
210 | name[length++] = folderEntry[i * -32 + 16]; |
||
211 | name[length++] = folderEntry[i * -32 + 18]; |
||
212 | name[length++] = folderEntry[i * -32 + 20]; |
||
213 | name[length++] = folderEntry[i * -32 + 22]; |
||
214 | name[length++] = folderEntry[i * -32 + 24]; |
||
215 | name[length++] = folderEntry[i * -32 + 28]; |
||
216 | name[length++] = folderEntry[i * -32 + 30]; |
||
217 | if (folderEntry[i * -32] & 0x40) { |
||
218 | while (name[length - 1] == 0xff) { name[--length] = 0; } |
||
219 | name[length++] = 0; |
||
220 | return; |
||
221 | } |
||
222 | } |
||
223 | } |
||
224 | } |
||
225 | |||
226 | |||
227 | static int fat12__forEachFile_handleFolderEntry(const Fat12 *this, int folderEntryOffset, String *name, |
||
228 | ForEachCallback callback, void *callbackParam) { |
||
229 | int nameSize = 0; |
||
230 | |||
231 | if (this->image[folderEntryOffset] == 0) { return 1; } // zero-entry, not file nor folder |
||
232 | nameSize = fat12__getItemNameSize(&this->image[folderEntryOffset]); // includes sizeof '\0' |
||
233 | if (nameSize != 0) { |
||
234 | while (name->capacity < name->length + nameSize + 1) { |
||
235 | name->capacity += name->capacity / 2; |
||
236 | name->data = realloc(name->data, name->capacity); |
||
237 | } |
||
238 | name->data[name->length++] = '/'; |
||
239 | fat12__getItemName(&this->image[folderEntryOffset], &name->data[name->length]); |
||
240 | name->length += nameSize - 1; |
||
241 | if ((this->image[folderEntryOffset + 11] & 0x10)) { |
||
242 | // the item is folder |
||
243 | // handle folder only if it isn't current folder or parent one |
||
244 | if (memcmp(&this->image[folderEntryOffset], ". ", 11) && |
||
245 | memcmp(&this->image[folderEntryOffset], ".. ", 11)) { |
||
246 | if (!fat12__forEachFile_handleFolder(this, get16(this->image, folderEntryOffset + 26), name, callback, callbackParam)) { |
||
247 | return 0; |
||
248 | } |
||
249 | } |
||
250 | } else { |
||
251 | // the item is a regular file |
||
252 | void *buffer = NULL; |
||
253 | int size = get32(this->image, folderEntryOffset + 28); |
||
254 | int cluster = get16(this->image, folderEntryOffset + 26); |
||
255 | |||
256 | buffer = malloc(size); |
||
257 | if (!fat12__getFile(this, buffer, size, cluster)) { |
||
258 | free(buffer); |
||
259 | return 0; |
||
260 | } |
||
261 | callback(name->data, size, buffer, callbackParam); |
||
262 | free(buffer); |
||
263 | } |
||
264 | name->length -= nameSize - 1; // substract length of current item name |
||
265 | name->length--; // substract length of '/' |
||
266 | name->data[name->length] = '\0'; |
||
267 | } |
||
268 | return 1; |
||
269 | } |
||
270 | |||
271 | static int fat12__forEachFile_handleFolder(const Fat12 *this, int claster, String *name, |
||
272 | ForEachCallback callback, void *callbackParam) { |
||
273 | for (; claster < 0xff7; claster = fat12__getNextClaster(this, claster)) { |
||
274 | int offset = fat12__getOffsetByClaster(this, claster); |
||
275 | |||
276 | for (int i = 0; i < (this->bytesPerSector * this->sectorsPerClaster / 32); i++) { |
||
277 | if (!fat12__forEachFile_handleFolderEntry(this, offset + 32 * i, name, callback, callbackParam)) { |
||
278 | return 0; |
||
279 | } |
||
280 | } |
||
281 | } |
||
282 | return 1; |
||
283 | } |
||
284 | |||
285 | static int fat12__forEachFile(const Fat12 *this, ForEachCallback callback, void *callbackParam) { |
||
286 | String name = { 0 }; |
||
287 | |||
288 | name.capacity = 4096; |
||
289 | name.data = malloc(name.capacity); |
||
290 | name.length = 0; |
||
291 | name.data[0] = '\0'; |
||
292 | |||
293 | for (int i = 0; i < this->maxRootEntries; i++) { |
||
294 | if (!fat12__forEachFile_handleFolderEntry(this, this->rootDirectory + 32 * i, &name, callback, callbackParam)) { |
||
295 | free(name.data); |
||
296 | return 0; |
||
297 | } |
||
298 | } |
||
299 | free(name.data); |
||
300 | return 1; |
||
301 | } |
||
302 | |||
303 | static int fat12__open(Fat12 *this, const char *img) { |
||
304 | FILE *fp = NULL; |
||
305 | |||
306 | if (!(fp = fopen(img, "rb"))) { |
||
307 | return fat12__error(this, "Can't open imput file"); |
||
308 | } |
||
309 | fseek(fp, 0, SEEK_END); |
||
310 | this->imageSize = ftell(fp); |
||
311 | rewind(fp); |
||
312 | if (!(this->image = malloc(this->imageSize))) { |
||
313 | return fat12__error(this, "Can't allocate memory for image"); |
||
314 | } |
||
315 | fread(this->image, 1, this->imageSize, fp); |
||
316 | fclose(fp); |
||
317 | this->bytesPerSector = *(uint16_t *)((uintptr_t)this->image + 11); |
||
318 | this->sectorsPerClaster = *(uint8_t *)((uintptr_t)this->image + 0x0d); |
||
319 | this->reservedSectorCount = *(uint16_t *)((uintptr_t)this->image + 0x0e); |
||
320 | this->numberOfFats = *(uint8_t *)((uintptr_t)this->image + 0x10); |
||
321 | this->maxRootEntries = *(uint16_t *)((uintptr_t)this->image + 0x11); |
||
322 | this->totalSectors = *(uint16_t *)((uintptr_t)this->image + 0x13); |
||
323 | if (!this->totalSectors) { |
||
324 | this->totalSectors = *(uint32_t *)((uintptr_t)this->image + 0x20); |
||
325 | } |
||
326 | this->sectorsPerFat = *(uint16_t *)((uintptr_t)this->image + 0x16); |
||
327 | this->firstFat = (0 + this->reservedSectorCount) * this->bytesPerSector; |
||
328 | this->rootDirectory = this->firstFat + this->numberOfFats |
||
329 | * this->sectorsPerFat * this->bytesPerSector; |
||
330 | this->dataRegion = this->rootDirectory + this->maxRootEntries * 32; |
||
7868 | Boppan | 331 | printf("\nBytes per sector: %d\n", this->bytesPerSector); |
332 | printf("Sectors per claster: %d\n", this->sectorsPerClaster); |
||
333 | printf("Reserver sector count: %d\n", this->reservedSectorCount); |
||
334 | printf("Number of FATs: %d\n", this->numberOfFats); |
||
335 | printf("Max root entries: %d\n", this->maxRootEntries); |
||
336 | printf("Total sectors: %d\n", this->totalSectors); |
||
337 | printf("Sectors per FAT: %d\n", this->sectorsPerFat); |
||
338 | printf("First FAT: %d\n", this->firstFat); |
||
339 | printf("Root directory: %d\n", this->rootDirectory); |
||
340 | printf("Data region: %d\n\n", this->dataRegion); |
||
7843 | Boppan | 341 | return 1; |
342 | } |
||
343 | |||
344 | static int fat12__error(Fat12 *this, const char *errorMessage) { |
||
345 | this->errorMessage = errorMessage; |
||
346 | return 0; |
||
347 | } |
||
348 | |||
349 | static int handleError(const Fat12 *fat12) { |
||
7868 | Boppan | 350 | printf("Error in Fat12: %s\n", fat12->errorMessage); |
7843 | Boppan | 351 | return -1; |
352 | } |
||
353 | |||
7868 | Boppan | 354 | void writeFile(const char *fileName, int size, const uint8_t *data) { |
355 | // FILE *fp = NULL; |
||
356 | // if (!(fp = fopen(fileName, "wb"))) { perror(NULL); } |
||
357 | // fwrite(data, 1, size, fp); |
||
358 | // fclose(fp); |
||
359 | struct Info { |
||
360 | int number; |
||
361 | int reserved0; |
||
362 | int reserved1; |
||
363 | int dataSize; |
||
364 | const void *data; |
||
365 | char zero; |
||
366 | const char *name; |
||
367 | } __attribute__((packed)) *info = calloc(sizeof(struct Info), 1); |
||
368 | |||
369 | info->number = 2; // create/overwrite file |
||
370 | info->dataSize = size; |
||
371 | info->data = data; |
||
372 | info->zero = 0; |
||
373 | info->name = fileName; |
||
374 | asm volatile ("int $0x40" :: "a"(70), "b"(info)); |
||
375 | } |
||
376 | |||
7843 | Boppan | 377 | static int callback(const char *name, size_t size, const uint8_t *data, void *param) { |
378 | String *outputPath = param; |
||
379 | |||
380 | while (outputPath->capacity < outputPath->length + strlen(name) + 1 + 1) { |
||
381 | outputPath->capacity += outputPath->capacity / 2; |
||
382 | outputPath->data = realloc(outputPath->data, outputPath->capacity); |
||
383 | } |
||
384 | strcat(outputPath->data, name); |
||
385 | { // don't let mkdir_p create folder where file should be located |
||
386 | char *fileNameDelim = NULL; |
||
387 | |||
388 | // no slash = no folders to create, outputPath->data contains only file name |
||
389 | // yes, I know, outputPath->data always contains '/', but who knows... |
||
390 | if ((fileNameDelim = strrchr(outputPath->data, '/'))) { |
||
391 | *fileNameDelim = '\0'; |
||
392 | mkdir_p(outputPath->data); |
||
393 | *fileNameDelim = '/'; |
||
394 | } |
||
395 | } |
||
7868 | Boppan | 396 | printf("Extracting %s\n", outputPath->data); |
397 | writeFile(outputPath->data, size, data); |
||
7843 | Boppan | 398 | outputPath->data[outputPath->length] = '\0'; |
399 | return 0; |
||
400 | } |
||
401 | |||
402 | |||
403 | |||
404 | int main(int argc, char **argv) { |
||
405 | Fat12 fat12 = { 0 }; |
||
406 | char *imageFile = NULL; |
||
407 | String outputFolder = { 0 }; |
||
408 | |||
409 | if (argc < 2) { |
||
7868 | Boppan | 410 | puts(" Usage:\n"); |
411 | puts(" unimg \"/path/to/kolibri.img\" \"/optional/extract/path\"\n"); |
||
7843 | Boppan | 412 | return -1; |
7867 | leency | 413 | } else { |
414 | imageFile = argv[1]; |
||
7868 | Boppan | 415 | printf("File: %s\n", imageFile); |
7843 | Boppan | 416 | } |
417 | |||
418 | |||
419 | outputFolder.capacity = 4096; |
||
420 | outputFolder.data = malloc(outputFolder.capacity); |
||
421 | |||
422 | //! ACHTUNG: possible buffer overflow, is 4096 enough in KolibriOS? |
||
423 | if (argc >= 3 && argv[2][0] != '-') strcpy(outputFolder.data, argv[2]); |
||
7867 | leency | 424 | else { |
425 | strcpy(outputFolder.data, "/tmp0/1"); |
||
426 | strcat(outputFolder.data, strrchr(imageFile, '/')); |
||
427 | } |
||
7843 | Boppan | 428 | |
429 | outputFolder.length = strlen(outputFolder.data); |
||
430 | |||
431 | if (!fat12__open(&fat12, imageFile)) { |
||
432 | return handleError(&fat12); |
||
433 | } |
||
434 | |||
435 | if (!fat12__forEachFile(&fat12, callback, &outputFolder)) { |
||
436 | return handleError(&fat12); |
||
437 | } |
||
438 | |||
7868 | Boppan | 439 | puts("\nDONE!\n"); |
7843 | Boppan | 440 | }>>>>>>>>>><>><>><>><> |