コード例 #1
0
ファイル: gamemap.cpp プロジェクト: Malvineous/libgamemaps
int main(int iArgC, char *cArgV[])
{
#ifdef __GLIBCXX__
	// Set a better exception handler
	std::set_terminate(__gnu_cxx::__verbose_terminate_handler);
#endif

	// Disable stdin/printf/etc. sync for a speed boost
	std::ios_base::sync_with_stdio(false);

	// Declare the supported options.
	po::options_description poActions("Actions");
	poActions.add_options()
		("info,i",
			"display information about the map, including attributes/metadata")

		("print,p", po::value<int>(),
			"print the given layer in ASCII")

		("render,r", po::value<std::string>(),
			"render the map to the given .png file")
	;

	po::options_description poOptions("Options");
	poOptions.add_options()
		("type,t", po::value<std::string>(),
			"specify the map type (default is autodetect)")
		("graphics,g", po::value<std::string>(),
			"filename storing game graphics (required with --render)")
		("script,s",
			"format output suitable for script parsing")
		("force,f",
			"force open even if the map is not in the given format")
		("list-types",
			"list supported file types")
	;

	po::options_description poHidden("Hidden parameters");
	poHidden.add_options()
		("map", "map file to manipulate")
		("help", "produce help message")
	;

	po::options_description poVisible("");
	poVisible.add(poActions).add(poOptions);

	po::options_description poComplete("Parameters");
	poComplete.add(poActions).add(poOptions).add(poHidden);
	po::variables_map mpArgs;

	std::string strFilename, strType;
	std::map<gm::ImagePurpose, gm::Map::GraphicsFilename> manualGfx;

	bool bScript = false; // show output suitable for script parsing?
	bool bForceOpen = false; // open anyway even if map not in given format?
	int iRet = RET_OK;
	try {
		po::parsed_options pa = po::parse_command_line(iArgC, cArgV, poComplete);

		// Parse the global command line options
		for (auto& i : pa.options) {
			if (i.string_key.empty()) {
				// If we've already got an map filename, complain that a second one
				// was given (probably a typo.)
				if (!strFilename.empty()) {
					std::cerr << "Error: unexpected extra parameter (multiple map "
						"filenames given?!)" << std::endl;
					return 1;
				}
				assert(i.value.size() > 0);  // can't have no values with no name!
				strFilename = i.value[0];
			} else if (i.string_key.compare("help") == 0) {
				std::cout <<
					"Copyright (C) 2010-2015 Adam Nielsen <*****@*****.**>\n"
					"This program comes with ABSOLUTELY NO WARRANTY.  This is free software,\n"
					"and you are welcome to change and redistribute it under certain conditions;\n"
					"see <http://www.gnu.org/licenses/> for details.\n"
					"\n"
					"Utility to manipulate map files used by games to store data files.\n"
					"Build date " __DATE__ " " __TIME__ << "\n"
					"\n"
					"Usage: gamemap <map> <action> [action...]\n" << poVisible << "\n"
					<< std::endl;
				return RET_OK;
			} else if (
				(i.string_key.compare("t") == 0) ||
				(i.string_key.compare("type") == 0)
			) {
				strType = i.value[0];
			} else if (
				(i.string_key.compare("g") == 0) ||
				(i.string_key.compare("graphics") == 0)
			) {
				std::string purpose, temp;
				gm::Map::GraphicsFilename gf;
				bool a = split(i.value[0], '=', &purpose, &temp);
				bool b = split(temp, ':', &gf.type, &gf.filename);
				if (!a || !b) {
					std::cerr << "Malformed -g/--graphics parameter.  Must be of the "
						"form purpose=type:filename.\n"
						"Use --help or --list-types for details." << std::endl;
					return RET_BADARGS;
				}
				bool found = false;
				for (unsigned int i = 0; i < (unsigned int)gm::ImagePurpose::ImagePurposeCount; i++) {
					gm::ImagePurpose p = (gm::ImagePurpose)i;
					if (purpose.compare(toString(p)) == 0) {
						manualGfx[p] = gf;
						found = true;
					}
				}
				if (!found) {
					std::cerr << "No match for tileset purpose: " << purpose << "\n"
						<< "Use --list-types for details." << std::endl;
					return RET_BADARGS;
				}
			} else if (
				(i.string_key.compare("s") == 0) ||
				(i.string_key.compare("script") == 0)
			) {
				bScript = true;
			} else if (
				(i.string_key.compare("f") == 0) ||
				(i.string_key.compare("force") == 0)
			) {
				bForceOpen = true;
			} else if (
				(i.string_key.compare("list-types") == 0)
			) {
				std::cout << "Tileset purposes: (--graphics purpose=type:file)\n";
				for (unsigned int i = 0; i < (unsigned int)gm::ImagePurpose::ImagePurposeCount; i++) {
					gm::ImagePurpose p = (gm::ImagePurpose)i;
					std::cout << "  " << toString(p) << "\n";
				}

				std::cout << "\nTileset types: (--graphics purpose=type:file)\n";
				for (auto& tilesetType : gg::TilesetManager::formats()) {
					std::string code = tilesetType->code();
					std::cout << "  " << code;
					int len = code.length();
					if (len < 20) std::cout << std::string(20-code.length(), ' ');
					std::cout << ' ' << tilesetType->friendlyName();
					auto ext = tilesetType->fileExtensions();
					if (ext.size()) {
						auto i = ext.begin();
						std::cout << " (*." << *i;
						for (i++; i != ext.end(); i++) {
							std::cout << "; *." << *i;
						}
						std::cout << ")";
					}
					std::cout << '\n';
				}

				std::cout << "\nMap types: (--type)\n";
				for (auto& mapType : gm::MapManager::formats()) {
					std::string code = mapType->code();
					std::cout << "  " << code;
					int len = code.length();
					if (len < 20) std::cout << std::string(20 - code.length(), ' ');
					std::cout << ' ' << mapType->friendlyName();
					auto ext = mapType->fileExtensions();
					if (ext.size()) {
						auto i = ext.begin();
						std::cout << " (*." << *i;
						for (i++; i != ext.end(); i++) {
							std::cout << "; *." << *i;
						}
						std::cout << ")";
					}
					std::cout << '\n';
				}

				return RET_OK;
			}
		}

		if (strFilename.empty()) {
			std::cerr << "Error: no game map filename given" << std::endl;
			return RET_BADARGS;
		}
		std::cout << "Opening " << strFilename << " as type "
			<< (strType.empty() ? "<autodetect>" : strType) << std::endl;

		std::unique_ptr<stream::inout> content;
		try {
			content = std::make_unique<stream::file>(strFilename, false);
		} catch (const stream::open_error& e) {
			std::cerr << "Error opening " << strFilename << ": " << e.what()
				<< std::endl;
			return RET_SHOWSTOPPER;
		}

		gm::MapManager::handler_t mapType;
		if (strType.empty()) {
			// Need to autodetect the file format.
			for (auto& mapTestType : gm::MapManager::formats()) {
				gm::MapType::Certainty cert = mapTestType->isInstance(*content);
				switch (cert) {
					case gm::MapType::Certainty::DefinitelyNo:
						// Don't print anything (TODO: Maybe unless verbose?)
						break;
					case gm::MapType::Certainty::Unsure:
						std::cout << "File could be a " << mapTestType->friendlyName()
							<< " [" << mapTestType->code() << "]" << std::endl;
						// If we haven't found a match already, use this one
						if (!mapType) mapType = mapTestType;
						break;
					case gm::MapType::Certainty::PossiblyYes:
						std::cout << "File is likely to be a " << mapTestType->friendlyName()
							<< " [" << mapTestType->code() << "]" << std::endl;
						// Take this one as it's better than an uncertain match
						mapType = mapTestType;
						break;
					case gm::MapType::Certainty::DefinitelyYes:
						std::cout << "File is definitely a " << mapTestType->friendlyName()
							<< " [" << mapTestType->code() << "]" << std::endl;
						mapType = mapTestType;
						// Don't bother checking any other formats if we got a 100% match
						goto finishTesting;
				}
				if (cert != gm::MapType::Certainty::DefinitelyNo) {
					// We got a possible match, see if it requires any suppdata
					auto suppList = mapTestType->getRequiredSupps(*content, strFilename);
					if (suppList.size() > 0) {
						// It has suppdata, see if it's present
						std::cout << "  * This format requires supplemental files..." << std::endl;
						bool bSuppOK = true;
						for (auto& i : suppList) {
							try {
								auto suppStream = std::make_unique<stream::file>(i.second, false);
							} catch (const stream::open_error&) {
								bSuppOK = false;
								std::cout << "  * Could not find/open " << i.second
									<< ", map is probably not "
									<< mapTestType->code() << std::endl;
								break;
							}
						}
						if (bSuppOK) {
							// All supp files opened ok
							std::cout << "  * All supp files present, map is likely "
								<< mapTestType->code() << std::endl;
							// Set this as the most likely format
							mapType = mapTestType;
						}
					}
				}
			}
finishTesting:
			if (!mapType) {
				std::cerr << "Unable to automatically determine the file type.  Use "
					"the --type option to manually specify the file format." << std::endl;
				return RET_BE_MORE_SPECIFIC;
			}
		} else {
			mapType = gm::MapManager::byCode(strType);
			if (!mapType) {
				std::cerr << "Unknown file type given to -t/--type: " << strType
					<< std::endl;
				return RET_BADARGS;
			}
		}

		assert(mapType != NULL);

		// Check to see if the file is actually in this format
		if (!mapType->isInstance(*content)) {
			if (bForceOpen) {
				std::cerr << "Warning: " << strFilename << " is not a "
					<< mapType->friendlyName() << ", open forced." << std::endl;
			} else {
				std::cerr << "Invalid format: " << strFilename << " is not a "
					<< mapType->friendlyName() << "\n"
					<< "Use the -f option to try anyway." << std::endl;
				return RET_BE_MORE_SPECIFIC;
			}
		}

		// See if the format requires any supplemental files
		camoto::SuppData suppData;
		for (auto& i : mapType->getRequiredSupps(*content, strFilename)) {
			try {
				std::cerr << "Opening supplemental file " << i.second << std::endl;
				suppData[i.first] = std::make_unique<stream::file>(i.second, false);
			} catch (const stream::open_error& e) {
				std::cerr << "Error opening supplemental file " << i.second << ": "
					<< e.what() << std::endl;
				// Continue anyway in case the file is optional
			}
		}

		// Open the map file
		std::shared_ptr<gm::Map> pMap = mapType->open(std::move(content), suppData);
		assert(pMap);

		// File type of inserted files defaults to empty, which means 'generic file'
		std::string strLastFiletype;

		// Run through the actions on the command line
		for (auto& i : pa.options) {
			if (i.string_key.compare("info") == 0) {
				listAttributes(pMap.get(), bScript);

				std::cout << (bScript ? "gfx_filename_count=" : "Number of graphics filenames: ")
					<< pMap->graphicsFilenames().size() << "\n";
				int fileNum = 0;
				for (auto& a : pMap->graphicsFilenames()) {
					if (bScript) {
						std::cout << "gfx_file" << fileNum << "_name=" << a.second.filename << "\n";
						std::cout << "gfx_file" << fileNum << "_type=" << a.second.type << "\n";
						std::cout << "gfx_file" << fileNum << "_purpose=" << (unsigned int)a.first << "\n";
					} else {
						std::cout << "Graphics file " << fileNum+1 << ": " << a.second.filename
							<< " [";
						switch (a.first) {
							case gm::ImagePurpose::GenericTileset1:    std::cout << "Generic tileset 1"; break;
							case gm::ImagePurpose::BackgroundImage:    std::cout << "Background image"; break;
							case gm::ImagePurpose::BackgroundTileset1: std::cout << "Background tileset 1"; break;
							case gm::ImagePurpose::BackgroundTileset2: std::cout << "Background tileset 2"; break;
							case gm::ImagePurpose::ForegroundTileset1: std::cout << "Foreground tileset 1"; break;
							case gm::ImagePurpose::ForegroundTileset2: std::cout << "Foreground tileset 2"; break;
							case gm::ImagePurpose::SpriteTileset1:     std::cout << "Sprite tileset 1"; break;
							case gm::ImagePurpose::FontTileset1:       std::cout << "Font tileset 1"; break;
							case gm::ImagePurpose::FontTileset2:       std::cout << "Font tileset 2"; break;
							default:
								std::cout << "Unknown purpose <fix this>";
								break;
						}
						std::cout << " of type " << a.second.type << "]\n";
					}
					fileNum++;
				}

				std::cout << (bScript ? "map_type=" : "Map type: ");
				auto map2d = std::dynamic_pointer_cast<gm::Map2D>(pMap);
				if (map2d) {
					std::cout << (bScript ? "2d" : "2D grid-based") << "\n";
#define CAP(o, c, v)        " " __STRING(c) << ((v & o::Caps::c) ? '+' : '-')
#define MAP2D_CAP(c)        CAP(gm::Map2D,        c, mapCaps)
#define MAP2D_LAYER_CAP(c)  CAP(gm::Map2D::Layer, c, layerCaps)

					auto mapCaps = map2d->caps();
					if (bScript) {
						std::cout << "map_caps=" << (unsigned int)mapCaps << "\n";
					} else {
						std::cout << "Map capabilities:"
							<< MAP2D_CAP(HasViewport)
							<< MAP2D_CAP(HasMapSize)
							<< MAP2D_CAP(SetMapSize)
							<< MAP2D_CAP(HasTileSize)
							<< MAP2D_CAP(SetTileSize)
							<< MAP2D_CAP(AddPaths)
							<< "\n"
						;
					}
					auto mapTileSize = map2d->tileSize();
					std::cout << (bScript ? "tile_width=" : "Tile size: ") << mapTileSize.x
						<< (bScript ? "\ntile_height=" : "x") << mapTileSize.y << "\n";

					auto mapSize = map2d->mapSize();
					std::cout
						<< (bScript ? "map_width=" : "Map size: ") << mapSize.x
						<< (bScript ? "\nmap_height=" : "x") << mapSize.y
						<< (bScript ? "" : " tiles")
						<< "\n";

					if (mapCaps & gm::Map2D::Caps::HasViewport) {
						auto vp = map2d->viewport();
						std::cout << (bScript ? "viewport_width=" : "Viewport size: ")
							<< vp.x
							<< (bScript ? "\nviewport_height=" : "x") << vp.y
							<< (bScript ? "" : " pixels") << "\n";
					}

					unsigned int layerCount = map2d->layers().size();
					std::cout << (bScript ? "layercount=" : "Layer count: ")
						<< layerCount << "\n";
					unsigned int layerIndex = 0;
					for (auto& layer : map2d->layers()) {
						std::string prefix;
						if (bScript) {
							std::stringstream ss;
							ss << "layer" << layerIndex << '_';
							prefix = ss.str();
							std::cout << prefix << "name=" << layer->title() << "\n";
						} else {
							prefix = "  ";
							std::cout << "Layer " << layerIndex + 1 << ": \"" << layer->title()
								<< "\"\n";
						}
						auto layerCaps = layer->caps();
						if (bScript) std::cout << prefix << "caps="
							<< (unsigned int)layerCaps << "\n";
						else std::cout << prefix << "Capabilities:"
							<< MAP2D_LAYER_CAP(HasOwnSize)
							<< MAP2D_LAYER_CAP(SetOwnSize)
							<< MAP2D_LAYER_CAP(HasOwnTileSize)
							<< MAP2D_LAYER_CAP(SetOwnTileSize)
							<< MAP2D_LAYER_CAP(HasPalette)
							<< MAP2D_LAYER_CAP(UseImageDims)
							<< "\n"
						;

						gg::Point layerTileSize;
						bool layerTileSame;
						if (layerCaps & gm::Map2D::Layer::Caps::HasOwnTileSize) {
							layerTileSize = layer->tileSize();
							layerTileSame = false;
						} else {
							layerTileSize = mapTileSize;
							layerTileSame = true;
						}
						std::cout << prefix << (bScript ? "tile_width=" : "Tile size: ")
							<< layerTileSize.x;
						if (bScript) std::cout << "\n" << prefix << "tile_height=";
						else std::cout << "x";
						std::cout << layerTileSize.y;
						if (layerTileSame && (!bScript)) {
							std::cout << " (same as map)";
						}
						std::cout << "\n";

						gg::Point layerSize;
						bool layerSame;
						if (layerCaps & gm::Map2D::Layer::Caps::HasOwnSize) {
							layerSize = layer->layerSize();
							layerSame = false;
						} else {
							// Convert from map tilesize to layer tilesize, leaving final
							// pixel dimensions unchanged
							layerSize.x = mapSize.x * mapTileSize.x / layerTileSize.x;
							layerSize.y = mapSize.y * mapTileSize.y / layerTileSize.y;
							layerSame = true;
						}
						std::cout << prefix << (bScript ? "width=" : "Layer size: ")
							<< layerSize.x;
						if (bScript) std::cout << "\n" << prefix << "height=";
						else std::cout << "x";
						std::cout << layerSize.y;
						if (layerSame && (!bScript)) {
							std::cout << " (same as map)";
						}
						std::cout << "\n";

						layerIndex++;
					}

				} else {
					std::cout << (bScript ? "unknown" : "Unknown!  Fix this!") << "\n";
				}

			} else if (i.string_key.compare("print") == 0) {
				auto map2d = std::dynamic_pointer_cast<gm::Map2D>(pMap);
				if (map2d) {
					unsigned int targetLayer = strtoul(i.value[0].c_str(), NULL, 10);
					if (targetLayer == 0) {
						std::cerr << "Invalid layer index passed to --print.  Use --info "
							"to list layers in this map." << std::endl;
						iRet = RET_BADARGS;
						continue;
					}
					if (targetLayer > map2d->layers().size()) {
						std::cerr << "Invalid layer index passed to --print.  Use --info "
							"to list layers in this map." << std::endl;
						iRet = RET_BADARGS;
						continue;
					}

					auto layer = map2d->layers().at(targetLayer - 1);
					// If this fails, the map format returned a null pointer for the layer
					assert(layer);

					// Figure out the layer size
					gg::Point layerSize, tileSize;
					getLayerDims(*map2d, *layer, &layerSize, &tileSize);

					auto items = layer->items();
					auto t = items.begin();
					unsigned int numItems = items.size();
					if (t != items.end()) {
						for (unsigned int y = 0; y < layerSize.y; y++) {
							for (unsigned int x = 0; x < layerSize.x; x++) {
								for (unsigned int i = 0; i < numItems; i++) {
									if ((t->pos.x == x) && (t->pos.y == y)) break;
									t++;
									if (t == items.end()) t = items.begin();
								}
								if ((t->pos.x != x) || (t->pos.y != y)) {
									// Grid position with no tile!
									std::cout << "     ";
								} else {
									std::cout << std::hex << std::setw(4)
										<< (unsigned int)t->code << ' ';
								}
							}
							std::cout << "\n";
						}
					} else {
						std::cout << "Layer is empty!" << std::endl;
					}

				} else {
					std::cerr << "Support for printing this map type has not yet "
						"been implemented!" << std::endl;
				}

			} else if (i.string_key.compare("render") == 0) {
				// Don't need to check i.value[0], program_options does that for us

				auto map2d = std::dynamic_pointer_cast<gm::Map2D>(pMap);
				if (map2d) {
					gm::TilesetCollection allTilesets;

					for (auto& a : manualGfx) {
						if (!bScript) {
							std::cout << "Loading " << a.second.type << " from "
								<< a.second.filename << std::endl;
						}
						allTilesets[a.first] = openTileset(a.second.filename, a.second.type);
					}

					for (auto& a : pMap->graphicsFilenames()) {
						if (allTilesets.find(a.first) == allTilesets.end()) {
							if (a.second.filename.empty()) {
								std::cerr << toString(a.first) << " is required, and must "
									"be specified manually with --graphics." << std::endl;
								iRet = RET_BADARGS;
							} else {
								// This tileset hasn't been specified on the command line, but the
								// map format handler has given us a filename, so open the file
								// suggested from the map.
								allTilesets[a.first] = openTileset(a.second.filename, a.second.type);
							}
						} else {
							if (!a.second.filename.empty()) {
								std::cout << toString(a.first) << " overridden on command-line\n";
							}
						}
					}

					if (allTilesets.empty()) {
						std::cerr << "No tilesets were loaded, map cannot be rendered.  "
							"Use --graphics to specify a tileset." << std::endl;
						iRet = RET_BADARGS;
					} else {
						map2dToPng(*map2d, allTilesets, i.value[0]);
					}
				} else {
					std::cerr << PROGNAME ": Rendering this type of map is not yet "
						"implemented." << std::endl;
					return RET_SHOWSTOPPER;
				}

			// Ignore --type/-t
			} else if (i.string_key.compare("type") == 0) {
			} else if (i.string_key.compare("t") == 0) {
			// Ignore --script/-s
			} else if (i.string_key.compare("script") == 0) {
			} else if (i.string_key.compare("s") == 0) {
			// Ignore --force/-f
			} else if (i.string_key.compare("force") == 0) {
			} else if (i.string_key.compare("f") == 0) {

			}
		} // for (all command line elements)
		//pMap->flush();
	} catch (const po::error& e) {
		std::cerr << PROGNAME ": " << e.what()
			<< "  Use --help for help." << std::endl;
		return RET_BADARGS;
	} catch (const stream::error& e) {
		std::cerr << PROGNAME ": " << e.what()
			<< "  Use --help for help." << std::endl;
		return RET_SHOWSTOPPER;
	}

	return iRet;
}
コード例 #2
0
ファイル: mfdmgr.cpp プロジェクト: adam-nielsen/libmfd
int main(int iArgC, char *cArgV[])
{
	// Set a better exception handler
	std::set_terminate( __gnu_cxx::__verbose_terminate_handler );

	// Disable stdin/printf/etc. sync for a speed boost
	std::ios_base::sync_with_stdio(false);

	// Declare the supported options.
	po::options_description poActions("Actions");
	poActions.add_options()
		("list,l",
			"list contents of the address book")

		("update,u", po::value<std::string>(),
			"select an address book entry to change by ID")

		("add,a", po::value<std::string>(),
			"add a new address book entry and select it")

		("set,s", po::value<std::string>(),
			"field:value parameter updates entry selected by earlier -u or -a")
	;

	po::options_description poOptions("Options");
	poOptions.add_options()
		("type,t", po::value<std::string>(),
			"specify the MFD type (default is autodetect)")
		("user,u", po::value<std::string>(),
			"username to log in as")
		("pass,p", po::value<std::string>(),
			"password")
	;

	po::options_description poHidden("Hidden parameters");
	poHidden.add_options()
		("host", "MFD hostname")
		("help", "produce help message")
	;

	po::options_description poVisible("");
	poVisible.add(poActions).add(poOptions);

	po::options_description poComplete("Parameters");
	poComplete.add(poActions).add(poOptions).add(poHidden);
	po::variables_map mpArgs;

	std::string host, user, pass, type;

	try {
		po::parsed_options pa = po::parse_command_line(iArgC, cArgV, poComplete);

		// Parse the global command line options
		for (std::vector<po::option>::iterator i = pa.options.begin(); i != pa.options.end(); i++) {
			if (i->string_key.empty()) {
				// If we've already got an archive filename, complain that a second one
				// was given (probably a typo.)
				if (!host.empty()) {
					std::cerr << "Error: unexpected extra parameter (multiple MFDs "
						"given?!)" << std::endl;
					return 1;
				}
				assert(i->value.size() > 0);  // can't have no values with no name!
				host = i->value[0];
			} else if (i->string_key.compare("help") == 0) {
				std::cout <<
					"Copyright (C) 2010 Adam Nielsen <*****@*****.**>\n"
					"This program comes with ABSOLUTELY NO WARRANTY.  This is free software,\n"
					"and you are welcome to change and redistribute it under certain conditions;\n"
					"see <http://www.gnu.org/licenses/> for details.\n"
					"\n"
					"Utility to remotely configure multi-function devices (networked photocopiers.)\n"
					"Build date " __DATE__ " " __TIME__ << "\n"
					"\n"
					"Usage: mfdmgr <hostname> <action> [action...]\n" << poVisible << "\n"
					<< std::endl;
				return RET_OK;
			} else if (
				(i->string_key.compare("t") == 0) ||
				(i->string_key.compare("type") == 0)
			) {
				if (i->value.size() == 0) {
					std::cerr << PROGNAME ": --type (-t) requires a parameter."
						<< std::endl;
					return RET_BADARGS;
				}
				type = i->value[0];
			} else if (
				(i->string_key.compare("u") == 0) ||
				(i->string_key.compare("user") == 0)
			) {
				if (i->value.size() == 0) {
					std::cerr << PROGNAME ": --user (-u) requires a parameter."
						<< std::endl;
					return RET_BADARGS;
				}
				user = i->value[0];
			} else if (
				(i->string_key.compare("p") == 0) ||
				(i->string_key.compare("pass") == 0)
			) {
				if (i->value.size() == 0) {
					std::cerr << PROGNAME ": --pass (-p) requires a parameter."
						<< std::endl;
					return RET_BADARGS;
				}
				pass = i->value[0];
			}
		}

		if (host.empty()) {
			std::cerr << "Error: no hostname given" << std::endl;
			return RET_BADARGS;
		}
		std::cout << "Opening " << host << " as type "
			<< (type.empty() ? "<autodetect>" : type) << std::endl;

		// Get the format handler for this file format
		boost::shared_ptr<mfd::Manager> pManager(mfd::getManager());

		mfd::DeviceTypePtr pDeviceType;
		if (type.empty()) {
			// Need to autodetect the file format.
			mfd::DeviceTypePtr pTestType;
			int i = 0;
			while ((pTestType = pManager->getDeviceType(i++))) {
				mfd::E_CERTAINTY cert = pTestType->isInstance(host);
				switch (cert) {
					case mfd::EC_DEFINITELY_NO:
						// Don't print anything (TODO: Maybe unless verbose?)
						break;
					case mfd::EC_DEFINITELY_YES:
						std::cout << "Device is definitely a " << pTestType->getFriendlyName()
							<< " [" << pTestType->getDeviceCode() << "]" << std::endl;
						pDeviceType = pTestType;
						// Don't bother checking any other formats if we got a 100% match
						goto finishTesting;
				}
			}
finishTesting:
			if (!pDeviceType) {
				std::cerr << "Unable to automatically determine the file type.  Use "
					"the --type option to manually specify the file format." << std::endl;
				return RET_BE_MORE_SPECIFIC;
			}
		} else {
			mfd::DeviceTypePtr pTestType(pManager->getDeviceTypeByCode(type));
			if (!pTestType) {
				std::cerr << "Unknown device type given to -t/--type: " << type
					<< std::endl;
				return RET_BADARGS;
			}
			pDeviceType = pTestType;
		}

		assert(pDeviceType != NULL);

		// Connect to the device
		boost::shared_ptr<mfd::Device> pDevice(pDeviceType->open(host, user, pass));
		assert(pDevice);

		int iRet = RET_OK;

		// ID of previously selected address book item
		std::string idABSelected;
		std::map<std::string, std::string> fieldSet; // TODO: enum for field type

		// Run through the actions on the command line
		for (std::vector<po::option>::iterator i = pa.options.begin(); i != pa.options.end(); i++) {
			if (i->string_key.compare("list") == 0) {
				boost::shared_ptr<mfd::AddressBook> ab = pDevice->getAddressBook();
				if (!ab) {
					std::cerr << "This device type does not have an address book." << std::endl;
					iRet = RET_BADARGS;
					continue;
				}
				const mfd::AddressBook::VC_ENTRYID& entryIds = ab->getEntryIds();
				for (mfd::AddressBook::VC_ENTRYID::const_iterator i = entryIds.begin();
					i != entryIds.end(); i++
				) {
					std::cout << "id is " << *i << std::endl;
				}
				mfd::AddressBook::VC_FIELDLIST allEntries;
				ab->getEntries(entryIds, allEntries);
				for (mfd::AddressBook::VC_FIELDLIST::iterator i = allEntries.begin();
					i != allEntries.end(); i++
				) {
					mfd::AddressBook::FieldList& fl = *i;
					for (mfd::AddressBook::FieldList::iterator j = fl.begin();
						j != fl.end(); j++
					) {
						std::cout << j->first << "=" << j->second << "; ";
					}
					std::cout << std::endl;
				}

			} else if (i->string_key.compare("update") == 0) {
				idABSelected = i->value[0];
				std::cout << "Selected ID " << idABSelected << " for update" << std::endl;
				fieldSet.clear(); // empty any fields previously set

			} else if (i->string_key.compare("set") == 0) {
				if (idABSelected.empty()) {
					std::cerr << "ERROR: You must use -u or -a before --set/-s" << std::endl;
					iRet = RET_BADARGS;
					break;
				}
				idABSelected = i->value[0];
				std::string fieldName, fieldVal;
				bool bAltDest = split(i->value[0], ':', &fieldName, &fieldVal);
				if (!bAltDest) {
					std::cerr << "ERROR: --set/-s requires a parameter of the form "
						"field:value" << std::endl;
					iRet = RET_BADARGS;
					break;
				}
				std::cout << "Set field \"" << fieldName << "\" to \"" << fieldVal
					<< "\"" << std::endl;

			// Ignore --type/-t
			} else if (i->string_key.compare("type") == 0) {
			} else if (i->string_key.compare("t") == 0) {

			}
		} // for (all command line elements)

	} catch (po::unknown_option& e) {
		std::cerr << PROGNAME ": " << e.what()
			<< ".  Use --help for help." << std::endl;
		return RET_BADARGS;
	} catch (po::invalid_command_line_syntax& e) {
		std::cerr << PROGNAME ": " << e.what()
			<< ".  Use --help for help." << std::endl;
		return RET_BADARGS;
	} catch (mfd::ECommFailure& e) {
		std::cerr << PROGNAME ": Communication failure (" << e.what()
			<< ")" << std::endl;
		return RET_SHOWSTOPPER;
	}

	return RET_OK;
}
コード例 #3
0
int main(int iArgC, char *cArgV[])
{
#ifdef __GLIBCXX__
	// Set a better exception handler
	std::set_terminate(__gnu_cxx::__verbose_terminate_handler);
#endif

	// Disable stdin/printf/etc. sync for a speed boost
	std::ios_base::sync_with_stdio(false);

	// Declare the supported options.
	po::options_description poActions("Actions");
	poActions.add_options()
		("list,l",
			"list files in the archive")

		("extract-all,X",
			"extract all files in the archive")

		("extract,x", po::value<std::string>(),
			"extract a specific file")

		("add,a", po::value<std::string>(),
			"add a file at the end of the archive")

		("insert,i", po::value<std::string>(),
			"add a file at a specific point in the archive")

		("metadata,m",
			"list archive attributes/metadata")

		("set-metadata,e", po::value<std::string>(),
			"change archive attributes/metadata")

		("overwrite,o", po::value<std::string>(),
			"replace a file in the archive with new data")

		("rename,r", po::value<std::string>(),
			"rename a file inside the archive")

		("delete,d", po::value<std::string>(),
			"remove a file from the archive")

		("uncompressed-size,z", po::value<int>(),
			"[with -u only] specify the uncompressed size to use with -i")
	;

	po::options_description poOptions("Options");
	poOptions.add_options()
		("type,t", po::value<std::string>(),
			"specify the archive type (default is autodetect)")
		("list-types",
			"list available formats that can be passed to --type")
		("filetype,y", po::value<std::string>(),
			"specify the file type when inserting (default is generic file)")
		("attribute,b", po::value<std::string>(),
			"specify the file attributes when inserting (optional)")
		("unfiltered,u",
			"do not filter files (no encrypt/decrypt/compress/decompress)")
		("script,s",
			"format output suitable for script parsing")
		("force,f",
			"force open even if the archive is not in the given format")
		("create,c",
			"create a new archive file instead of opening an existing one")
	;

	po::options_description poHidden("Hidden parameters");
	poHidden.add_options()
		("archive", "archive file to manipulate")
		("help", "produce help message")
	;

	po::options_description poVisible("");
	poVisible.add(poActions).add(poOptions);

	po::options_description poComplete("Parameters");
	poComplete.add(poActions).add(poOptions).add(poHidden);
	po::variables_map mpArgs;

	std::string strFilename;
	std::string strType;

	bool bScript = false; // show output suitable for script parsing?
	bool bForceOpen = false; // open anyway even if archive not in given format?
	bool bCreate = false; // create a new archive?
	try {
		po::parsed_options pa = po::parse_command_line(iArgC, cArgV, poComplete);

		// Parse the global command line options
		for (std::vector<po::option>::iterator i = pa.options.begin(); i != pa.options.end(); i++) {
			if (i->string_key.empty()) {
				// If we've already got an archive filename, complain that a second one
				// was given (probably a typo.)
				if (!strFilename.empty()) {
					std::cerr << "Error: unexpected extra parameter (multiple archive "
						"filenames given?!)" << std::endl;
					return RET_BADARGS;
				}
				assert(i->value.size() > 0);  // can't have no values with no name!
				strFilename = i->value[0];
			} else if (i->string_key.compare("help") == 0) {
				std::cout <<
					"Copyright (C) 2010-2016 Adam Nielsen <*****@*****.**>\n"
					"This program comes with ABSOLUTELY NO WARRANTY.  This is free software,\n"
					"and you are welcome to change and redistribute it under certain conditions;\n"
					"see <http://www.gnu.org/licenses/> for details.\n"
					"\n"
					"Utility to manipulate archive files used by games to store data files.\n"
					"Build date " __DATE__ " " __TIME__ << "\n"
					"\n"
					"Usage: gamearch <archive> <action> [action...]\n" << poVisible << "\n"
					<< std::endl;
				return RET_OK;
			} else if (
				(i->string_key.compare("list-types") == 0)
			) {
				for (const auto& i : ga::ArchiveManager::formats()) {
					std::string code = i->code();
					std::cout << code;
					int len = code.length();
					if (len < 20) std::cout << std::string(20 - len, ' ');
					std::cout << ' ' << i->friendlyName() << '\n';
				}
				return RET_OK;
			} else if (
				(i->string_key.compare("t") == 0) ||
				(i->string_key.compare("type") == 0)
			) {
				if (i->value.size() == 0) {
					std::cerr << PROGNAME ": --type (-t) requires a parameter.  Use "
						"--list-types to see valid values." << std::endl;
					return RET_BADARGS;
				}
				strType = i->value[0];
			} else if (
				(i->string_key.compare("s") == 0) ||
				(i->string_key.compare("script") == 0)
			) {
				bScript = true;
			} else if (
				(i->string_key.compare("f") == 0) ||
				(i->string_key.compare("force") == 0)
			) {
				bForceOpen = true;
			} else if (
				(i->string_key.compare("u") == 0) ||
				(i->string_key.compare("unfiltered") == 0)
			) {
				bUseFilters = false;
			} else if (
				(i->string_key.compare("c") == 0) ||
				(i->string_key.compare("create") == 0)
			) {
				bCreate = true;
			}
		}

		if (strFilename.empty()) {
			std::cerr << "Error: no game archive filename given" << std::endl;
			return RET_BADARGS;
		}

		std::unique_ptr<stream::file> psArchive;
		if (bCreate && strType.empty()) {
			std::cerr << "Error: You must specify the --type of archive to create"
				<< std::endl;
			return RET_BADARGS;
		}
		std::cout << (bCreate ? "Creating " : "Opening ") << strFilename
			<< " as type " << (strType.empty() ? "<autodetect>" : strType)
			<< std::endl;
		try {
			psArchive = std::make_unique<stream::file>(strFilename, bCreate);
		} catch (const stream::open_error& e) {
			std::cerr << "Error " << (bCreate ? "creating" : "opening")
				<< " archive file " << strFilename << ": " << e.what() << std::endl;
			return RET_SHOWSTOPPER;
		}

		// Get the format handler for this file format
		ga::ArchiveManager::handler_t pArchType;
		if (strType.empty()) {
			// Need to autodetect the file format.
			for (const auto& i : ga::ArchiveManager::formats()) {
				ga::ArchiveType::Certainty cert = i->isInstance(*psArchive);
				switch (cert) {

					case ga::ArchiveType::Certainty::DefinitelyNo:
						// Don't print anything (TODO: Maybe unless verbose?)
						break;

					case ga::ArchiveType::Certainty::Unsure:
						std::cout << "File could be a " << i->friendlyName()
							<< " [" << i->code() << "]" << std::endl;
						// If we haven't found a match already, use this one
						if (!pArchType) pArchType = i;
						break;

					case ga::ArchiveType::Certainty::PossiblyYes:
						std::cout << "File is likely to be a " << i->friendlyName()
							<< " [" << i->code() << "]" << std::endl;
						// Take this one as it's better than an uncertain match
						pArchType = i;
						break;

					case ga::ArchiveType::Certainty::DefinitelyYes:
						std::cout << "File is definitely a " << i->friendlyName()
							<< " [" << i->code() << "]" << std::endl;
						pArchType = i;
						// Don't bother checking any other formats if we got a 100% match
						goto finishTesting;
				}
				if (cert != ga::ArchiveType::Certainty::DefinitelyNo) {
					// We got a possible match, see if it requires any suppdata
					auto suppList = i->getRequiredSupps(*psArchive, strFilename);
					if (suppList.size() > 0) {
						// It has suppdata, see if it's present
						std::cout << "  * This format requires supplemental files..."
							<< std::endl;
						bool bSuppOK = true;
						for (const auto& s : suppList) {
							try {
								stream::file test_presence(s.second, false);
							} catch (const stream::open_error&) {
								bSuppOK = false;
								std::cout << "  * Could not find/open " << s.second
									<< ", archive is probably not " << i->code() << std::endl;
								break;
							}
						}
						if (bSuppOK) {
							// All supp files opened ok
							std::cout << "  * All supp files present, archive is likely "
								<< i->code() << std::endl;
							// Set this as the most likely format
							pArchType = i;
						}
					}
				}
			}
finishTesting:
			if (!pArchType) {
				std::cerr << "Unable to automatically determine the file type.  Use "
					"the --type option to manually specify the file format." << std::endl;
				return RET_BE_MORE_SPECIFIC;
			}
		} else {
			auto pTestType = ga::ArchiveManager::byCode(strType);
			if (!pTestType) {
				std::cerr << "Unknown file type given to -t/--type: " << strType
					<< std::endl;
				return RET_BADARGS;
			}
			pArchType = pTestType;
		}

		assert(pArchType != NULL);

		if (!bCreate) {
			// Check to see if the file is actually in this format
			if (pArchType->isInstance(*psArchive) == ga::ArchiveType::Certainty::DefinitelyNo) {
				if (bForceOpen) {
					std::cerr << "Warning: " << strFilename << " is not a "
						<< pArchType->friendlyName() << ", open forced." << std::endl;
				} else {
					std::cerr << "Invalid format: " << strFilename << " is not a "
						<< pArchType->friendlyName() << "\n"
						<< "Use the -f option to try anyway." << std::endl;
					return RET_BE_MORE_SPECIFIC;
				}
			}
		}

		// See if the format requires any supplemental files
		auto suppList = pArchType->getRequiredSupps(*psArchive, strFilename);
		camoto::SuppData suppData;
		for (const auto& s : suppList) {
			try {
				std::cout << "Opening supplemental file " << s.second << std::endl;
				auto suppStream = std::make_unique<stream::file>(s.second, false);
				suppData[s.first] = std::move(suppStream);
			} catch (const stream::open_error& e) {
				std::cerr << "Error opening supplemental file " << s.second
					<< ": " << e.what() << std::endl;
				return RET_SHOWSTOPPER;
			}
		}

		// Open the archive file
		std::shared_ptr<ga::Archive> pArchive;
		try {
			if (bCreate) {
				pArchive = pArchType->create(std::move(psArchive), suppData);
			} else {
				pArchive = pArchType->open(std::move(psArchive), suppData);
			}
			assert(pArchive);
		} catch (const camoto::error& e) {
			std::cerr << "Error " << (bCreate ? "creating" : "opening")
				<< " archive file: " << e.what() << std::endl;
			return RET_SHOWSTOPPER;
		}

		// File type of inserted files defaults to empty, which means 'generic file'
		std::string strLastFiletype;

		// Last attribute value set with -b
		auto iLastAttr = ga::Archive::File::Attribute::Default;

		// Last value set with -z
		stream::len lenReal = 0;

		// Run through the actions on the command line
		for (auto& i : pa.options) {
			if (i.string_key.compare("list") == 0) {
				listFiles(std::string(), std::string(), *pArchive, bScript);

			} else if (i.string_key.compare("extract-all") == 0) {
				extractAll(pArchive, bScript);

			} else if (i.string_key.compare("metadata") == 0) {
				listAttributes(pArchive.get(), bScript);

			} else if (i.string_key.compare("set-metadata") == 0) {
				std::string strIndex, strValue;
				if (!split(i.value[0], '=', &strIndex, &strValue)) {
					std::cerr << PROGNAME ": -e/--set-metadata requires an index and "
						"a value (e.g. --set-metadata 0=example)" << std::endl;
					return RET_BADARGS;
				}
				unsigned int index = strtoul(strIndex.c_str(), nullptr, 0);
				setAttribute(pArchive.get(), bScript, index, strValue);

			} else if (i.string_key.compare("extract") == 0) {
				std::string strArchFile, strLocalFile;
				bool bAltDest = split(i.value[0], '=', &strArchFile, &strLocalFile);
				if (!bAltDest) sanitisePath(strLocalFile);

				std::cout << " extracting: " << strArchFile;
				if (strArchFile.compare(strLocalFile)) std::cout << " (into " << strLocalFile << ")";
				std::cout << std::flush;

				try {
					// Find the file
					auto destArch = pArchive;
					ga::Archive::FileHandle id;
					findFile(&destArch, &id, strArchFile);
					if (!id) {
						std::cout << " [failed; file not found]";
						iRet = RET_NONCRITICAL_FAILURE; // one or more files failed
					} else {
						// Found it, open on disk
						auto pfsIn = destArch->open(id, bUseFilters);
						try {
							auto fsOut = std::make_shared<stream::output_file>(strLocalFile, true);
							try {
								stream::copy(*fsOut, *pfsIn);
							} catch (const stream::error& e) {
								std::cout << " [failed; read/write error: " << e.what() << "]";
								iRet = RET_UNCOMMON_FAILURE; // some files failed, but not in a usual way
							}
						} catch (const stream::error&) {
							std::cout << " [failed; unable to create output file]";
							iRet = RET_UNCOMMON_FAILURE; // some files failed, but not in a usual way
						}
					}
				} catch (const stream::error& e) {
					std::cout << " [failed; " << e.what() << "]";
					iRet = RET_UNCOMMON_FAILURE; // some files failed, but not in a usual way
				}
				std::cout << std::endl;

			} else if (i.string_key.compare("delete") == 0) {
				std::string& strArchFile = i.value[0];
				std::cout << "   deleting: " << strArchFile << std::flush;

				try {
					auto destArch = pArchive;
					ga::Archive::FileHandle id;
					findFile(&destArch, &id, strArchFile);
					if (!id) {
						std::cout << " [failed; file not found]";
						iRet = RET_NONCRITICAL_FAILURE; // one or more files failed
					} else {
						destArch->remove(id);
					}
				} catch (const stream::error& e) {
					std::cout << " [failed; " << e.what() << "]";
					iRet = RET_UNCOMMON_FAILURE; // some files failed, but not in a usual way
				}
				std::cout << std::endl;

			} else if (i.string_key.compare("insert") == 0) {
				std::string strSource, strInsertBefore;
				if (!split(i.value[0], ':', &strSource, &strInsertBefore)) {
					std::cerr << PROGNAME ": -i/--insert requires a file to insert "
						"before (parameter should end with \":beforeme.xyz\")\n"
						"Or use --add instead." << std::endl;
					return RET_BADARGS;
				}

				std::string strArchFile, strLocalFile;
				bool bAltDest = split(strSource, '=', &strArchFile, &strLocalFile);

				std::cout << "  inserting: " << strArchFile;
				if (!strLastFiletype.empty()) std::cout << " as type " << strLastFiletype;
				std::cout << " (before "
					<< strInsertBefore;
				if (bAltDest) std::cout << ", from " << strLocalFile;
				std::cout << ")";
				if (lenReal != 0) std::cout << ", with uncompressed size " << lenReal;
				std::cout << std::flush;

				// Try to find strInsertBefore
				auto destArch = pArchive;
				ga::Archive::FileHandle idBeforeThis;
				findFile(&destArch, &idBeforeThis, strInsertBefore);
				if (!idBeforeThis) {
					std::cout << " [failed; could not find " << strInsertBefore << "]";
					iRet = RET_NONCRITICAL_FAILURE; // one or more files failed
					continue;
				}

				try {
					insertFile(destArch, strLocalFile, strArchFile, idBeforeThis,
						strLastFiletype, iLastAttr, lenReal);
				} catch (const stream::error& e) {
					std::cout << " [failed; " << e.what() << "]";
					iRet = RET_UNCOMMON_FAILURE; // some files failed, but not in a usual way
				}

				std::cout << std::endl;

			// Remember --filetype/-y
			} else if (i.string_key.compare("filetype") == 0) {
			//} else if (i.string_key.compare("y") == 0) {
				strLastFiletype = i.value[0];

			// Remember --attributes/-b
			} else if (i.string_key.compare("attribute") == 0) {
			//} else if (i.string_key.compare("b") == 0) {
				std::string nextAttr = i.value[0];
				bool disable = (nextAttr[0] == '-');
				if (disable) nextAttr = nextAttr.substr(1);

				ga::Archive::File::Attribute next;
				if      (nextAttr.compare("empty")      == 0) next = ga::Archive::File::Attribute::Vacant;
				else if (nextAttr.compare("hidden")     == 0) next = ga::Archive::File::Attribute::Hidden;
				else if (nextAttr.compare("compressed") == 0) next = ga::Archive::File::Attribute::Compressed;
				else if (nextAttr.compare("encrypted")  == 0) next = ga::Archive::File::Attribute::Encrypted;
				else {
					std::cerr << "Unknown attribute " << nextAttr
						<< ", valid values are: empty hidden compressed encrypted"
						<< std::endl;
					iRet = RET_UNCOMMON_FAILURE;
					next = ga::Archive::File::Attribute::Default;
				}
				if (next != ga::Archive::File::Attribute::Default) {
					auto allowed = pArchive->getSupportedAttributes();
					if (allowed & next) {
						if (disable) iLastAttr &= ~next;
						else iLastAttr |= next;
					} else {
						std::cerr << "Warning: Attribute unsupported by archive format, "
							"ignoring: " << nextAttr << std::endl;
					}
				}

			// Remember --uncompressed-size/-z
			} else if (i.string_key.compare("uncompressed-size") == 0) {
			//} else if (i.string_key.compare("z") == 0) {
				if (bUseFilters) {
					std::cerr << PROGNAME ": -z/--uncompressed-size only needs to be "
						"specified when it can't be determined automatically (i.e. when "
						"-u/--unfiltered is in use.)" << std::endl;
					return RET_BADARGS;
				}
				lenReal = strtoul(i.value[0].c_str(), NULL, 0);

			// Ignore --type/-t
			} else if (i.string_key.compare("type") == 0) {
			} else if (i.string_key.compare("t") == 0) {
			// Ignore --script/-s
			} else if (i.string_key.compare("script") == 0) {
			} else if (i.string_key.compare("s") == 0) {
			// Ignore --force/-f
			} else if (i.string_key.compare("force") == 0) {
			} else if (i.string_key.compare("f") == 0) {

			} else if ((!i.string_key.empty()) && (i.value.size() > 0)) {
				// None of the above (single param) options matched, so it's probably
				// an option with up to two filenames (with an equal-sign as a
				// separator.)  It could also be the --type option, which we'll ignore.
				std::string& strParam = i.value[0];
				std::string strArchFile, strLocalFile;
				bool bAltDest = split(strParam, '=', &strArchFile, &strLocalFile);

				if (i.string_key.compare("add") == 0) {
					std::cout << "     adding: " << strArchFile;
					if (!strLastFiletype.empty()) std::cout << " as type " << strLastFiletype;
					if (bAltDest) std::cout << " (from " << strLocalFile << ")";
					if (lenReal != 0) std::cout << ", with uncompressed size set to " << lenReal;
					std::cout << std::endl;

					try {
						insertFile(pArchive, strLocalFile, strArchFile,
							nullptr, strLastFiletype, iLastAttr,
							lenReal);
					} catch (const stream::error& e) {
						std::cout << " [failed; " << e.what() << "]";
						iRet = RET_UNCOMMON_FAILURE; // some files failed, but not in a usual way
					}

				} else if (i.string_key.compare("rename") == 0) {
					if ((!bAltDest) || (boost::equals(strArchFile, strLocalFile))) {
						std::cout << "ignoring attempt to rename " << strArchFile
							<< " into the same name" << std::endl;
					} else {
						std::cout << "   renaming: " << strArchFile << " to "
							<< strLocalFile << std::flush;

						try {
							auto destArch = pArchive;
							ga::Archive::FileHandle id;
							findFile(&destArch, &id, strArchFile);
							if (!id) {
								std::cout << " [failed; file not found inside archive]";
								iRet = RET_NONCRITICAL_FAILURE; // one or more files failed
							} else {
								destArch->rename(id, strLocalFile);
							}
						} catch (const stream::error& e) {
							std::cout << " [failed; " << e.what() << "]";
							iRet = RET_UNCOMMON_FAILURE; // some files failed, but not in a common way
						}
						std::cout << std::endl;
					}

				} else if (i.string_key.compare("overwrite") == 0) {
					std::cout << "overwriting: " << strArchFile;
					if (bAltDest) std::cout << " (from " << strLocalFile << ")";
					if (lenReal != 0) std::cout << ", with uncompressed size set to " << lenReal;
					std::cout << std::flush;

					try {
						// Find the file
						auto destArch = pArchive;
						ga::Archive::FileHandle id;
						findFile(&destArch, &id, strArchFile);
						if (!id) {
							std::cout << " [failed; file not found inside archive]";
							iRet = RET_NONCRITICAL_FAILURE; // one or more files failed
						} else {
							// Found it, open replacement file
							auto sSrc = std::make_shared<stream::input_file>(strLocalFile);
							stream::len lenSource = sSrc->size();

							// Note that we are opening the file into an output_sptr (instead
							// of an inout_sptr) as this is more efficient.  By foregoing read
							// access to the file, it means a compressed file won't be
							// decompressed in case we want to read it.  Which we don't,
							// because we're about to completely overwrite it.
							auto psDest = destArch->open(id, bUseFilters);

							// Set the size of the stream within the archive, so it exactly
							// holds the data we want to write.
							psDest->truncate(lenSource);

							if (!bUseFilters) {
								if (lenReal) {
									pArchive->resize(id, lenSource, lenReal);
								} else {
									// Leave the prefiltered/decompressed size unchanged
									pArchive->resize(id, lenSource, id->realSize);
								}
							}

							psDest->seekp(0, stream::start);
							stream::copy(*psDest, *sSrc);
							psDest->flush();
						}
					} catch (const stream::open_error&) {
						std::cout << " [failed; unable to open replacement file]";
						iRet = RET_NONCRITICAL_FAILURE; // one or more files failed
					} catch (const stream::error& e) {
						std::cout << " [failed; " << e.what() << "]";
						iRet = RET_UNCOMMON_FAILURE; // some files failed, but not in a common way
					}
					std::cout << std::endl;
				}
				// else it's the archive filename, but we already have that
			}
		} // for (all command line elements)
		pArchive->flush();
	} catch (const po::unknown_option& e) {
		std::cerr << PROGNAME ": " << e.what()
			<< ".  Use --help for help." << std::endl;
		return RET_BADARGS;
	} catch (const po::invalid_command_line_syntax& e) {
		std::cerr << PROGNAME ": " << e.what()
			<< ".  Use --help for help." << std::endl;
		return RET_BADARGS;
	}

	return iRet;
}