diff options
Diffstat (limited to 'tools/narccomp/narccomp.cpp')
-rw-r--r-- | tools/narccomp/narccomp.cpp | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/tools/narccomp/narccomp.cpp b/tools/narccomp/narccomp.cpp new file mode 100644 index 00000000..2cbbeb4c --- /dev/null +++ b/tools/narccomp/narccomp.cpp @@ -0,0 +1,261 @@ +#include <iostream> +#include <fstream> +#include <vector> +#include <string> +#include <cstring> +#include <sstream> +#include <map> +#include <set> + +using namespace std; + +struct Options +{ + fstream outfile; + map<string, string> infiles; + unsigned char padval; + bool keep_names; + string host_root; + string nitro_root; + Options(int const & argc, char ** const & argv) : + // Default values + padval(255), + keep_names(false) + { + char last_opt = 0; + string outfname; // Use this to avoid clobbering an existing file in the event of an error + for (string arg : vector<char *>(argv + 1, argv + argc)) + { + if (last_opt != 0) { + switch (last_opt) + { + case 'p': + { + // Padding + unsigned long x; + stringstream is(arg); + is >> x; + if (x >= 256) { + stringstream ss; + ss << "Invalid value for -p: " << x << endl; + throw invalid_argument(ss.str()); + } + padval = x; + break; + } + case 'o': + // Outfile + if (!outfname.empty()) { + throw invalid_argument("Can't output more than one file\n"); + } + outfname = arg; + break; + case 'r': + // Not fully implemented + if (!host_root.empty()) { + throw invalid_argument("Duplicate settings for option -r\n"); + } + host_root = arg; + break; + case 'R': + // Not fully implemented + if (!nitro_root.empty()) { + throw invalid_argument("Duplicate settings for option -R\n"); + } + nitro_root = arg; + break; + default: + stringstream ss; + ss << "Unrecognized option: -" << (char)last_opt << endl; + throw invalid_argument(ss.str()); + } + last_opt = 0; + } + else if (arg[0] == '-') { + if (arg[1] == 0 || arg[2] != 0) { + stringstream ss; + ss << "Unrecognized option: " << arg << endl; + throw invalid_argument(ss.str()); + } + switch (arg[1]) { + case 'h': + cout << "Usage: " << argv[0] << " [-p PADVAL] [-h] -o OUTFILE FILE [FILE ...]" << endl; + cout << endl; + cout << " FILE [FILE ...] List of files to be packed into the archive." << endl; + cout << " -o OUTFILE The destination archive, in NARC format" << endl; + cout << " -p PADVAL Pad archive members to word with this character. Default: 255" << endl; + cout << " -h Print this message and exit" << endl; + exit(0); + case 'k': + throw invalid_argument("'-k': not implemented\n"); +// if (keep_names) { +// throw invalid_argument("'-k' specified more than once\n"); +// } +// keep_names = true; +// break; + default: + last_opt = arg[1]; + } + } + else + { + fstream file; + stringstream uss; + + if (!infiles[arg].empty()) { + stringstream ss; + ss << "Duplicate file: " << arg << endl; + throw invalid_argument(ss.str()); + } + file.open(arg, ios_base::in | ios_base::binary); + uss << file.rdbuf(); + file.close(); + infiles[arg] = uss.str(); + } + } + if (outfname.empty()) { + throw invalid_argument("Must specify an output file (-o OUTFILE)\n"); + } + if (infiles.empty()) { + throw invalid_argument("No input files\n"); + } + outfile.open(outfname, ios_base::binary | ios_base::out); + } +}; + +struct FatbEntry +{ + uint32_t start; + uint32_t end; +}; + +struct FatbHeader +{ + char magic[4]; + uint32_t chunk_size; + uint16_t file_count; + char padding[2]; + FatbHeader(uint16_t numfiles) : + chunk_size(sizeof(FatbHeader) + sizeof(FatbEntry) * numfiles), + file_count(numfiles) + { strncpy(magic, "BTAF", 4); memset(padding, 0, 2); } +}; + +struct FntbDirHeader +{ + uint32_t offset; + uint16_t first_file; + uint16_t id_or_count; +}; + +struct FntbHeader +{ + char magic[4]; + uint32_t chunk_size; + FntbHeader() : + chunk_size(sizeof(FntbHeader) + sizeof(FntbDirHeader)) + { strncpy(magic, "BTNF", 4); } + string set_names(Options*& opt, FntbDirHeader*& dirHeader) { + throw runtime_error("not implemented\n"); +// typedef struct NitroFS { +// bool is_dir; +// vector<struct NitroFS> children; +// } nitrofs_t; +// map<string, nitrofs_t> fs; +// fs[opt->nitro_root].is_dir = true; +// string host_root; +// for (auto pair : opt->infiles) { +// string fname = pair.first; +// fname = fname.replace(0, opt->host_root.size(), opt->nitro_root); +// int pos; +// while ((pos = fname.find("//")) != fname.npos) { +// fname = fname.replace(pos, 2, 1, '/'); +// } +// } +// return nullptr; + } +}; + +#define FNT_END 0 +#define FNT_LENGTH_MASK 0x7F +#define FNT_IS_DIR_MASK 0x80 + +struct FimgHeader +{ + char magic[4]; + uint32_t chunk_size; + FimgHeader(size_t image_size) : + chunk_size(sizeof(FimgHeader) + image_size) + { strncpy(magic, "GMIF", 4); } +}; + +struct NarcHeader +{ + char magic[4]; // NARC + uint16_t bom; + uint16_t version; + uint32_t size; + uint16_t chunk_size; + uint16_t num_chunks; + NarcHeader(uint16_t numfiles, size_t image_size) : + bom(0xFFFE), + version(0x0100), + size(sizeof(NarcHeader) + sizeof(FatbHeader) + sizeof(FatbEntry) * numfiles + sizeof(FntbHeader) + sizeof(FntbDirHeader) + sizeof(FimgHeader) + image_size), + chunk_size(sizeof(NarcHeader)), + num_chunks(3) + { strncpy(magic, "NARC", 4); } +}; + +int main(int argc, char ** argv) +{ + try + { + auto opt = new Options(argc, argv); + size_t nfiles = opt->infiles.size(); + auto fatb = new FatbEntry[nfiles]; + FatbEntry *entry = fatb; + for (auto pair : opt->infiles) + { + entry->start = (entry == fatb ? 0 : (entry[-1].end + 3) & ~3); + entry->end = entry->start + pair.second.size(); + entry++; + } + size_t image_size = (fatb[nfiles - 1].end + 3) & ~3; + FntbHeader fntbHeader; + auto fntbDir = new FntbDirHeader; + *fntbDir = (FntbDirHeader) {4, 0, 1}; // dummy + stringstream fnt; + if (opt->keep_names) + { + fntbHeader.set_names(opt, fntbDir); + } + NarcHeader narcHeader(nfiles, (fatb[nfiles - 1].end + 3) & ~3); + FatbHeader fatbHeader(nfiles); + FimgHeader fimgHeader((fatb[nfiles - 1].end + 3) & ~3); + opt->outfile.write((char *) &narcHeader, sizeof(NarcHeader)); + opt->outfile.write((char *) &fatbHeader, sizeof(FatbHeader)); + opt->outfile.write((char *) fatb, sizeof(FatbEntry) * nfiles); + opt->outfile.write((char *) &fntbHeader, sizeof(FntbHeader)); + opt->outfile.write((char *) fntbDir, sizeof(FntbDirHeader)); + opt->outfile.write((char *) &fimgHeader, sizeof(FimgHeader)); + char padding[4]; + memset(padding, opt->padval, 4); + for (auto pair : opt->infiles) + { + stringstream uss(pair.second); + opt->outfile.write(pair.second.c_str(), pair.second.size()); + opt->outfile.write(padding, (4 - pair.second.size()) & 3); + } + opt->outfile.close(); + return 0; + } catch (invalid_argument e) { + cerr << "Invalid exception: " << e.what() << endl; + return 1; + } catch (runtime_error e) { + cerr << "Runtime error: " << e.what() << endl; + return 2; + } catch (exception e) { + cerr << "unhandled exception caught" << endl; + return 3; + } +} |