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