Add files via upload
This commit is contained in:
35 files changed
+14482
No files matched your search
@@ -0,0 +1,162 @@
|
||||
#include "branding_bundle.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
namespace branding {
|
||||
namespace {
|
||||
|
||||
void put16(std::vector<unsigned char>& out, std::uint16_t v) {
|
||||
out.push_back(static_cast<unsigned char>(v));
|
||||
out.push_back(static_cast<unsigned char>(v >> 8));
|
||||
}
|
||||
void put32(std::vector<unsigned char>& out, std::uint32_t v) {
|
||||
put16(out, static_cast<std::uint16_t>(v));
|
||||
put16(out, static_cast<std::uint16_t>(v >> 16));
|
||||
}
|
||||
std::uint16_t get16(const std::vector<unsigned char>& in, std::size_t p) {
|
||||
return static_cast<std::uint16_t>(in[p] | (static_cast<std::uint16_t>(in[p + 1]) << 8));
|
||||
}
|
||||
std::uint32_t get32(const std::vector<unsigned char>& in, std::size_t p) {
|
||||
return static_cast<std::uint32_t>(get16(in, p)) |
|
||||
(static_cast<std::uint32_t>(get16(in, p + 2)) << 16);
|
||||
}
|
||||
std::uint32_t crc32(const unsigned char* data, std::size_t size) {
|
||||
std::uint32_t crc = 0xffffffffU;
|
||||
for (std::size_t i = 0; i < size; ++i) {
|
||||
crc ^= data[i];
|
||||
for (int bit = 0; bit < 8; ++bit)
|
||||
crc = (crc >> 1) ^ (0xedb88320U & (0U - (crc & 1U)));
|
||||
}
|
||||
return ~crc;
|
||||
}
|
||||
std::string escape(const std::string& value) {
|
||||
std::string out = "\"";
|
||||
for (unsigned char c : value) {
|
||||
if (c == '"' || c == '\\') { out.push_back('\\'); out.push_back(static_cast<char>(c)); }
|
||||
else if (c < 0x20) {
|
||||
static constexpr char hex[] = "0123456789abcdef";
|
||||
out += "\\u00"; out.push_back(hex[c >> 4]); out.push_back(hex[c & 15]);
|
||||
} else out.push_back(static_cast<char>(c));
|
||||
}
|
||||
out.push_back('"');
|
||||
return out;
|
||||
}
|
||||
void appendUtf8(std::string& out, std::uint32_t c) {
|
||||
if (c <= 0x7f) out.push_back(static_cast<char>(c));
|
||||
else if (c <= 0x7ff) { out.push_back(static_cast<char>(0xc0 | (c >> 6))); out.push_back(static_cast<char>(0x80 | (c & 63))); }
|
||||
else { out.push_back(static_cast<char>(0xe0 | (c >> 12))); out.push_back(static_cast<char>(0x80 | ((c >> 6) & 63))); out.push_back(static_cast<char>(0x80 | (c & 63))); }
|
||||
}
|
||||
bool jsonString(const std::string& json, const std::string& key, std::string& value) {
|
||||
const std::string marker = "\"" + key + "\"";
|
||||
std::size_t p = json.find(marker);
|
||||
if (p == std::string::npos) return false;
|
||||
p = json.find(':', p + marker.size());
|
||||
if (p == std::string::npos) return false;
|
||||
while (++p < json.size() && (json[p] == ' ' || json[p] == '\t' || json[p] == '\r' || json[p] == '\n')) {}
|
||||
if (p >= json.size() || json[p++] != '"') return false;
|
||||
value.clear();
|
||||
while (p < json.size()) {
|
||||
unsigned char c = static_cast<unsigned char>(json[p++]);
|
||||
if (c == '"') return true;
|
||||
if (c != '\\') { value.push_back(static_cast<char>(c)); continue; }
|
||||
if (p >= json.size()) return false;
|
||||
c = static_cast<unsigned char>(json[p++]);
|
||||
switch (c) {
|
||||
case '"': case '\\': case '/': value.push_back(static_cast<char>(c)); break;
|
||||
case 'b': value.push_back('\b'); break; case 'f': value.push_back('\f'); break;
|
||||
case 'n': value.push_back('\n'); break; case 'r': value.push_back('\r'); break; case 't': value.push_back('\t'); break;
|
||||
case 'u': {
|
||||
if (p + 4 > json.size()) return false;
|
||||
std::uint32_t cp = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
const char h = json[p++]; cp <<= 4;
|
||||
if (h >= '0' && h <= '9') cp |= static_cast<unsigned>(h - '0');
|
||||
else if (h >= 'a' && h <= 'f') cp |= static_cast<unsigned>(h - 'a' + 10);
|
||||
else if (h >= 'A' && h <= 'F') cp |= static_cast<unsigned>(h - 'A' + 10);
|
||||
else return false;
|
||||
}
|
||||
appendUtf8(value, cp); break;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool writeConfig(const std::string& path, const Bundle& bundle, std::string& error) {
|
||||
if (bundle.app.empty() || bundle.app.size() > 40 || bundle.singular.empty() || bundle.singular.size() > 24 ||
|
||||
bundle.plural.empty() || bundle.plural.size() > 24 || bundle.logo.empty() || bundle.logo.size() > 8U * 1024U * 1024U) {
|
||||
error = "Branding values or logo are outside supported limits."; return false;
|
||||
}
|
||||
const std::string json = "{\"app\":" + escape(bundle.app) + ",\"singular\":" + escape(bundle.singular) +
|
||||
",\"plural\":" + escape(bundle.plural) + "}";
|
||||
struct Entry { std::string name; const unsigned char* data; std::uint32_t size; std::uint32_t crc; std::uint32_t offset; };
|
||||
const std::string names[2] = {"branding.json", "logo.png"};
|
||||
const std::string jsonOwned = json;
|
||||
const unsigned char* data[2] = {reinterpret_cast<const unsigned char*>(jsonOwned.data()), bundle.logo.data()};
|
||||
const std::uint32_t sizes[2] = {static_cast<std::uint32_t>(jsonOwned.size()), static_cast<std::uint32_t>(bundle.logo.size())};
|
||||
std::vector<unsigned char> archive;
|
||||
Entry entries[2]{};
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
Entry& e = entries[i]; e.name = names[i]; e.data = data[i]; e.size = sizes[i]; e.crc = crc32(e.data, e.size); e.offset = static_cast<std::uint32_t>(archive.size());
|
||||
put32(archive, 0x04034b50U); put16(archive, 20); put16(archive, 0); put16(archive, 0);
|
||||
put16(archive, 0); put16(archive, 0); put32(archive, e.crc); put32(archive, e.size); put32(archive, e.size);
|
||||
put16(archive, static_cast<std::uint16_t>(e.name.size())); put16(archive, 0);
|
||||
archive.insert(archive.end(), e.name.begin(), e.name.end()); archive.insert(archive.end(), e.data, e.data + e.size);
|
||||
}
|
||||
const std::uint32_t centralOffset = static_cast<std::uint32_t>(archive.size());
|
||||
for (const Entry& e : entries) {
|
||||
put32(archive, 0x02014b50U); put16(archive, 20); put16(archive, 20); put16(archive, 0); put16(archive, 0);
|
||||
put16(archive, 0); put16(archive, 0); put32(archive, e.crc); put32(archive, e.size); put32(archive, e.size);
|
||||
put16(archive, static_cast<std::uint16_t>(e.name.size())); put16(archive, 0); put16(archive, 0); put16(archive, 0);
|
||||
put16(archive, 0); put32(archive, 0); put32(archive, e.offset);
|
||||
archive.insert(archive.end(), e.name.begin(), e.name.end());
|
||||
}
|
||||
const std::uint32_t centralSize = static_cast<std::uint32_t>(archive.size()) - centralOffset;
|
||||
put32(archive, 0x06054b50U); put16(archive, 0); put16(archive, 0); put16(archive, 2); put16(archive, 2);
|
||||
put32(archive, centralSize); put32(archive, centralOffset); put16(archive, 0);
|
||||
std::ofstream out(path, std::ios::binary | std::ios::trunc);
|
||||
if (!out) { error = "Could not create branding.config."; return false; }
|
||||
out.write(reinterpret_cast<const char*>(archive.data()), static_cast<std::streamsize>(archive.size()));
|
||||
out.flush();
|
||||
if (!out) { error = "Could not write branding.config."; return false; }
|
||||
error.clear(); return true;
|
||||
}
|
||||
|
||||
bool readConfig(const std::string& path, Bundle& bundle, std::string& error) {
|
||||
std::ifstream in(path, std::ios::binary | std::ios::ate);
|
||||
if (!in) { error = "Copy a .config file to the Switchnote folder first."; return false; }
|
||||
const std::streamoff length = in.tellg();
|
||||
if (length < 22 || length > 9 * 1024 * 1024) { error = "Branding config size is invalid."; return false; }
|
||||
std::vector<unsigned char> bytes(static_cast<std::size_t>(length)); in.seekg(0); in.read(reinterpret_cast<char*>(bytes.data()), length);
|
||||
if (!in) { error = "Could not read branding config."; return false; }
|
||||
std::string json; std::vector<unsigned char> logo; std::size_t p = 0;
|
||||
while (p + 4 <= bytes.size() && get32(bytes, p) == 0x04034b50U) {
|
||||
if (p + 30 > bytes.size()) { error = "Branding config archive is truncated."; return false; }
|
||||
const std::uint16_t flags = get16(bytes, p + 6), method = get16(bytes, p + 8), nameSize = get16(bytes, p + 26), extraSize = get16(bytes, p + 28);
|
||||
const std::uint32_t expectedCrc = get32(bytes, p + 14), compressed = get32(bytes, p + 18), uncompressed = get32(bytes, p + 22);
|
||||
const std::size_t nameAt = p + 30, dataAt = nameAt + nameSize + extraSize;
|
||||
if (flags != 0 || method != 0 || compressed != uncompressed || dataAt > bytes.size() || compressed > bytes.size() - dataAt) { error = "Only standard uncompressed branding.config files are supported."; return false; }
|
||||
const std::string name(bytes.begin() + static_cast<std::ptrdiff_t>(nameAt), bytes.begin() + static_cast<std::ptrdiff_t>(nameAt + nameSize));
|
||||
const auto* data = bytes.data() + dataAt;
|
||||
if (crc32(data, uncompressed) != expectedCrc) { error = "Branding config file checksum failed."; return false; }
|
||||
if (name == "branding.json") json.assign(reinterpret_cast<const char*>(data), uncompressed);
|
||||
else if (name == "logo.png") logo.assign(data, data + uncompressed);
|
||||
else if (name.find('/') != std::string::npos || name.find('\\') != std::string::npos || name == "..") { error = "Branding config contains an unsafe file path."; return false; }
|
||||
p = dataAt + compressed;
|
||||
}
|
||||
if (json.empty() || logo.empty() || logo.size() > 8U * 1024U * 1024U ||
|
||||
!jsonString(json, "app", bundle.app) || !jsonString(json, "singular", bundle.singular) || !jsonString(json, "plural", bundle.plural) ||
|
||||
bundle.app.empty() || bundle.app.size() > 40 || bundle.singular.empty() || bundle.singular.size() > 24 || bundle.plural.empty() || bundle.plural.size() > 24) {
|
||||
error = "Branding config is missing valid names or a logo."; return false;
|
||||
}
|
||||
bundle.logo = std::move(logo);
|
||||
error.clear(); return true;
|
||||
}
|
||||
|
||||
} // namespace branding
|
||||
Reference in new issue
Block a user
