007/tools/extractor/main.c

333 lines
9.4 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include "puff.h"
#include "fread_csv_line.h"
#ifdef _WIN32
#include <windows.h>
#define MAX_THREADS 32
#elif __APPLE__
#include <sys/param.h>
#include <sys/sysctl.h>
#define MAX_THREADS 32
#else
#include <unistd.h>
#ifdef __linux__
#define MAX_THREADS 32
#else
#define MAX_THREADS 1
#endif
#endif
#define CLAMP_VAL(value, min, max) (((value) < (min)) ? (min) : (((value) > (max)) ? (max) : ((value) < (min)) ? (min) : (value)))
#define MBTOBYTES(megs) (megs*0x100000)
#define LINE "__________________________________________________________________"
#define MIN_ROM_SIZE MBTOBYTES(12) /* minimum ROM filesize supported (12MB) */
#define MAX_FILENAME 256 /* max filename supported */
#define GE_1172_HEADER_LENGTH 2 /* header length of GE 1172 zip */
unsigned char *rom_buf;
unsigned char tmp_buf[MAX_THREADS][MBTOBYTES(2)] = {0}; /* holds our input/output for gzip inflate (max output file supported 2MB) */
pthread_t puff_threads[MAX_THREADS];
unsigned long int counted = 0, success_thread[MAX_THREADS] = {0}, failed_thread[MAX_THREADS] = {0};
struct pthread_arg
{
unsigned long int offset, size;
char *name;
int compress, thread_id, ready;
};
typedef struct pthread_arg arg_thread;
void *extract_thread(void *arg)
{
/************************/
arg_thread *thread_arg = (arg_thread *)arg;
int compress, thread_id, puff_return;
unsigned long offset, size, out_size;
char name[MAX_FILENAME];
FILE *output;
/************************/
memcpy(name, thread_arg->name, MAX_FILENAME);
offset = thread_arg->offset;
compress = thread_arg->compress;
size = thread_arg->size;
thread_id = thread_arg->thread_id;
printf("\n Extracting %s %s, %lu bytes...", compress ? "compressed" : "uncompressed", name, size);
thread_arg->ready = 1;
output = fopen(name, "wb");
if(output == NULL)
{
printf("\n Error: Could not output file %s", name);
failed_thread[thread_id]++;
goto error_thread_output;
}
if(compress) /* unzip */
{
puff_return = puff(tmp_buf[thread_id], (unsigned long)MBTOBYTES(2), &rom_buf[offset + GE_1172_HEADER_LENGTH], size, &out_size);
if(puff_return != 0)
{
switch(puff_return)
{
case 2: printf("\n\n Error: Could not uncompress file %s\n Available inflate data did not terminate\n", name); break;
case 1: printf("\n\n Error: Could not uncompress file %s\n Output space exhausted before completing inflate\n", name); break;
case -1: printf("\n\n Error: Could not uncompress file %s\n Invalid block type (type == 3)\n", name); break;
case -2: printf("\n\n Error: Could not uncompress file %s\n Stored block length did not match one's complement\n", name); break;
case -3: printf("\n\n Error: Could not uncompress file %s\n Dynamic block code description: too many length or distance codes\n", name); break;
case -4: printf("\n\n Error: Could not uncompress file %s\n Dynamic block code description: code lengths codes incomplete\n", name); break;
case -5: printf("\n\n Error: Could not uncompress file %s\n Dynamic block code description: repeat lengths with no first length\n", name); break;
case -6: printf("\n\n Error: Could not uncompress file %s\n Dynamic block code description: repeat more than specified lengths\n", name); break;
case -7: printf("\n\n Error: Could not uncompress file %s\n Dynamic block code description: invalid literal/length code lengths\n", name); break;
case -8: printf("\n\n Error: Could not uncompress file %s\n Dynamic block code description: invalid distance code lengths\n", name); break;
case -9: printf("\n\n Error: Could not uncompress file %s\n Dynamic block code description: missing end-of-block code\n", name); break;
case -10: printf("\n\n Error: Could not uncompress file %s\n Invalid literal/length or distance code in fixed or dynamic block\n", name); break;
case -11: printf("\n\n Error: Could not uncompress file %s\n Distance is too far back in fixed or dynamic block\n", name); break;
default: printf("\n\n Error: Could not uncompress file %s\n Unknown error\n", name); break;
}
failed_thread[thread_id]++;
goto error_thread_unzip;
}
fwrite(tmp_buf[thread_id], out_size, 1, output);
}
else /* uncompressed */
{
fwrite(&rom_buf[offset], size, 1, output);
}
success_thread[thread_id]++;
error_thread_unzip:
fclose(output);
error_thread_output:
return NULL;
}
int detect_threads(void)
{
#ifdef WIN32
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
return sysinfo.dwNumberOfProcessors;
#elif __APPLE__
int nm[2];
size_t len = 4;
uint32_t count;
nm[0] = CTL_HW; nm[1] = HW_AVAILCPU;
sysctl(nm, 2, &count, &len, NULL, 0);
if(count < 1)
{
nm[1] = HW_NCPU;
sysctl(nm, 2, &count, &len, NULL, 0);
if(count < 1)
{
count = 1;
}
}
return count;
#elif __linux__
#if defined (_SC_NPROCESSORS_ONLN)
return sysconf(_SC_NPROCESSORS_ONLN);
#elif defined (_SC_NPROC_ONLN)
return sysconf(_SC_NPROC_ONLN);
#else
return 1;
#endif
#else
return 1;
#endif
}
void extract_files(FILE *csvfile, const int max_threads)
{
/************************/
int cur_line_entry = 0, max_line_entry = 0;
int eof = 0, index, cur_thread = 0, check_threads = 0;
char *csv_buf, cur_line;
unsigned long int offset, size;
int compress, extract;
char name[MAX_FILENAME] = {'\0'};
volatile arg_thread thread_arg;
/************************/
/* get max line entry in csv file (required for allocation) */
while(fread(&cur_line, 1, 1, csvfile))
{
if(cur_line == '\n')
{
if(cur_line_entry > max_line_entry)
max_line_entry = cur_line_entry;
cur_line_entry = 0;
continue;
}
cur_line_entry++;
}
if(fseek(csvfile, 0, SEEK_SET) != 0)
{
printf("\n Error: Aborted, could not set to beginning of csv file");
return;
}
if(!fread_csv_alloc(max_line_entry))
{
printf("\n Error: Aborted, could not allocate memory for csv file");
return;
}
while(!eof)
{
csv_buf = fread_csv_line(csvfile, &eof);
if(csv_buf == NULL)
{
printf("\n Error: Aborted, could not read csv line");
break;
}
for(index = 0; index < max_line_entry; index++) /* replace commas with new line characters for sscanf */
{
if(csv_buf[index] == ',')
csv_buf[index] = '\n';
}
if(sscanf(csv_buf, "%lu\n%lu\n%s\n%d\n%d", &offset, &size, name, &compress, &extract) != 5) /* error or reached end of csv file, abort */
break;
counted++;
if(extract != 1)
continue;
if(check_threads)
{
if(pthread_join(puff_threads[cur_thread], NULL) != 0)
{
printf("\n Error: Aborted, could not create thread %d", cur_thread);
break;
}
}
thread_arg.ready = 0;
thread_arg.name = name;
thread_arg.size = size;
thread_arg.offset = offset;
thread_arg.compress = !(!compress);
thread_arg.thread_id = (short)cur_thread;
pthread_create(&puff_threads[cur_thread], NULL, extract_thread, (void *)&thread_arg);
for(;;) /* wait for thread to finish copy arguments */
{
if(thread_arg.ready)
{
break;
}
}
cur_thread++;
if(cur_thread >= max_threads)
{
cur_thread = 0;
check_threads = 1;
}
}
fread_csv_free();
cur_thread = 0;
while(cur_thread < max_threads) /* wait for thread to finish task */
{
if(pthread_join(puff_threads[cur_thread], NULL) != 0)
{
continue;
}
cur_thread++;
}
}
int main(int argc, char **argv)
{
/************************/
FILE *romfile, *csvfile;
long int filesize;
int threads_total, index;
unsigned long success_total = 0, failed_total = 0;
/************************/
/* set threads total */
threads_total = CLAMP_VAL(detect_threads(), 1, MAX_THREADS);
printf("\n GoldenEye 007 Extractor\n%s\n", LINE);
if(argc != 3) /* no file provided or too many arguments */
{
printf("\n About: Extract GoldenEye 007 files\n\n Syntax: %s \"input ROM\" \"input csv file\"\n Multithread Mode: %sabled", argv[0], threads_total > 1 ? "En" : "Dis");
goto exit;
}
/* load ROM from argument */
romfile = fopen(argv[1], "rb");
if(romfile == NULL)
{
printf("\n Error: Aborted, ROM cannot be opened");
goto exit;
}
if(fseek(romfile, 0, SEEK_END) != 0)
{
printf("\n Error: Aborted, could not find end to ROM file");
goto error_rom;
}
/* get ROM filesize */
filesize = ftell(romfile);
if(filesize == -1)
{
printf("\n Error: Aborted, could not find length to ROM file");
goto error_rom;
}
if(filesize < MIN_ROM_SIZE) /* if ROM filesize is under supported filesize, abort */
{
printf("\n Error: Aborted, ROM is invalid");
goto error_rom;
}
if(fseek(romfile, 0, SEEK_SET) != 0)
{
printf("\n Error: Aborted, could not find beginning of ROM file");
goto error_rom;
}
printf("\n Input ROM: %s", argv[1]);
/* load csv file from argument */
csvfile = fopen(argv[2], "rb");
if(csvfile == NULL)
{
printf("\n Error: Aborted, csv file cannot be opened");
goto error_rom;
}
/* read ROM to file buffer */
rom_buf = (unsigned char *)malloc(filesize);
if(rom_buf == NULL)
{
printf("\n Error: Aborted, not enough memory to load ROM");
goto error_csv;
}
fread(rom_buf, filesize, 1, romfile);
extract_files(csvfile, threads_total);
for(index = 0; index < threads_total; index++)
{
success_total += success_thread[index];
failed_total += failed_thread[index];
}
printf("\n\n Total Files: %lu\n\n Extracted: %lu\n Skipped: %lu", counted, success_total, counted - success_total);
if(failed_total)
printf("\n Failed: %lu", failed_total);
free(rom_buf);
error_csv:
fclose(csvfile);
error_rom:
fclose(romfile);
exit:
printf("\n%s\n", LINE);
return 0;
}