bool appendAtoB(const FS_NAMESPACE::path & destinationPath, const FS_NAMESPACE::path & sourcePath) { // try opening the output file. std::fstream outStream; outStream.open(destinationPath.string(), std::ofstream::out | std::ofstream::binary | std::ofstream::app); if (outStream.is_open() && outStream.good()) { if (beVerbose) { std::cout << std::endl << "Opened output file " << destinationPath << std::endl; } // seek to the end outStream.seekg(0, std::ios::end); // open input file std::ifstream inStream; inStream.open(sourcePath.string(), std::ifstream::in | std::ifstream::binary); if (inStream.is_open() && inStream.good()) { if (beVerbose) { std::cout << "Opened input file \"" << sourcePath << "\". Appending data to output." << std::endl; } // copy data from input to output file while (!inStream.eof() && inStream.good()) { unsigned char buffer[1024]; std::streamsize readSize = sizeof(buffer); try { // try reading data from input file inStream.read(reinterpret_cast<char *>(&buffer), sizeof(buffer)); } catch (std::ios_base::failure) { /*ignore read failure. salvage what we can.*/ } // store how many bytes were actually read readSize = inStream.gcount(); // write to output file outStream.write(reinterpret_cast<const char *>(&buffer), readSize); } // close input file inStream.close(); } else { std::cout << "Error: Failed to open input file \"" << sourcePath.string() << "\" for reading!" << std::endl; outStream.close(); return false; } // close output file outStream.close(); return true; } else { std::cout << "Error: Failed to open output file \"" << destinationPath.string() << "\" for writing!" << std::endl; } return false; }
int main(int argc, const char * argv[]) { printVersion(); // check number of arguments and if all arguments can be read if (argc < 3 || !readArguments(argc, argv)) { printUsage(); return -1; } // check if the input path exist if (!FS_NAMESPACE::exists(inFilePath)) { std::cout << "Error: Invalid input file/directory \"" << inFilePath.string() << "\"!" << std::endl; return -2; } if (createBinary) { // check if argument 2 is a file if (FS_NAMESPACE::is_directory(outFilePath)) { std::cout << "Error: Output must be a file if -b is used!" << std::endl; return -2; } } else if (appendFile) { // check if argument 2 is a file if (FS_NAMESPACE::is_directory(outFilePath)) { std::cout << "Error: Output must be a file if -a is used!" << std::endl; return -2; } } else if (FS_NAMESPACE::is_directory(inFilePath) != FS_NAMESPACE::is_directory(outFilePath)) { // check if output directory exists if (FS_NAMESPACE::is_directory(outFilePath) && !FS_NAMESPACE::exists(outFilePath)) { std::cout << "Error: Invalid output directory \"" << outFilePath.string() << "\"!" << std::endl; return -2; } // check if arguments 1 and 2 are both files or both directories std::cout << "Error: Input and output file must be both either a file or a directory!" << std::endl; return -2; } if (appendFile) { // append file a to b if (!appendAtoB(outFilePath, inFilePath)) { std::cout << "Error: Failed to append data to executable!" << std::endl; return -3; } } else { // build list of files to process std::vector<FileData> fileList; if (FS_NAMESPACE::is_directory(inFilePath) && FS_NAMESPACE::is_directory(inFilePath)) { // both files are directories, build file ist fileList = getFileDataFrom(inFilePath, outFilePath, inFilePath, useRecursion); if (fileList.empty()) { std::cout << "Error: No files to convert!" << std::endl; return -3; } } else { // just add single input/output file FileData temp; temp.inPath = inFilePath; temp.outPath = outFilePath; temp.internalName = inFilePath.filename().string(); // remove all, but the file name and extension if (beVerbose) { std::cout << "Found input file " << inFilePath << std::endl; std::cout << "Internal name will be \"" << temp.internalName << "\"" << std::endl; std::cout << "Output path is " << temp.outPath << std::endl; } // get file size try { temp.size = static_cast<uint64_t>(FS_NAMESPACE::file_size(inFilePath)); if (beVerbose) { std::cout << "Size is " << temp.size << " bytes." << std::endl; } } catch (...) { std::cout << "Error: Failed to get size of " << inFilePath << "!" << std::endl; temp.size = 0; } fileList.push_back(temp); } // does the user want an binary file? if (createBinary) { // yes. build it. if (!createBlob(fileList, outFilePath)) { std::cout << "Error: Failed to convert to binary file!" << std::endl; return -4; } } else { // no. convert files to .c/.cpp. loop through list, converting files for (auto fdIt = fileList.begin(); fdIt != fileList.cend(); ++fdIt) { if (!convertFile(*fdIt, commonHeaderFilePath)) { std::cout << "Error: Failed to convert all files. Aborting!" << std::endl; return -4; } } // do we need to write a header file? if (!commonHeaderFilePath.empty()) { if (!createCommonHeader(fileList, commonHeaderFilePath, !utilitiesFilePath.empty(), useC)) { return -5; } // do we need to create utilities? if (!utilitiesFilePath.empty()) { if (!createUtilities(fileList, utilitiesFilePath, commonHeaderFilePath, useC, combineResults)) { return -6; } } } } } // if (!appendFile) { // profit!!! std::cout << "res2h succeeded." << std::endl; return 0; }
bool createUtilities(std::vector<FileData> & fileList, const FS_NAMESPACE::path & utilitiesPath, const FS_NAMESPACE::path & commonHeaderPath, bool useCConstructs = false, bool addFileData = false) { // try opening the output file. truncate it when it exists std::ofstream outStream; outStream.open(utilitiesPath.generic_string(), std::ofstream::out | std::ofstream::trunc); if (outStream.is_open() && outStream.good()) { if (beVerbose) { std::cout << std::endl << "Creating utilities file " << utilitiesPath; } // add message outStream << "// this file was auto-generated by res2h" << std::endl << std::endl; // create path to include file RELATIVE to this file FS_NAMESPACE::path relativePath = naiveUncomplete(commonHeaderPath, utilitiesPath); // include header file outStream << "#include \"" << relativePath.string() << "\"" << std::endl << std::endl; // if the data should go to this file too, add it if (addFileData) { for (auto fdIt = fileList.begin(); fdIt != fileList.cend(); ++fdIt) { if (!convertFile(*fdIt, commonHeaderFilePath, outStream, false)) { std::cout << "Error: Failed to convert all files. Aborting!" << std::endl; outStream.close(); return false; } } } // begin data arrays. switch depending whether C or C++ outStream << "const uint32_t res2hNrOfFiles = " << fileList.size() << ";" << std::endl; // add files outStream << "const Res2hEntry res2hFiles[res2hNrOfFiles] = {" << std::endl; outStream << " "; // first indent for (auto fdIt = fileList.cbegin(); fdIt != fileList.cend();) { outStream << "{\"" << fdIt->internalName << "\", " << fdIt->sizeVariableName << ", " << fdIt->dataVariableName << "}"; // was this the last entry? ++fdIt; if (fdIt != fileList.cend()) { // no. add comma. outStream << ","; // add break after every entry and add indent again outStream << std::endl << " "; } } outStream << std::endl << "};" << std::endl; if (!useCConstructs) { // add files to map outStream << std::endl << "res2hMapType::value_type mapTemp[] = {" << std::endl; outStream << " "; for (auto fdIt = fileList.cbegin(); fdIt != fileList.cend();) { outStream << "std::make_pair(\"" << fdIt->internalName << "\", res2hFiles[" << (fdIt - fileList.cbegin()) << "])"; // was this the last entry? ++fdIt; if (fdIt != fileList.cend()) { // no. add comma. outStream << ","; // add break after every entry and add indent again outStream << std::endl << " "; } } outStream << std::endl << "};" << std::endl << std::endl; // create map outStream << "res2hMapType res2hMap(mapTemp, mapTemp + sizeof mapTemp / sizeof mapTemp[0]);" << std::endl; } // close file outStream.close(); if (beVerbose) { std::cout << " - succeeded." << std::endl; } return true; } else { std::cout << "Error: Failed to open file \"" << utilitiesPath << "\" for writing!" << std::endl; } return true; }
// Blob archive file format: // Offset | Type | Description // ---------------------+---------------------+------------------------------------------- // START OF DATA | char[8] | magic number string "res2hbin" // 08 | uint32_t | file format version number (currently 2) // 12 | uint32_t | format flags. The lower 8 bit state the bit depth of the archive (32/64) // 16 | uint32_t / uint64_t | size of whole archive including checksum in bytes // 20/24 | uint32_t | number of directory and file entries following // Then follows the directory: // 24/28 + 00 | uint16_t | file entry #0, size of internal name WITHOUT null-terminating character // 24/28 + 02 | char[] | file entry #0, internal name (NOT null-terminated) // 24/28 + 02 + name | uint32_t | file entry #0, format flags for entry (currently 0) // 24/28 + 06 + name | uint32_t / uint64_t | file entry #0, size of data // 24/28 + 10/14 + name | uint32_t / uint64_t | file entry #0, absolute offset of data in file // 24/28 + 14/22 + name | uint32_t / uint64_t | file entry #0, Fletcher32/64 checksum of data // Then follow other directory entries. There is some redundant information here, but that's for reading stuff faster. // Directly after the directory the data blocks begin. // END - 04/08 | uint32_t / uint64_t | Fletcher32/64 checksum of whole file up to this point // Obviously with a 32bit archive you're limited to ~4GB for the whole binary file and ~4GB per data entry. // Res2h will automagically create a 32bit archive, if data permits it, or a 64bit archive if needed. bool createBlob(const std::vector<FileData> & fileList, const FS_NAMESPACE::path & filePath) { // try opening the output file. truncate it when it exists std::fstream outStream; outStream.open(filePath.string(), std::ofstream::out | std::ofstream::binary | std::ofstream::trunc); if (outStream.is_open() && outStream.good()) { const uint32_t nrOfEntries = static_cast<uint32_t>(fileList.size()); // check if a 64bit archive is needed, or 32bit suffice uint64_t directorySize = 0; uint64_t maxDataSize = 0; uint64_t dataSize = 0; for (const auto & file : fileList) { dataSize += file.size; maxDataSize = maxDataSize < file.size ? file.size : maxDataSize; directorySize += file.internalName.size(); } // now take worst case header and fixed directory size into account and check if we need 32 or 64 bit const bool mustUse64Bit = maxDataSize > UINT32_MAX || (RES2H_HEADER_SIZE_64 + directorySize + nrOfEntries * RES2H_DIRECTORY_SIZE_64 + dataSize + sizeof(uint64_t)) > UINT32_MAX; if (beVerbose) { std::cout << std::endl << "Creating binary " << (mustUse64Bit ? "64" : "32") << "bit archive " << filePath << std::endl; } // now that we know how many bits, add the correct amount of data for the fixed directory entries to the variable directorySize += nrOfEntries * (mustUse64Bit ? RES2H_DIRECTORY_SIZE_64 : RES2H_DIRECTORY_SIZE_32); // add magic number to file const unsigned char magicBytes[9] = RES2H_MAGIC_BYTES; outStream.write(reinterpret_cast<const char *>(&magicBytes), sizeof(magicBytes) - 1); // add version and format flag to file const uint32_t fileVersion = RES2H_ARCHIVE_VERSION; const uint32_t fileFlags = mustUse64Bit ? 64 : 32; outStream.write(reinterpret_cast<const char *>(&fileVersion), sizeof(uint32_t)); outStream.write(reinterpret_cast<const char *>(&fileFlags), sizeof(uint32_t)); // add dummy archive size to file uint64_t archiveSize = 0; outStream.write(reinterpret_cast<const char *>(&archiveSize), (mustUse64Bit ? sizeof(uint64_t) : sizeof(uint32_t))); // add number of directory entries to file outStream.write(reinterpret_cast<const char *>(&nrOfEntries), sizeof(uint32_t)); // calculate data start offset behind directory uint64_t dataStart = mustUse64Bit ? RES2H_HEADER_SIZE_64 : RES2H_HEADER_SIZE_32; dataStart += directorySize; // add directory for all files for (const auto & file : fileList) { // add size of name if (file.internalName.size() > UINT16_MAX) { std::cout << "Error: File name \"" << file.internalName << "\" is too long!" << std::endl; outStream.close(); return false; } const uint16_t nameSize = static_cast<uint16_t>(file.internalName.size()); outStream.write(reinterpret_cast<const char *>(&nameSize), sizeof(uint16_t)); // add name outStream.write(reinterpret_cast<const char *>(&file.internalName[0]), nameSize); // add flags const uint32_t entryFlags = 0; outStream.write(reinterpret_cast<const char *>(&entryFlags), sizeof(uint32_t)); uint64_t fileChecksum = mustUse64Bit ? calculateFletcher<uint64_t>(file.inPath.string()) : calculateFletcher<uint32_t>(file.inPath.string()); // add data size, offset from file start to start of data and checksum outStream.write(reinterpret_cast<const char *>(&file.size), (mustUse64Bit ? sizeof(uint64_t) : sizeof(uint32_t))); outStream.write(reinterpret_cast<const char *>(&dataStart), (mustUse64Bit ? sizeof(uint64_t) : sizeof(uint32_t))); outStream.write(reinterpret_cast<const char *>(&fileChecksum), (mustUse64Bit ? sizeof(uint64_t) : sizeof(uint32_t))); if (beVerbose) { std::cout << "Creating directory entry for \"" << file.internalName << "\"" << std::endl; std::cout << "Data starts at " << std::dec << std::showbase << dataStart << " bytes" << std::endl; std::cout << "Size is " << std::dec << file.size << " bytes" << std::endl; std::cout << "Fletcher" << (mustUse64Bit ? "64" : "32") << " checksum is " << std::hex << std::showbase << fileChecksum << std::endl; } // now add size of this entries data to start offset for next data block dataStart += file.size; } // add data for all files for (const auto & file : fileList) { // try to open file std::ifstream inStream; inStream.open(file.inPath.string(), std::ifstream::in | std::ifstream::binary); if (inStream.is_open() && inStream.good()) { if (beVerbose) { std::cout << "Adding data for \"" << file.internalName << "\"" << std::endl; } std::streamsize overallDataSize = 0; // copy data from input to output file while (!inStream.eof() && inStream.good()) { unsigned char buffer[4096]; std::streamsize readSize = sizeof(buffer); try { // try reading data from input file inStream.read(reinterpret_cast<char *>(&buffer), sizeof(buffer)); } catch (std::ios_base::failure) { /*ignore read failure. salvage what we can.*/ } // store how many bytes were actually read readSize = inStream.gcount(); // write to output file outStream.write(reinterpret_cast<const char *>(&buffer), readSize); // increase size of overall data read overallDataSize += readSize; } // close input file inStream.close(); // check if the file was completely read if (overallDataSize != file.size) { std::cout << "Error: Failed to completely copy file \"" << file.inPath.string() << "\" to binary data!" << std::endl; outStream.close(); return false; } } else { std::cout << "Error: Failed to open file \"" << file.inPath.string() << "\" for reading!" << std::endl; outStream.close(); return false; } } // final archive size is current size + checksum. write size to the header now archiveSize = static_cast<uint64_t>(outStream.tellg()) + (mustUse64Bit ? sizeof(uint64_t) : sizeof(uint32_t)); outStream.seekg(RES2H_OFFSET_ARCHIVE_SIZE); outStream.write(reinterpret_cast<const char *>(&archiveSize), (mustUse64Bit ? sizeof(uint64_t) : sizeof(uint32_t))); // close file outStream.close(); if (beVerbose) { std::cout << "Binary archive creation succeeded." << std::endl; std::cout << "Archive has " << std::dec << archiveSize << " bytes." << std::endl; } // calculate checksum of whole file const uint64_t checksum = mustUse64Bit ? calculateFletcher<uint64_t>(filePath.string()) : calculateFletcher<uint32_t>(filePath.string()); // open file again, move to end of file and append checksum outStream.open(filePath.string(), std::ofstream::out | std::ofstream::binary | std::ofstream::app); if (outStream.is_open() && outStream.good()) { outStream.seekg(0, std::ios::end); outStream.write(reinterpret_cast<const char *>(&checksum), (mustUse64Bit ? sizeof(uint64_t) : sizeof(uint32_t))); // close file outStream.close(); } else { std::cout << "Error: Failed to open file \"" << filePath.string() << "\" for writing!" << std::endl; return false; } if (beVerbose) { std::cout << "Archive Fletcher" << (mustUse64Bit ? "64" : "32") << " checksum is " << std::hex << std::showbase << checksum << "." << std::endl; } return true; } else { std::cout << "Error: Failed to open file \"" << filePath.string() << "\" for writing!" << std::endl; return false; } return false; }
std::vector<FileData> getFileDataFrom(const FS_NAMESPACE::path & inPath, const FS_NAMESPACE::path & outPath, const FS_NAMESPACE::path & parentDir, const bool recurse) { // get all files from directory std::vector<FileData> files; // check for infinite symlinks if (FS_NAMESPACE::is_symlink(inPath)) { // check if the symlink points somewhere in the path. this would recurse if (inPath.string().find(FS_NAMESPACE::canonical(inPath).string()) == 0) { std::cout << "Warning: Path " << inPath << " contains recursive symlink! Skipping." << std::endl; return files; } } // iterate through source directory searching for files const FS_NAMESPACE::directory_iterator dirEnd; for (FS_NAMESPACE::directory_iterator fileIt(inPath); fileIt != dirEnd; ++fileIt) { FS_NAMESPACE::path filePath = (*fileIt).path(); if (!FS_NAMESPACE::is_directory(filePath)) { if (beVerbose) { std::cout << "Found input file " << filePath << std::endl; } // add file to list FileData temp; temp.inPath = filePath; // replace dots in file name with '_' and add a .c/.cpp extension std::string newFileName = filePath.filename().generic_string(); std::replace(newFileName.begin(), newFileName.end(), '.', '_'); if (useC) { newFileName.append(".c"); } else { newFileName.append(".cpp"); } // remove parent directory of file from path for internal name. This could surely be done in a safer way FS_NAMESPACE::path subPath(filePath.generic_string().substr(parentDir.generic_string().size() + 1)); // add a ":/" before the name to mark internal resources (Yes. Hello Qt!) temp.internalName = ":/" + subPath.generic_string(); // add subdir below parent path to name to enable multiple files with the same name std::string subDirString(subPath.remove_filename().generic_string()); if (!subDirString.empty()) { // replace dir separators by underscores std::replace(subDirString.begin(), subDirString.end(), '/', '_'); // add in front of file name newFileName = subDirString + "_" + newFileName; } // build new output file name temp.outPath = outPath / newFileName; if (beVerbose) { std::cout << "Internal name will be \"" << temp.internalName << "\"" << std::endl; std::cout << "Output path is " << temp.outPath << std::endl; } // get file size try { temp.size = static_cast<uint64_t>(FS_NAMESPACE::file_size(filePath)); if (beVerbose) { std::cout << "Size is " << temp.size << " bytes." << std::endl; } } catch (...) { std::cout << "Error: Failed to get size of " << filePath << "!" << std::endl; temp.size = 0; } // add file to list files.push_back(temp); } } // does the user want subdirectories? if (recurse) { // iterate through source directory again searching for directories for (FS_NAMESPACE::directory_iterator dirIt(inPath); dirIt != dirEnd; ++dirIt) { FS_NAMESPACE::path dirPath = (*dirIt).path(); if (FS_NAMESPACE::is_directory(dirPath)) { if (beVerbose) { std::cout << "Found subdirectory " << dirPath << std::endl; } // subdirectory found. recurse. std::vector<FileData> subFiles = getFileDataFrom(dirPath, outPath, parentDir, recurse); // add returned result to file list files.insert(files.end(), subFiles.cbegin(), subFiles.cend()); } } } // return result return files; }