Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| /* | |
| ** Copyright (c) 2017, Thomas Farr | |
| ** | |
| ** This Source Code Form is subject to the terms of the Mozilla Public | |
| ** License, v. 2.0. If a copy of the MPL was not distributed with this | |
| ** file, You can obtain one at https://mozilla.org/MPL/2.0/. | |
| */ | |
| #include <algorithm> | |
| #include <args.hxx> | |
| #include <fstream> | |
| #include <iomanip> | |
| #include <iostream> | |
| #include <regex> | |
| #include <string> | |
| struct PositiveIntReader { | |
| bool operator()(const std::string &name, const std::string &value, | |
| int32_t &destination) { | |
| std::istringstream ss(value); | |
| ss >> destination; | |
| if (ss.rdbuf()->in_avail() > 0 || destination < 1) { | |
| std::ostringstream problem; | |
| problem << "Argument '" << name << "' must be a positive int, received '" | |
| << value << "'"; | |
| throw args::ParseError(problem.str()); | |
| } | |
| return true; | |
| } | |
| }; | |
| struct PO2IntReader { | |
| bool operator()(const std::string &name, const std::string &value, | |
| int32_t &destination) { | |
| std::istringstream ss(value); | |
| ss >> destination; | |
| if (ss.rdbuf()->in_avail() > 0 || destination < 1 || | |
| (destination & (destination - 1)) != 0) { | |
| std::ostringstream problem; | |
| problem << "Argument '" << name << "' must be a power of two, received '" | |
| << value << "'"; | |
| throw args::ParseError(problem.str()); | |
| } | |
| return true; | |
| } | |
| }; | |
| std::string basename(const std::string &path) { | |
| #if defined(WIN32) || defined(_WIN32) | |
| static constexpr auto path_seps = "/\\"; | |
| #else | |
| static constexpr auto path_seps = "/"; | |
| #endif | |
| auto pos = path.find_last_of(path_seps); | |
| if (pos == path.length() - 1) { | |
| return basename(path.substr(0, path.length() - 1)); | |
| } else if (pos == std::string::npos) { | |
| return path; | |
| } else { | |
| return path.substr(pos + 1); | |
| } | |
| } | |
| std::string make_c_identifier(const std::string &str) { | |
| static std::regex invalidChar("[^A-Za-z0-9_./-]"); | |
| static std::regex separatorChar("[./-]"); | |
| static std::regex leadingDigit("^(\\d)"); | |
| auto res = std::regex_replace(str, invalidChar, ""); | |
| res = std::regex_replace(res, separatorChar, "_"); | |
| return std::regex_replace(res, leadingDigit, "_$1"); | |
| } | |
| enum class result { success, identifier_invalid, read_error, empty_stream }; | |
| result bin2s(const std::string &identifier, std::istream &input, | |
| std::ostream &output, int32_t alignment = 4, | |
| int32_t lineLength = 16) { | |
| auto c_identifier = make_c_identifier(identifier); | |
| if (c_identifier.empty()) return result::identifier_invalid; | |
| auto cur_pos = input.tellg(); | |
| input.seekg(0, std::ios::end); | |
| auto size = input.tellg() - cur_pos; | |
| input.seekg(cur_pos); | |
| if (size == 0) return result::empty_stream; | |
| input.get(); | |
| if (!input) return result::read_error; | |
| input.seekg(-1, std::ios::cur); | |
| output << " .section .rodata" << std::endl | |
| << " .balign " << alignment << std::endl | |
| << " .global " << c_identifier << std::endl | |
| << " .global " << c_identifier << "_end" << std::endl | |
| << " .global " << c_identifier << "_size" << std::endl | |
| << std::endl | |
| << c_identifier << ":" << std::endl; | |
| std::streamsize nRead = 0; | |
| std::vector<uint8_t> readBytes(lineLength); | |
| for (auto remaining = size; remaining > 0; remaining -= nRead) { | |
| input.read(reinterpret_cast<char *>(&readBytes[0]), | |
| std::min(static_cast<std::streamsize>(lineLength), remaining)); | |
| nRead = input.gcount(); | |
| if (!input) return result::read_error; | |
| output << " .byte "; | |
| for (std::streamsize n = 0; n < nRead; ++n) { | |
| output << std::setw(3) << static_cast<uint16_t>(readBytes[n]); | |
| if (n < nRead - 1) output << ','; | |
| } | |
| output << std::endl; | |
| } | |
| output << std::endl | |
| << c_identifier << "_end:" << std::endl | |
| << std::endl | |
| << " .align" << std::endl | |
| << c_identifier << "_size: .int " << size << std::endl; | |
| return result::success; | |
| } | |
| int bin2s_files(const std::vector<std::string> &files, std::ostream &output, | |
| int32_t alignment = 4, int32_t lineLength = 16) { | |
| output << "/* Generated by BIN2S - please don't edit directly */" | |
| << std::endl; | |
| for (const auto &file : files) { | |
| std::ifstream input(file, std::ios::binary); | |
| if (!input) { | |
| std::cerr << "bin2s: error: could not open \"" << file << '"' | |
| << std::endl; | |
| return 1; | |
| } | |
| auto ret = bin2s(basename(file), input, output, alignment, lineLength); | |
| input.close(); | |
| switch (ret) { | |
| case result::success: | |
| break; | |
| case result::identifier_invalid: | |
| std::cerr | |
| << "bin2s: error: filename does not contain any valid characters \"" | |
| << file << '"' << std::endl; | |
| return 1; | |
| case result::read_error: | |
| std::cerr << "bin2s: error: unable to read file \"" << file << '"' | |
| << std::endl; | |
| return 1; | |
| case result::empty_stream: | |
| std::cerr << "bin2s: warning: skipping empty file \"" << file << '"' | |
| << std::endl; | |
| break; | |
| } | |
| } | |
| return 0; | |
| } | |
| int main(int argc, char *argv[]) { | |
| args::ArgumentParser parser("Convert binary files to GCC assembly modules."); | |
| static constexpr auto extended_description = R"__( | |
| For each input file it will output assembly defining: | |
| * {identifier}: | |
| An array of bytes containing the data. | |
| * {identifier}_end: | |
| Will be at the location directly after the end of the data. | |
| * {identifier}_size: | |
| An unsigned int containing the length of the data in bytes. | |
| Roughly equivalent to this pseudocode: | |
| unsigned int identifier_size = ... | |
| unsigned char identifier[identifier_size] = { ... } | |
| unsigned char identifier_end[] = identifier + identifier_size | |
| Where {identifier} is the input file's name, | |
| sanitized to produce a legal C identifier, by doing the following: | |
| * Stripping all character that are not ASCII letters, digits or one of _-./ | |
| * Replacing all of -./ with _ | |
| * Prepending _ if the remaining identifier begins with a digit. | |
| e.g. for gfx/foo.bin {identifier} will be foo_bin, | |
| and for 4bit.chr it will be _4bit_chr. | |
| )__"; | |
| parser.SetArgumentSeparations(false, true, true, true); | |
| args::HelpFlag helpFlag(parser, "HELP", "Show this help message and exit", | |
| {'h', "help"}); | |
| args::ValueFlag<int32_t, PO2IntReader> alignmentFlag( | |
| parser, "ALIGNMENT", "Boundary alignment, in bytes [default: 4]", | |
| {'a', "alignment"}, 4); | |
| args::ValueFlag<int32_t, PositiveIntReader> lineLengthFlag( | |
| parser, "LINE_LENGTH", | |
| "Number of bytes to output per line of ASM [default: 16]", | |
| {'l', "line-length"}, 16); | |
| args::ValueFlag<std::string> outputFlag( | |
| parser, "OUTPUT", "Output file, \"-\" represents stdout [default: -]", | |
| {'o', "output"}, "-"); | |
| args::PositionalList<std::string> filesList( | |
| parser, "FILES", "Binary file to convert to GCC assembly"); | |
| try { | |
| parser.ParseCLI(argc, argv); | |
| } catch (args::Help) { | |
| std::cout << parser; | |
| std::cout << extended_description; | |
| return 0; | |
| } catch (args::ParseError e) { | |
| std::cerr << e.what() << std::endl; | |
| std::cerr << parser; | |
| std::cerr << extended_description; | |
| return 1; | |
| } | |
| auto alignment = args::get(alignmentFlag); | |
| auto lineLength = args::get(lineLengthFlag); | |
| const auto &outputFile = args::get(outputFlag); | |
| const auto &inputFiles = args::get(filesList); | |
| if (inputFiles.empty()) return 0; | |
| if (outputFile == "-") { | |
| return bin2s_files(inputFiles, std::cout, alignment, lineLength); | |
| } else { | |
| std::ofstream output(outputFile); | |
| if (!output) { | |
| std::cerr << "bin2s: error: could not open output file \"" << outputFile | |
| << '"' << std::endl; | |
| return 1; | |
| } | |
| auto ret = bin2s_files(inputFiles, output, alignment, lineLength); | |
| output.close(); | |
| return ret; | |
| } | |
| } |