* Copyright 2005-2010, Ingo Weinhold, bonefish@users.sf.net.
* Distributed under the terms of the MIT License.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <List.h>
#include <Resources.h>
#include <StorageDefs.h>
#include <TypeConstants.h>
using namespace std;
static const char *kCommandName = "xres";
static const char *kDefaultResourceName = NULL;
static const char *kDefaultOutputFile = "xres.output.rsrc";
static const int kMaxSaneResourceSize = 100 * 1024 * 1024;
static int kArgc;
static const char *const *kArgv;
const char *kUsage =
"Usage: %s ( -h | --help )\n"
" %s -l <file> ...\n"
" %s <command> ...\n"
"\n"
"The first form prints this help text and exits.\n"
"\n"
"The second form lists the resources of all given files.\n"
"\n"
"The third form manipulates the resources of one or more files according to\n"
"the given commands.\n"
"\n"
"Valid commands are:\n"
" <input file>\n"
" - Add the resources read from file <input file> to the current\n"
" output file. The file can either be a resource file or an\n"
" executable file.\n"
" -a <type>:<id>[:<name>] ( <file> | -s <data> )\n"
" - Add a resource to the current output file. The added resource is\n"
" of type <type> and has the ID <id>. If given the resource will\n"
" have name <name>, otherwise it won't have a name. The resource\n"
" data will either be the string <data> provided on the command\n"
" line or the data read from file <file> (the whole contents).\n"
" -d <type>[:<id>]\n"
" - Excludes resources with type <type> and, if given, ID <id> from\n"
" being written to the output file. This applies to all resources\n"
" read from input files or directly specified via command \"-a\"\n"
" following this command until the next \"-d\" command.\n"
" -o <output file>\n"
" - Changes the output file to <output file>. All resources specified\n"
" by subsequent <input file> or \"-a\" commands will be written\n"
" to this file until the next output file is specified via the\n"
" \"-o\" command. Resources specified later overwrite earlier ones\n"
" with the same type and ID. If <output file> doesn't exist yet, \n"
" a resource file with the name will be created. If it exists and\n"
" is an executable file, the resources will be added to it (if the\n"
" file already has resources, they will be removed before). If it\n"
" is a resource file or a file of unknown type, it will be\n"
" overwritten with a resource file containing the specified\n"
" resources. The initial output file is \"xres.output.rsrc\".\n"
" Note that an output file will only be created or modified, if at\n"
" least one <input file> or \"-a\" command is given for it.\n"
" -x <type>[:<id>]\n"
" - Only resources with type <type> and, if given, ID <id> will be\n"
" written to the output file. This applies to all resources\n"
" read from input files or directly specified via command \"-a\"\n"
" following this command until the next \"-x\" command.\n"
" -- - All following arguments, even if starting with a \"-\" character,\n"
" are treated as input file names.\n"
"\n"
"Parameters:\n"
" <type> - A type constant consisting of exactly four characters.\n"
" <id> - A positive or negative integer.\n"
;
static const char *
resource_type(type_code type)
{
static char typeString[5];
typeString[0] = type >> 24;
typeString[1] = (type >> 16) & 0xff;
typeString[2] = (type >> 8) & 0xff;
typeString[3] = type & 0xff;
typeString[4] = '\0';
return typeString;
}
struct ResourceID {
type_code type;
int32 id;
bool wildcardID;
ResourceID(type_code type = B_ANY_TYPE, int32 id = 0,
bool wildcardID = true)
:
type(type),
id(id),
wildcardID(wildcardID)
{
}
ResourceID(const ResourceID &other)
{
*this = other;
}
bool Matches(const ResourceID &other) const
{
return ((type == other.type || type == B_ANY_TYPE)
&& (wildcardID || id == other.id));
}
ResourceID &operator=(const ResourceID &other)
{
type = other.type;
id = other.id;
wildcardID = other.wildcardID;
return *this;
}
};
struct ResourceDataSource {
ResourceDataSource()
{
}
virtual ~ResourceDataSource()
{
}
virtual void GetData(const void *&data, size_t &size) = 0;
virtual void Flush()
{
}
};
struct MemoryResourceDataSource : ResourceDataSource {
MemoryResourceDataSource(const void *data, size_t size, bool clone)
{
_Init(data, size, clone);
}
MemoryResourceDataSource(const char *data, bool clone)
{
_Init(data, strlen(data) + 1, clone);
}
virtual ~MemoryResourceDataSource()
{
if (fOwner)
delete[] fData;
}
virtual void GetData(const void *&data, size_t &size)
{
data = fData;
size = fSize;
}
private:
void _Init(const void *data, size_t size, bool clone)
{
if (clone) {
fData = new uint8[size];
memcpy(fData, data, size);
fSize = size;
fOwner = true;
} else {
fData = (uint8*)data;
fSize = size;
fOwner = false;
}
}
private:
uint8 *fData;
size_t fSize;
bool fOwner;
};
struct FileResourceDataSource : ResourceDataSource {
FileResourceDataSource(const char *path)
:
fPath(path),
fData(NULL),
fSize(0)
{
}
virtual ~FileResourceDataSource()
{
Flush();
}
virtual void GetData(const void *&_data, size_t &_size)
{
if (!fData) {
BFile file;
status_t error = file.SetTo(fPath.c_str(), B_READ_ONLY);
if (error != B_OK) {
fprintf(stderr, "Error: Failed to open file \"%s\": %s\n",
fPath.c_str(), strerror(error));
exit(1);
}
off_t size;
error = file.GetSize(&size);
if (error != B_OK) {
fprintf(stderr, "Error: Failed to get size of file \"%s\": "
"%s\n", fPath.c_str(), strerror(error));
exit(1);
}
if (size > kMaxSaneResourceSize) {
fprintf(stderr, "Error: Resource data file \"%s\" is too big\n",
fPath.c_str());
exit(1);
}
fData = new uint8[size];
fSize = size;
ssize_t bytesRead = file.ReadAt(0, fData, fSize);
if (bytesRead < 0) {
fprintf(stderr, "Error: Failed to read data size from file "
"\"%s\": %s\n", fPath.c_str(), strerror(bytesRead));
exit(1);
}
}
_data = fData;
_size = fSize;
}
virtual void Flush()
{
if (fData) {
delete[] fData;
fData = NULL;
}
}
private:
string fPath;
uint8 *fData;
size_t fSize;
};
struct State {
State()
{
}
virtual ~State()
{
}
virtual void SetOutput(const char *path)
{
(void)path;
}
virtual void ProcessInput(const char *path)
{
(void)path;
}
virtual void SetInclusionPattern(const ResourceID &pattern)
{
(void)pattern;
}
virtual void SetExclusionPattern(const ResourceID &pattern)
{
(void)pattern;
}
virtual void AddResource(const ResourceID &id, const char *name,
ResourceDataSource *dataSource)
{
(void)id;
(void)name;
(void)dataSource;
}
};
struct ListState : State {
ListState()
{
}
virtual ~ListState()
{
}
virtual void ProcessInput(const char *path)
{
BFile file;
status_t error = file.SetTo(path, B_READ_ONLY);
if (error != B_OK) {
fprintf(stderr, "Error: Failed to open file \"%s\": %s\n", path,
strerror(error));
exit(1);
}
BResources resources;
error = resources.SetTo(&file, false);
if (error != B_OK) {
if (error == B_ERROR) {
fprintf(stderr, "Error: File \"%s\" is not a resource file.\n",
path);
} else {
fprintf(stderr, "Error: Failed to read resources from file "
"\"%s\": %s\n", path, strerror(error));
}
exit(1);
}
printf("\n%s resources:\n\n", path);
printf(" type ID size name\n");
printf("------ ----------- ----------- --------------------\n");
type_code type;
int32 id;
const char *name;
size_t size;
for (int32 i = 0;
resources.GetResourceInfo(i, &type, &id, &name, &size); i++) {
printf("'%s' %11" B_PRId32 " %11" B_PRIuSIZE " %s\n",
resource_type(type), id, size,
name != NULL && name[0] != '\0' ? name : "(no name)");
}
}
};
struct WriteFileState : State {
WriteFileState()
:
fOutputFilePath(kDefaultOutputFile),
fResources(NULL),
fInclusionPattern(NULL),
fExclusionPattern(NULL)
{
}
virtual ~WriteFileState()
{
_FlushOutput();
}
virtual void SetOutput(const char *path)
{
_FlushOutput();
fOutputFilePath = path;
}
virtual void ProcessInput(const char *path)
{
BFile file;
status_t error = file.SetTo(path, B_READ_ONLY);
if (error != B_OK) {
fprintf(stderr, "Error: Failed to open input file \"%s\": %s\n",
path, strerror(error));
exit(1);
}
BResources resources;
error = resources.SetTo(&file, false);
if (error != B_OK) {
if (error == B_ERROR) {
fprintf(stderr, "Error: Input file \"%s\" is not a resource "
"file.\n", path);
} else {
fprintf(stderr, "Error: Failed to read resources from input "
"file \"%s\": %s\n", path, strerror(error));
}
exit(1);
}
resources.PreloadResourceType();
type_code type;
int32 id;
const char *name;
size_t size;
for (int32 i = 0;
resources.GetResourceInfo(i, &type, &id, &name, &size);
i++) {
const void *data = resources.LoadResource(type, id, &size);
if (!data) {
fprintf(stderr, "Error: Failed to read resources from input "
"file \"%s\".\n", path);
exit(1);
}
MemoryResourceDataSource dataSource(data, size, false);
AddResource(ResourceID(type, id), name, &dataSource);
}
}
virtual void SetInclusionPattern(const ResourceID &pattern)
{
if (!fInclusionPattern)
fInclusionPattern = new ResourceID;
*fInclusionPattern = pattern;
}
virtual void SetExclusionPattern(const ResourceID &pattern)
{
if (!fExclusionPattern)
fExclusionPattern = new ResourceID;
*fExclusionPattern = pattern;
}
virtual void AddResource(const ResourceID &id, const char *name,
ResourceDataSource *dataSource)
{
_PrepareOutput();
if ((fInclusionPattern && !fInclusionPattern->Matches(id))
|| (fExclusionPattern && fExclusionPattern->Matches(id))) {
return;
}
const void *data;
size_t size;
dataSource->GetData(data, size);
status_t error = fResources->AddResource(id.type, id.id, data, size,
name);
if (error != B_OK) {
fprintf(stderr, "Error: Failed to add resource type '%s', ID %"
B_PRId32 " to output file \"%s\": %s\n", resource_type(id.type),
id.id, fOutputFilePath.c_str(), strerror(error));
exit(1);
}
}
private:
void _FlushOutput()
{
if (fResources) {
status_t error = fResources->Sync();
if (error != B_OK) {
fprintf(stderr, "Error: Failed to write resources to output "
"file \"%s\": %s\n", fOutputFilePath.c_str(),
strerror(error));
exit(1);
}
delete fResources;
fResources = NULL;
}
}
void _PrepareOutput()
{
if (fResources)
return;
BFile file;
status_t error = file.SetTo(fOutputFilePath.c_str(),
B_READ_WRITE | B_CREATE_FILE);
if (error != B_OK) {
fprintf(stderr, "Error: Failed to open output file \"%s\": %s\n",
fOutputFilePath.c_str(), strerror(error));
exit(1);
}
fResources = new BResources;
error = fResources->SetTo(&file, true);
if (error != B_OK) {
fprintf(stderr, "Error: Failed to init resources for output "
"file \"%s\": %s\n", fOutputFilePath.c_str(), strerror(error));
exit(1);
}
}
private:
string fOutputFilePath;
BResources *fResources;
ResourceID *fInclusionPattern;
ResourceID *fExclusionPattern;
};
struct Command {
Command()
{
}
virtual ~Command()
{
}
virtual void Do(State *state) = 0;
};
struct SetOutputCommand : Command {
SetOutputCommand(const char *path)
:
Command(),
fPath(path)
{
}
virtual void Do(State *state)
{
state->SetOutput(fPath.c_str());
}
private:
string fPath;
};
struct ProcessInputCommand : Command {
ProcessInputCommand(const char *path)
:
Command(),
fPath(path)
{
}
virtual void Do(State *state)
{
state->ProcessInput(fPath.c_str());
}
private:
string fPath;
};
struct SetResourcePatternCommand : Command {
SetResourcePatternCommand(const ResourceID &pattern, bool inclusion)
:
Command(),
fPattern(pattern),
fInclusion(inclusion)
{
}
virtual void Do(State *state)
{
if (fInclusion)
state->SetInclusionPattern(fPattern);
else
state->SetExclusionPattern(fPattern);
}
private:
ResourceID fPattern;
bool fInclusion;
};
struct AddResourceCommand : Command {
AddResourceCommand(const ResourceID &id, const char *name,
ResourceDataSource *dataSource)
:
Command(),
fID(id),
fHasName(name),
fDataSource(dataSource)
{
if (fHasName)
fName = name;
}
virtual ~AddResourceCommand()
{
delete fDataSource;
}
virtual void Do(State *state)
{
state->AddResource(fID, (fHasName ? fName.c_str() : NULL),
fDataSource);
fDataSource->Flush();
}
private:
ResourceID fID;
string fName;
bool fHasName;
ResourceDataSource *fDataSource;
};
static void
print_usage(bool error)
{
const char *commandName = NULL;
if (kArgc > 0) {
if (const char *lastSlash = strchr(kArgv[0], '/'))
commandName = lastSlash + 1;
else
commandName = kArgv[0];
}
if (!commandName || commandName[0] == '\0')
commandName = kCommandName;
fprintf((error ? stderr : stdout), kUsage, commandName, commandName,
commandName);
}
static void
print_usage_and_exit(bool error)
{
print_usage(error);
exit(error ? 1 : 0);
}
static const char *
next_arg(int &argi, bool optional = false)
{
if (argi >= kArgc) {
if (!optional)
print_usage_and_exit(true);
return NULL;
}
return kArgv[argi++];
}
static void
parse_resource_id(const char *toParse, ResourceID &resourceID,
const char **name = NULL)
{
int len = strlen(toParse);
if (len < 4)
print_usage_and_exit(true);
resourceID.type = ((int32)toParse[0] << 24) | ((int32)toParse[1] << 16)
| ((int32)toParse[2] << 8) | (int32)toParse[3];
if (toParse[4] == '\0') {
if (name)
print_usage_and_exit(true);
resourceID.id = 0;
resourceID.wildcardID = true;
return;
}
if (toParse[4] != ':')
print_usage_and_exit(true);
toParse += 5;
len -= 5;
bool negative = false;
if (*toParse == '-') {
negative = true;
toParse++;
len--;
}
if (*toParse < '0' || *toParse > '9')
print_usage_and_exit(true);
int id = 0;
while (*toParse >= '0' && *toParse <= '9') {
id = 10 * id + (*toParse - '0');
toParse++;
len--;
}
resourceID.wildcardID = false;
resourceID.id = (negative ? -id : id);
if (*toParse == '\0') {
if (name) {
*name = kDefaultResourceName;
return;
}
}
if (*toParse != ':')
print_usage_and_exit(true);
*name = toParse + 1;
}
int
main(int argc, const char *const *argv)
{
kArgc = argc;
kArgv = argv;
if (argc < 2)
print_usage_and_exit(true);
BList commandList;
bool noMoreOptions = false;
bool list = false;
bool noList = false;
bool hasInputFiles = false;
for (int argi = 1; argi < argc; ) {
const char *arg = argv[argi++];
if (!noMoreOptions && arg[0] == '-') {
if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0)
print_usage_and_exit(false);
if (strlen(arg) != 2)
print_usage_and_exit(true);
switch (arg[1]) {
case 'a':
{
noList = true;
const char *typeString = next_arg(argi);
ResourceID resourceID;
const char *name = NULL;
parse_resource_id(typeString, resourceID, &name);
const char *file = next_arg(argi);
ResourceDataSource *dataSource;
if (strcmp(file, "-s") == 0) {
const char *data = next_arg(argi);
dataSource = new MemoryResourceDataSource(data, false);
} else {
dataSource = new FileResourceDataSource(file);
}
Command *command = new AddResourceCommand(resourceID,
name, dataSource);
commandList.AddItem(command);
break;
}
case 'd':
{
noList = true;
const char *typeString = next_arg(argi);
ResourceID pattern;
parse_resource_id(typeString, pattern);
Command *command = new SetResourcePatternCommand(pattern,
false);
commandList.AddItem(command);
break;
}
case 'l':
{
list = true;
break;
}
case 'o':
{
noList = true;
const char *out = next_arg(argi);
Command *command = new SetOutputCommand(out);
commandList.AddItem(command);
break;
}
case 'x':
{
noList = true;
const char *typeString = next_arg(argi);
ResourceID pattern;
parse_resource_id(typeString, pattern);
Command *command = new SetResourcePatternCommand(pattern,
true);
commandList.AddItem(command);
break;
}
case '-':
noMoreOptions = true;
break;
default:
print_usage_and_exit(true);
break;
}
} else {
hasInputFiles = true;
Command *command = new ProcessInputCommand(arg);
commandList.AddItem(command);
}
}
if ((list && noList) || (list && !hasInputFiles))
print_usage_and_exit(true);
State *state;
if (list)
state = new ListState();
else
state = new WriteFileState();
for (int32 i = 0; Command *command = (Command*)commandList.ItemAt(i); i++)
command->Do(state);
delete state;
return 0;
}