mirror of
https://github.com/simtactics/mysimulation.git
synced 2025-03-15 14:51:21 +00:00
429 lines
No EOL
14 KiB
C
429 lines
No EOL
14 KiB
C
/*
|
|
FileHandler - General-purpose file handling library for Niotso
|
|
farextract.c - Copyright (c) 2011 Niotso Project <http://niotso.org/>
|
|
Author(s): Fatbag <X-Fi6@phppoll.org>
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <time.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#include "far.h"
|
|
|
|
#ifndef read_uint32
|
|
#define read_uint32(x) (unsigned)(((x)[0]<<(8*0)) | ((x)[1]<<(8*1)) | ((x)[2]<<(8*2)) | ((x)[3]<<(8*3)))
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#define mkdir(path, x) mkdir(path)
|
|
#endif
|
|
static int mkpath(char * path){
|
|
char * p;
|
|
for(p = strpbrk(path+1, "/\\"); p; p = strpbrk(p+1, "/\\")){
|
|
char c = *p;
|
|
int value;
|
|
*p = '\0'; value = mkdir(path, 0644); *p = c;
|
|
if(value != 0 && errno != EEXIST)
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
enum {
|
|
profile_ts1 = 1,
|
|
profile_tso,
|
|
profile_sc4,
|
|
profile_ts2,
|
|
profile_spore,
|
|
profile_ts3
|
|
};
|
|
|
|
typedef struct {
|
|
uint32_t GroupID;
|
|
const char * Directory;
|
|
} GroupMap_t;
|
|
|
|
typedef struct {
|
|
uint32_t TypeID;
|
|
const char * Extension;
|
|
} TypeMap_t;
|
|
|
|
typedef struct {
|
|
uint8_t Size;
|
|
const char * Header;
|
|
const char * Extension;
|
|
} HeaderMap_t;
|
|
|
|
static const GroupMap_t GroupMap[] = {
|
|
{0x0A3C55C7, "Music/Stations/Horror/"},
|
|
{0x0A3C55CE, "Music/Stations/OldWorld/"},
|
|
{0x0A3C55D3, "Music/Stations/SciFi/"},
|
|
{0x1D6962CF, "SoundData/HitLabUI/"},
|
|
{0x1D8A8B4F, "SoundData/HitLabTestSamples/"},
|
|
{0x29D9359D, "SoundData/CustomTrks/"},
|
|
{0x29DAA4A6, "SoundData/Custom/"},
|
|
{0x29DD0888, "SoundData/Multiplayer/"},
|
|
{0x69C6C943, "SoundData/tsov2/"},
|
|
{0x8A6FCC30, "SoundData/EP5Samps/"},
|
|
{0x9DBDBF74, "SoundData/HitLists/"},
|
|
{0x9DBDBF89, "SoundData/Samples/"},
|
|
{0x9DF26DAD, "Music/Stations/Country/"},
|
|
{0x9DF26DAE, "Music/Stations/CountryD/"},
|
|
{0x9DF26DB1, "Music/Stations/Latin/"},
|
|
{0x9DF26DB3, "Music/Stations/Rap/"},
|
|
{0x9DF26DB6, "Music/Stations/Rock/"},
|
|
{0xA9C6C89A, "SoundData/Tracks/"},
|
|
{0xBD6E5937, "SoundData/HitLabTest/"},
|
|
{0xBDF26DB0, "Music/Stations/Disco/"},
|
|
{0xC9C6C9B3, "SoundData/HitListsTemp/"},
|
|
{0xDDBDBF8C, "SoundData/Stings/"},
|
|
{0xDDE8F5C6, "SoundData/EP2/"},
|
|
{0xDDF26DA9, "Music/Stations/Beach/"},
|
|
{0xDDF26DB4, "Music/Stations/Rave/"},
|
|
{0xFDBDBF87, "SoundData/TrackDefs/"},
|
|
{0xFDF26DAB, "Music/Stations/Classica/"}
|
|
};
|
|
|
|
static const TypeMap_t TypeMap[] = {
|
|
{0, ".dat"},
|
|
{1, ".bmp"},
|
|
{2, ".tga"},
|
|
{5, ".skel"},
|
|
{7, ".anim"},
|
|
{9, ".mesh"},
|
|
{11, ".bnd"},
|
|
{12, ".apr"},
|
|
{13, ".oft"},
|
|
{14, ".png"},
|
|
{15, ".po"},
|
|
{16, ".col"},
|
|
{18, ".hag"},
|
|
{20, ".jpg"},
|
|
{24, ".png"},
|
|
{0x0A8B0E70, ".mad"},
|
|
{0x1B6B9806, ".utk"},
|
|
{0x1D07EB4B, ".xa"},
|
|
{0x1D968538, ".xa"},
|
|
{0x2026960B, ".dat"},
|
|
{0x3CEC2B47, ".mp3"},
|
|
{0x3D968536, ".mp3"},
|
|
{0x5D73A611, ".trk"},
|
|
{0x7B1ACFCD, ".hls"},
|
|
{0x856DDBAC, ".bmp"},
|
|
{0x9D796DB4, ".tlo"},
|
|
{0x9D96853A, ".wav"},
|
|
{0xA3CD96CF, ".tkd"},
|
|
{0xBB7051F5, ".wav"}
|
|
};
|
|
|
|
static const HeaderMap_t AudioHeaders[] = {
|
|
{2, "XA", ".xa"},
|
|
{4, "RIFF", ".wav"},
|
|
{4, "UTM0", ".utk"},
|
|
{4, "\xFF\xFB\x90\x40", ".mp3"},
|
|
{24, "# Generated by UI editor", ".scr"}
|
|
};
|
|
|
|
static const char * groupid_to_dir(uint32_t GroupID){
|
|
size_t i;
|
|
for(i=0; i<sizeof(GroupMap)/sizeof(GroupMap_t); i++){
|
|
if(GroupMap[i].GroupID == GroupID)
|
|
return GroupMap[i].Directory;
|
|
}
|
|
fprintf(stderr, "%sUnrecognized Group ID 0x%08X.\n", "farextract: warning: ", GroupID);
|
|
return "./";
|
|
}
|
|
|
|
static const char * typeid_to_ext(uint32_t TypeID){
|
|
size_t i;
|
|
for(i=0; i<sizeof(TypeMap)/sizeof(TypeMap_t); i++){
|
|
if(TypeMap[i].TypeID == TypeID)
|
|
return TypeMap[i].Extension;
|
|
}
|
|
fprintf(stderr, "%sUnrecognized Type ID 0x%08X.\n", "farextract: warning: ", TypeID);
|
|
return ".dat";
|
|
}
|
|
|
|
static const char * header_to_ext(const uint8_t * Buffer, size_t Size){
|
|
size_t i;
|
|
for(i=0; i<sizeof(AudioHeaders)/sizeof(HeaderMap_t); i++){
|
|
if(Size >= AudioHeaders[i].Size && !memcmp(Buffer, AudioHeaders[i].Header, AudioHeaders[i].Size))
|
|
return AudioHeaders[i].Extension;
|
|
}
|
|
|
|
return ".str";
|
|
}
|
|
|
|
int main(int argc, char *argv[]){
|
|
int profile = 0, overwrite = 0;
|
|
const char * InFile = "", * OutDirectory;
|
|
FILE * hFile;
|
|
size_t ArchiveSize;
|
|
uint8_t * ArchiveData;
|
|
int ArchiveType;
|
|
clock_t BeginningTime, EndingTime;
|
|
unsigned extracted = 0;
|
|
int i;
|
|
|
|
/****
|
|
** Check the arguments
|
|
*/
|
|
|
|
if(argc == 1 || (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")))){
|
|
printf("Usage: farextract [OPTIONS] [FILE] [OUTDIRECTORY]\n"
|
|
"Extract the contents of a FAR, DBPF, or Persist file.\n"
|
|
"With no FILE, or when FILE is -, read from standard input.\n"
|
|
"\n"
|
|
"Profile options:\n"
|
|
" -ts1, -tso, -sc4, Select presets suitable for The Sims 1,\n"
|
|
" -ts2, -spore, -ts3 The Sims Online, SimCity 4, Spore, or The Sims 3\n"
|
|
"\n");
|
|
printf("Miscellaneous options:\n"
|
|
" -f, --force Force overwriting of files without confirmation\n"
|
|
" -h, --help Show this help and exit\n"
|
|
"\n"
|
|
"Report bugs to <X-Fi6@phppoll.org>.\n"
|
|
"farextract and libfar are maintained by the Niotso project.\n"
|
|
"Home page: <http://www.niotso.org/>\n");
|
|
return 0;
|
|
}
|
|
|
|
for(i=1; !InFile[0] && i != argc-1; i++){
|
|
/* Match for options */
|
|
if(!profile){
|
|
if(!strcmp(argv[i], "-ts1")){ profile = profile_ts1; continue; }
|
|
if(!strcmp(argv[i], "-tso")){ profile = profile_tso; continue; }
|
|
if(!strcmp(argv[i], "-sc4")){ profile = profile_sc4; continue; }
|
|
if(!strcmp(argv[i], "-ts2")){ profile = profile_ts2; continue; }
|
|
if(!strcmp(argv[i], "-spore")){ profile = profile_spore; continue; }
|
|
if(!strcmp(argv[i], "-ts3")){ profile = profile_ts3; continue; }
|
|
}
|
|
if(!overwrite && (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--force"))){
|
|
overwrite = 1;
|
|
continue;
|
|
}
|
|
/* Not an option */
|
|
if(!strcmp(argv[i], "-")){
|
|
fprintf(stderr, "%sReading from standard input is not yet implemented.", "farextract: error: ");
|
|
return -1;
|
|
}
|
|
InFile = argv[i];
|
|
continue;
|
|
}
|
|
/* We're left with the out directory */
|
|
if(!InFile[0]){
|
|
fprintf(stderr, "%sReading from standard input is not yet implemented.", "farextract: error: ");
|
|
return -1;
|
|
}
|
|
OutDirectory = argv[i];
|
|
|
|
/****
|
|
** Handle profile settings
|
|
*/
|
|
if(!profile) profile = profile_tso;
|
|
libfar_set_option(FAR_CONFIG_DEFAULT_TO_1A, (profile == profile_ts1));
|
|
libfar_set_option(FAR_CONFIG_DBPF_COMPRESSED, (profile >= profile_sc4));
|
|
libfar_set_option(FAR_CONFIG_REFPACK_HNSV, 0xFB);
|
|
|
|
/****
|
|
** Open the file and read in the entire contents to memory
|
|
*/
|
|
|
|
hFile = fopen(InFile, "rb");
|
|
if(hFile == NULL){
|
|
fprintf(stderr, "%sThe specified input file does not exist or could not be opened for reading.", "farextract: error: ");
|
|
return -1;
|
|
}
|
|
fseek(hFile, 0, SEEK_END);
|
|
ArchiveSize = ftell(hFile);
|
|
if(ArchiveSize < 24){
|
|
fprintf(stderr, "%sNot a valid archive.", "farextract: error: ");
|
|
return -1;
|
|
}
|
|
fseek(hFile, 0, SEEK_SET);
|
|
|
|
ArchiveData = malloc(ArchiveSize);
|
|
if(ArchiveData == NULL){
|
|
fprintf(stderr, "%sMemory for this archive could not be allocated.", "farextract: error: ");
|
|
return -1;
|
|
}
|
|
if(fread(ArchiveData, 1, ArchiveSize, hFile) != ArchiveSize){
|
|
fprintf(stderr, "%sThe input file could not be read.", "farextract: error: ");
|
|
return -1;
|
|
}
|
|
fclose(hFile);
|
|
|
|
/****
|
|
** Identify the type of archive
|
|
*/
|
|
|
|
ArchiveType = far_identify(ArchiveData, ArchiveSize);
|
|
if(ArchiveType == FAR_TYPE_INVALID){
|
|
fprintf(stderr, "%sNot a valid archive.", "farextract: error: ");
|
|
return -1;
|
|
}
|
|
|
|
if(chdir(OutDirectory) != 0){
|
|
fprintf(stderr, "%sOutput directory '%s' does not exist.", "farextract: error: ", OutDirectory);
|
|
return -1;
|
|
}
|
|
|
|
if(ArchiveType != FAR_TYPE_PERSIST){
|
|
FAREntryNode * EntryNode;
|
|
unsigned file = 0, filescount;
|
|
|
|
/****
|
|
** Load header information
|
|
*/
|
|
|
|
FARFile * FARFileInfo = far_create_archive(ArchiveType);
|
|
if(FARFileInfo == NULL){
|
|
fprintf(stderr, "%sMemory for this archive could not be allocated.", "farextract: error: ");
|
|
return -1;
|
|
}
|
|
if(!far_read_header(FARFileInfo, ArchiveData, ArchiveSize)){
|
|
fprintf(stderr, "%sNot a valid archive.", "farextract: error: ");
|
|
return -1;
|
|
}
|
|
|
|
filescount = FARFileInfo->Files;
|
|
printf("This archive contains %u files.\n\nExtracting\n", filescount);
|
|
BeginningTime = clock();
|
|
|
|
/****
|
|
** Load entry information
|
|
*/
|
|
|
|
if(!far_enumerate_entries(FARFileInfo, ArchiveData+FARFileInfo->IndexOffset,
|
|
ArchiveSize-FARFileInfo->IndexOffset, ArchiveSize)){
|
|
fprintf(stderr, "%sEntry data is corrupt.", "farextract: error: ");
|
|
return -1;
|
|
}
|
|
|
|
/****
|
|
** Extract each entry
|
|
*/
|
|
for(EntryNode = FARFileInfo->FirstEntry; EntryNode; EntryNode = EntryNode->NextEntry){
|
|
char destbuffer[256], * destination = destbuffer;
|
|
|
|
file++;
|
|
if(EntryNode->Entry.Filename)
|
|
destination = EntryNode->Entry.Filename;
|
|
else
|
|
sprintf(destbuffer, "%s%08x%s", groupid_to_dir(EntryNode->Entry.GroupID),
|
|
EntryNode->Entry.FileID, typeid_to_ext(EntryNode->Entry.TypeID));
|
|
|
|
if(far_read_entry_data(FARFileInfo, &(EntryNode->Entry), ArchiveData)){
|
|
/* Decompression, if any, was successful */
|
|
if(!EntryNode->Entry.Filename && EntryNode->Entry.TypeID == 0x2026960B)
|
|
sprintf(destbuffer, "%s%08x%s", groupid_to_dir(EntryNode->Entry.GroupID), EntryNode->Entry.FileID,
|
|
header_to_ext(EntryNode->Entry.DecompressedData, EntryNode->Entry.DecompressedSize));
|
|
}else{
|
|
printf(" (%u/%u) Skipped (%s): %s\n", file, filescount, "entry data is corrupt", destination);
|
|
continue;
|
|
}
|
|
|
|
if(mkpath(destination) != 0){
|
|
fprintf(stderr, "%sCould not create path '%s'.", "farextract: error: ", destination);
|
|
return -1;
|
|
}
|
|
if(!overwrite){
|
|
hFile = fopen(destination, "rb");
|
|
if(hFile != NULL){
|
|
/* File exists */
|
|
fclose(hFile);
|
|
printf(" (%u/%u) Skipped (%s): %s\n", file, filescount, "file exists", destination);
|
|
if(EntryNode->Entry.DecompressedData != EntryNode->Entry.CompressedData)
|
|
libfar_free(EntryNode->Entry.DecompressedData);
|
|
continue;
|
|
}
|
|
}
|
|
hFile = fopen(destination, "wb");
|
|
if(hFile == NULL){
|
|
printf(" (%u/%u) Skipped (%s): %s\n", file, filescount, "could not open", destination);
|
|
if(EntryNode->Entry.DecompressedData != EntryNode->Entry.CompressedData)
|
|
libfar_free(EntryNode->Entry.DecompressedData);
|
|
continue;
|
|
}
|
|
|
|
printf(" (%u/%u) %s (Group ID: 0x%08X, File ID: 0x%08X, Type ID: 0x%08X) (%u bytes)\n", file, filescount,
|
|
destination, EntryNode->Entry.GroupID, EntryNode->Entry.FileID, EntryNode->Entry.TypeID,
|
|
EntryNode->Entry.DecompressedSize);
|
|
|
|
fwrite(EntryNode->Entry.DecompressedData, 1, EntryNode->Entry.DecompressedSize, hFile);
|
|
fclose(hFile);
|
|
|
|
if(EntryNode->Entry.DecompressedData != EntryNode->Entry.CompressedData)
|
|
libfar_free(EntryNode->Entry.DecompressedData);
|
|
extracted++;
|
|
}
|
|
printf("\nFinished extracting %u of %u files in %.2f seconds.", extracted, filescount,
|
|
((float) (clock() - BeginningTime))/CLOCKS_PER_SEC);
|
|
}else{
|
|
/* Persist file */
|
|
PersistFile * PersistInfo;
|
|
|
|
/****
|
|
** Load header information
|
|
*/
|
|
|
|
PersistInfo = far_create_persist();
|
|
if(PersistInfo == NULL){
|
|
fprintf(stderr, "%sMemory for this archive could not be allocated.", "farextract: error: ");
|
|
return -1;
|
|
}
|
|
if(!far_read_persist_header(PersistInfo, ArchiveData, ArchiveSize)){
|
|
fprintf(stderr, "%sNot a valid archive.", "farextract: error: ");
|
|
return -1;
|
|
}
|
|
|
|
/****
|
|
** Extract the data
|
|
*/
|
|
printf("Extracting\n");
|
|
BeginningTime = clock();
|
|
if(!far_read_persist_data(PersistInfo, ArchiveData+18)){
|
|
fprintf(stderr, "%sNot a valid archive.", "farextract: error: ");
|
|
return -1;
|
|
}
|
|
EndingTime = clock();
|
|
|
|
if(!overwrite){
|
|
hFile = fopen(InFile, "rb");
|
|
if(hFile != NULL){
|
|
/* File exists */
|
|
fclose(hFile);
|
|
fprintf(stderr, "%sFile exists.", "farextract: error: ");
|
|
libfar_free(PersistInfo->DecompressedData);
|
|
return -1;
|
|
}
|
|
}
|
|
hFile = fopen(InFile, "wb");
|
|
if(hFile == NULL){
|
|
fprintf(stderr, "%sCould not open.", "farextract: error: ");
|
|
libfar_free(PersistInfo->DecompressedData);
|
|
return -1;
|
|
}
|
|
|
|
fwrite(PersistInfo->DecompressedData, 1, PersistInfo->DecompressedSize, hFile);
|
|
fclose(hFile);
|
|
printf("Extracted %u bytes in %.2f seconds.\n", PersistInfo->DecompressedSize,
|
|
((float) (EndingTime - BeginningTime))/CLOCKS_PER_SEC);
|
|
}
|
|
return 0;
|
|
} |