/* unblorb.c: function for extracting resources from a blorb file. MIT-licensed by Daniel Stelzer. */ #include /* malloc, free */ #include /* fprintf for error messages, fopen, fclose, fread, fwrite */ #include /* strncmp, strncpy, strlen */ #include /* uint32_t (replace with appropriate unsigned integer type if your compiler doesn't have this) */ #include /* POSIX header needed for the ntohl function, which is the easiest way to get the endianness right cross-platform. Available on all decent Linuxes and many Windows compilers as well, though sometimes with a different path; check your manual if this line gives an error. */ void safeRead(void *out, size_t n, FILE *f){ /* Not really "safe" but prints a decent error message at least */ size_t s; if((s=fread(out, 1, n, f)) < n){ fprintf(stderr, "Error reading from blorb file: %lu < %lu\n", s, n); } } void safeWrite(void *in, size_t n, FILE *f){ size_t s; if((s=fwrite(in, 1, n, f)) < n){ fprintf(stderr, "Error writing to external file: %lu < %lu\n", s, n); } } uint32_t getNextWord(FILE *f){ /* Get a four-byte value, converting from big-endian to host order */ uint32_t i; safeRead(&i, 4, f); return ntohl(i); } #define BUFFER_SIZE 1024 void copyBytesToFile(FILE* src, FILE* dst, uint32_t n){ char buffer[BUFFER_SIZE]; uint32_t full = n / BUFFER_SIZE; uint32_t part = n % BUFFER_SIZE; for(uint32_t i = 0; i < full; i++){ safeRead(buffer, BUFFER_SIZE, src); safeWrite(buffer, BUFFER_SIZE, dst); } safeRead(buffer, part, src); safeWrite(buffer, part, dst); } int unblorb(const char *blorb_path, const char *resource_path){ /* Copy all resource files from blorb_path to resource_path. Returns 0 on success, 1 on bad arguments, 2 on missing directories, 3 on failure to open blorb, 4 on invalid blorb file, 5 on failure to write resource file. */ /* Sanity check the arguments */ if(!blorb_path || !*blorb_path){ fprintf(stderr, "Blorb path is empty\n"); return 1; } if(!resource_path || !*resource_path){ fprintf(stderr, "Resource path is empty\n"); return 1; } /* Here is where you'd make sure that your directories exist. However this is messy to do in a cross-platform way so I'm just assuming they always exist. In production code you should check or create resource_path, resource_path/Data, resource_path/Exec, resource_path/Pict, and resource_path/Snd_, and return 2 if any of this fails. */ size_t pathlen = strlen(resource_path); char *res_path_buf = malloc(pathlen + 1 + 4 + 1 + 4 + 1 + 3 + 1); /* path / TYPE / 0000 . ext \0 */ strcpy(res_path_buf, resource_path); res_path_buf[pathlen] = '/'; char *res_path_type = res_path_buf + pathlen + 1; /* Step past the newly-added slash: this is where filenames should go */ res_path_type[4] = '/'; char *res_path_name = res_path_type + 5; res_path_name[4] = '.'; char *res_path_ext = res_path_name + 5; res_path_ext[3] = '\0'; FILE *blorb_file = fopen(blorb_path, "rb"); if(!blorb_file){ fprintf(stderr, "Failed to open blorb file\n"); free(res_path_buf); return 3; } char workspace[16]; /* Step 1: make sure this is actually a blorb file. */ safeRead(workspace, 16, blorb_file); workspace[4] = ' '; /* I don't care about the file size since I can get that from stat, and it'll mess up the strncmp */ workspace[5] = ' '; workspace[6] = ' '; workspace[7] = ' '; if(strncmp(workspace, "FORM IFRSRIdx", 16)){ /* FORM, blocked, type IFRS, first chunk RIdx (resource index) */ fprintf(stderr, "This is not a valid blorb file!\n"); fclose(blorb_file); free(res_path_buf); return 4; } fseek(blorb_file, 4, SEEK_CUR); /* Skip the next word (the number of bytes in the index chunk) */ uint32_t res = getNextWord(blorb_file); /* Then read the number of resources stored in this blorb */ for(uint32_t i = 0; i < res; i++){ fseek(blorb_file, 24 + 12*i, SEEK_SET); safeRead(workspace, 4, blorb_file); uint32_t resnum = getNextWord(blorb_file); uint32_t startpos = getNextWord(blorb_file); workspace[4] = '\0'; strncpy(res_path_type, workspace, 4); if(res_path_type[3] == ' ') res_path_type[3] = '_'; /* For "Snd "; trailing spaces in filenames are not fun to deal with */ sprintf(workspace, "%04lu", (unsigned long)resnum); /* Write the file "name" (actually its resource number) */ strncpy(res_path_name, workspace, 4); fseek(blorb_file, startpos, SEEK_SET); /* Go to the chunk we found */ safeRead(workspace, 4, blorb_file); /* Read the resource chunk's tag */ uint32_t chunk_size = getNextWord(blorb_file); if(!strncmp(workspace, "PNG ", 4)){ /* This is really tedious and kind of unnecessary, but it lets us check for any unrecognized chunk types also */ res_path_ext[0] = 'p'; res_path_ext[1] = 'n'; res_path_ext[2] = 'g'; }else if(!strncmp(workspace, "JPEG", 4)){ res_path_ext[0] = 'j'; res_path_ext[1] = 'p'; res_path_ext[2] = 'g'; }else if(!strncmp(workspace, "RECT", 4)){ res_path_ext[0] = 'r'; res_path_ext[1] = 'c'; res_path_ext[2] = 't'; }else if(!strncmp(workspace, "FORM", 4)){ /* SPECIAL CASE for AIFF data, which is also a form (luckily the only one) */ res_path_ext[0] = 'a'; res_path_ext[1] = 'i'; res_path_ext[2] = 'f'; fseek(blorb_file, -8, SEEK_CUR); /* Step back before the FORM tag since we want to copy that too */ chunk_size += 8; }else if(!strncmp(workspace, "OGGV", 4)){ res_path_ext[0] = 'o'; res_path_ext[1] = 'g'; res_path_ext[2] = 'g'; }else if(!strncmp(workspace, "MOD ", 4)){ res_path_ext[0] = 'm'; res_path_ext[1] = 'o'; res_path_ext[2] = 'd'; }else if(!strncmp(workspace, "SONG", 4)){ res_path_ext[0] = 's'; res_path_ext[1] = 'n'; res_path_ext[2] = 'g'; }else if(!strncmp(workspace, "TEXT", 4)){ res_path_ext[0] = 't'; res_path_ext[1] = 'x'; res_path_ext[2] = 't'; }else if(!strncmp(workspace, "BINA", 4)){ res_path_ext[0] = 'b'; res_path_ext[1] = 'i'; res_path_ext[2] = 'n'; }else if(!strncmp(workspace, "ZCOD", 4)){ /* Is there a common extension for Z-machine games? I don't know of one... */ res_path_ext[0] = 'z'; res_path_ext[1] = '\0'; res_path_ext[2] = '\0'; }else if(!strncmp(workspace, "GLUL", 4)){ res_path_ext[0] = 'u'; res_path_ext[1] = 'l'; res_path_ext[2] = 'x'; }else if(!strncmp(workspace, "IFmd", 4)){ /* This shouldn't be in the resource list at all, but for a few weird blorbs it is */ res_path_ext[0] = 'x'; res_path_ext[1] = 'm'; res_path_ext[2] = 'l'; }else{ workspace[4] = '\0'; fprintf(stderr, "Unrecognized resource chunk \"%s\"! Either this is a bad blorb file, or you've run against a limitation of my very simple approach. Contact the developer.\n", workspace); fclose(blorb_file); free(res_path_buf); return 5; } FILE *out_file = fopen(res_path_buf, "wb"); if(!out_file){ fprintf(stderr, "Failed to open external file %s...continuing anyway\n", res_path_buf); continue; } copyBytesToFile(blorb_file, out_file, chunk_size); fclose(out_file); } fclose(blorb_file); free(res_path_buf); return 0; }