Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
252 lines (209 sloc) 7.66 KB
/*
** 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;
}
}