mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-09-03 07:33:06 +02:00
Update to v106r65 release.
byuu says: This synchronizes bsnes/higan with many recent internal nall changes. This will be the last WIP until I am situated in Japan. Apologies for the bugfixes that didn't get applied yet, I ran out of time.
This commit is contained in:
@@ -1,272 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/random.hpp>
|
||||
#include <nall/cipher/chacha20.hpp>
|
||||
#include <nall/elliptic-curve/ed25519.hpp>
|
||||
#include <nall/encode/base.hpp>
|
||||
#include <nall/decode/base.hpp>
|
||||
#include <nall/encode/lzsa.hpp>
|
||||
#include <nall/decode/lzsa.hpp>
|
||||
|
||||
namespace nall { namespace Beat {
|
||||
|
||||
struct Archive {
|
||||
struct Encryption {
|
||||
string type;
|
||||
uint256_t key = 0;
|
||||
uint192_t nonce = 0;
|
||||
};
|
||||
|
||||
struct Signature {
|
||||
string type;
|
||||
uint256_t privateKey = 0;
|
||||
uint256_t publicKey = 0;
|
||||
uint512_t signature = 0;
|
||||
};
|
||||
|
||||
struct Compression {
|
||||
string type;
|
||||
uint size = 0;
|
||||
};
|
||||
|
||||
//timestamps are human-readable strings in ISO 8601 format; save for T=>space
|
||||
//times are stored in UTC, rather than local times
|
||||
struct Timestamps {
|
||||
string created;
|
||||
string modified;
|
||||
string accessed;
|
||||
};
|
||||
|
||||
struct Permissions {
|
||||
string name;
|
||||
bool readable = false;
|
||||
bool writable = false;
|
||||
bool executable = false;
|
||||
};
|
||||
|
||||
struct Node {
|
||||
string name;
|
||||
|
||||
//paths and files
|
||||
Timestamps timestamps;
|
||||
struct {
|
||||
Permissions owner;
|
||||
Permissions group;
|
||||
Permissions other;
|
||||
} permissions;
|
||||
|
||||
//files only
|
||||
uint offset = 0;
|
||||
uint size = 0;
|
||||
|
||||
Compression compression;
|
||||
|
||||
string filename;
|
||||
vector<uint8_t> filedata;
|
||||
};
|
||||
|
||||
auto append(const Node& node) -> bool;
|
||||
auto encryptionManifest() -> string;
|
||||
auto manifest() -> string;
|
||||
auto create() -> vector<uint8_t>;
|
||||
|
||||
//internal functions
|
||||
auto encode() -> vector<uint8_t>;
|
||||
auto encode(Node& node, uint64_t offset) -> vector<uint8_t>;
|
||||
|
||||
Encryption encryption;
|
||||
Signature signature;
|
||||
Compression compression; //solid archiving
|
||||
vector<Node> nodes;
|
||||
};
|
||||
|
||||
auto Archive::append(const Node& node) -> bool {
|
||||
//prevent multiple nodes with the same name
|
||||
if(nodes.find([&](auto& item) { return item.name == node.name; })) return false;
|
||||
|
||||
nodes.append(node);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Archive::encryptionManifest() -> string {
|
||||
string manifest;
|
||||
manifest.append("encryption\n");
|
||||
manifest.append(" type: ", encryption.type, "\n");
|
||||
manifest.append(" nonce: ", Encode::Base<57>(encryption.nonce), "\n");
|
||||
return manifest;
|
||||
}
|
||||
|
||||
auto Archive::manifest() -> string {
|
||||
string manifest;
|
||||
manifest.append("archive\n");
|
||||
|
||||
for(auto& node : nodes) {
|
||||
if(node.name.endsWith("/")) {
|
||||
manifest.append(" path: ", string{node.name}.trimRight("/", 1L), "\n");
|
||||
} else {
|
||||
manifest.append(" file: ", node.name, "\n");
|
||||
manifest.append(" offset: ", node.offset, "\n");
|
||||
manifest.append(" size: ", node.size, "\n");
|
||||
if(node.compression.type) {
|
||||
manifest.append(" compression: ", node.compression.type, "\n");
|
||||
manifest.append(" size: ", node.compression.size, "\n");
|
||||
}
|
||||
}
|
||||
if(node.timestamps.created || node.timestamps.modified || node.timestamps.accessed) {
|
||||
manifest.append(" timestamp\n");
|
||||
if(auto timestamp = node.timestamps.created ) manifest.append(" created: ", timestamp, "\n");
|
||||
if(auto timestamp = node.timestamps.modified) manifest.append(" modified: ", timestamp, "\n");
|
||||
if(auto timestamp = node.timestamps.accessed) manifest.append(" accessed: ", timestamp, "\n");
|
||||
}
|
||||
if(node.permissions.owner.name || node.permissions.group.name || node.permissions.other.name) {
|
||||
manifest.append(" permission\n");
|
||||
if(node.permissions.owner.name) {
|
||||
manifest.append(" owner: ", node.permissions.owner.name, "\n");
|
||||
if(node.permissions.owner.readable ) manifest.append(" readable\n");
|
||||
if(node.permissions.owner.writable ) manifest.append(" writable\n");
|
||||
if(node.permissions.owner.executable) manifest.append(" executable\n");
|
||||
}
|
||||
if(node.permissions.group.name) {
|
||||
manifest.append(" group: ", node.permissions.group.name, "\n");
|
||||
if(node.permissions.group.readable ) manifest.append(" readable\n");
|
||||
if(node.permissions.group.writable ) manifest.append(" writable\n");
|
||||
if(node.permissions.group.executable) manifest.append(" executable\n");
|
||||
}
|
||||
if(node.permissions.other.name) {
|
||||
manifest.append(" other\n");
|
||||
if(node.permissions.other.readable ) manifest.append(" readable\n");
|
||||
if(node.permissions.other.writable ) manifest.append(" writable\n");
|
||||
if(node.permissions.other.executable) manifest.append(" executable\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(compression.type) {
|
||||
manifest.append(" compression: ", compression.type, "\n");
|
||||
manifest.append(" size: ", compression.size, "\n");
|
||||
}
|
||||
|
||||
if(signature.type == "ed25519") {
|
||||
manifest.append(" signature: ", signature.type, "\n");
|
||||
manifest.append(" publicKey: ", Encode::Base<57>(signature.publicKey), "\n");
|
||||
manifest.append(" signature: ", Encode::Base<57>(signature.signature), "\n");
|
||||
}
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
auto Archive::create() -> vector<uint8_t> {
|
||||
vector<uint8_t> output;
|
||||
|
||||
output.append('B');
|
||||
output.append('P');
|
||||
output.append('A');
|
||||
output.append('1');
|
||||
|
||||
nodes.sort([&](auto& lhs, auto& rhs) {
|
||||
return string::compare(lhs.name, rhs.name) < 0;
|
||||
});
|
||||
|
||||
auto content = encode();
|
||||
if(compression.type == "lzsa") {
|
||||
content = Encode::LZSA(content);
|
||||
compression.size = content.size();
|
||||
}
|
||||
|
||||
if(signature.type == "ed25519") {
|
||||
EllipticCurve::Ed25519 ed25519;
|
||||
signature.publicKey = ed25519.publicKey(signature.privateKey);
|
||||
signature.signature = ed25519.sign(content, signature.privateKey);
|
||||
}
|
||||
|
||||
if(encryption.type == "xchacha20") {
|
||||
//a randomly generated nonce is preferred
|
||||
if(!encryption.nonce) {
|
||||
CSPRNG csprng;
|
||||
encryption.nonce = csprng.random<uint192_t>();
|
||||
}
|
||||
Cipher::XChaCha20 xchacha20{encryption.key, encryption.nonce};
|
||||
content = xchacha20.encrypt(content);
|
||||
|
||||
string manifest;
|
||||
manifest.append("encryption\n");
|
||||
manifest.append(" type: ", encryption.type, "\n");
|
||||
manifest.append(" nonce: ", Encode::Base<57>(encryption.nonce), "\n");
|
||||
|
||||
output.append(content);
|
||||
for(uint8_t byte : manifest) output.append(byte);
|
||||
output.appendl(manifest.size(), 8);
|
||||
} else {
|
||||
encryption = {};
|
||||
output.append(content);
|
||||
}
|
||||
|
||||
auto sha256 = Hash::SHA256(output).value();
|
||||
output.appendl(sha256, 32);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto Archive::encode() -> vector<uint8_t> {
|
||||
vector<uint8_t> output;
|
||||
|
||||
for(auto& node : nodes) {
|
||||
if(node.filename) {
|
||||
node.timestamps.created = chrono::utc::datetime(inode::timestamp(node.filename, inode::time::create));
|
||||
node.timestamps.accessed = chrono::utc::datetime(inode::timestamp(node.filename, inode::time::access));
|
||||
node.timestamps.modified = chrono::utc::datetime(inode::timestamp(node.filename, inode::time::modify));
|
||||
|
||||
uint mode = inode::mode(node.filename);
|
||||
node.permissions.owner.name = inode::user(node.filename);
|
||||
node.permissions.owner.executable = mode & 0100;
|
||||
node.permissions.owner.writable = mode & 0200;
|
||||
node.permissions.owner.readable = mode & 0400;
|
||||
node.permissions.group.name = inode::group(node.filename);
|
||||
node.permissions.group.executable = mode & 0010;
|
||||
node.permissions.group.writable = mode & 0020;
|
||||
node.permissions.group.readable = mode & 0040;
|
||||
node.permissions.other.name = " ";
|
||||
node.permissions.other.executable = mode & 0001;
|
||||
node.permissions.other.writable = mode & 0002;
|
||||
node.permissions.other.readable = mode & 0004;
|
||||
}
|
||||
|
||||
if(node.name.endsWith("/")) continue;
|
||||
|
||||
auto buffer = encode(node, output.size());
|
||||
output.append(buffer);
|
||||
}
|
||||
|
||||
auto manifest = this->manifest();
|
||||
for(auto byte : manifest) output.append(byte);
|
||||
for(auto byte : range(8)) output.append((uint64_t)manifest.size() >> byte * 8);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
auto Archive::encode(Node& node, uint64_t offset) -> vector<uint8_t> {
|
||||
node.offset = offset;
|
||||
|
||||
vector<uint8_t> output;
|
||||
|
||||
if(node.filename) {
|
||||
output = file::read(node.filename);
|
||||
} else {
|
||||
output = node.filedata;
|
||||
}
|
||||
|
||||
node.size = output.size();
|
||||
|
||||
if(node.compression.type == "lzsa") {
|
||||
output = Encode::LZSA(output);
|
||||
node.compression.size = output.size();
|
||||
} else {
|
||||
node.compression = {};
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}}
|
200
nall/beat/archive/container.hpp
Normal file
200
nall/beat/archive/container.hpp
Normal file
@@ -0,0 +1,200 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/beat/archive/node.hpp>
|
||||
|
||||
namespace nall { namespace Beat { namespace Archive {
|
||||
|
||||
struct Container {
|
||||
Container(array_view<uint8_t> = {});
|
||||
~Container();
|
||||
|
||||
auto isCompressed() const -> bool { return (bool)compression.type; }
|
||||
auto isSigned() const -> bool { return (bool)signature.type; }
|
||||
auto isEncrypted() const -> bool { return (bool)encryption.type; }
|
||||
|
||||
auto compressLZSA() -> void;
|
||||
auto signEd25519(uint256_t privateKey) -> void;
|
||||
auto encryptXChaCha20(uint256_t privateKey, uint192_t nonce = 0) -> void;
|
||||
|
||||
auto validate() -> bool;
|
||||
auto decryptXChaCha20(uint256_t privateKey) -> bool;
|
||||
auto verifyEd25519(uint256_t publicKey) -> bool;
|
||||
auto decompressLZSA() -> bool;
|
||||
|
||||
auto append(string name, string location) -> shared_pointer<Node>;
|
||||
auto appendPath(string name) -> shared_pointer<Node>;
|
||||
auto appendFile(string name, array_view<uint8_t> memory) -> shared_pointer<Node>;
|
||||
auto remove(string name) -> bool;
|
||||
auto find(string name) -> shared_pointer<Node>;
|
||||
auto sort() -> void;
|
||||
|
||||
auto begin() { return nodes.begin(); }
|
||||
auto end() { return nodes.end(); }
|
||||
|
||||
auto begin() const { return nodes.begin(); }
|
||||
auto end() const { return nodes.end(); }
|
||||
|
||||
auto rbegin() { return nodes.rbegin(); }
|
||||
auto rend() { return nodes.rend(); }
|
||||
|
||||
auto rbegin() const { return nodes.rbegin(); }
|
||||
auto rend() const { return nodes.rend(); }
|
||||
|
||||
vector<shared_pointer<Node>> nodes;
|
||||
vector<uint8_t> memory;
|
||||
string metadata;
|
||||
|
||||
struct Compression {
|
||||
string type;
|
||||
} compression;
|
||||
|
||||
struct Signature {
|
||||
string type;
|
||||
uint256_t privateKey = 0;
|
||||
uint256_t publicKey = 0;
|
||||
uint512_t value = 0;
|
||||
} signature;
|
||||
|
||||
struct Encryption {
|
||||
string type;
|
||||
uint256_t privateKey = 0;
|
||||
uint192_t nonce = 0;
|
||||
} encryption;
|
||||
};
|
||||
|
||||
Container::Container(array_view<uint8_t> memory) {
|
||||
this->memory.resize(memory.size());
|
||||
nall::memory::copy(this->memory.data(), memory.data(), memory.size());
|
||||
}
|
||||
|
||||
Container::~Container() {
|
||||
metadata = {};
|
||||
signature = {};
|
||||
encryption = {};
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto Container::compressLZSA() -> void {
|
||||
compression.type = "lzsa";
|
||||
}
|
||||
|
||||
auto Container::signEd25519(uint256_t privateKey) -> void {
|
||||
signature.type = "ed25519";
|
||||
signature.privateKey = privateKey;
|
||||
}
|
||||
|
||||
auto Container::encryptXChaCha20(uint256_t privateKey, uint192_t nonce) -> void {
|
||||
if(!nonce) {
|
||||
CSPRNG::XChaCha20 csprng;
|
||||
nonce = csprng.random<uint192_t>();
|
||||
}
|
||||
|
||||
encryption.type = "xchacha20";
|
||||
encryption.privateKey = privateKey;
|
||||
encryption.nonce = nonce;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto Container::validate() -> bool {
|
||||
array_view<uint8_t> memory = this->memory;
|
||||
if(memory.size() < 44) return false; //8 (metadata size) + 32 (SHA256) + 4 (signature)
|
||||
|
||||
if(memory[memory.size() - 4] != 'B') return false;
|
||||
if(memory[memory.size() - 3] != 'P') return false;
|
||||
if(memory[memory.size() - 2] != 'A') return false;
|
||||
if(memory[memory.size() - 1] != '1') return false;
|
||||
|
||||
auto sha256 = memory.readl<uint256_t>(memory.size() - 36, 32);
|
||||
if(Hash::SHA256({memory.data(), memory.size() - 36}).value() != sha256) return false;
|
||||
|
||||
auto size = memory.readl<uint64_t>(memory.size() - 44, 8);
|
||||
|
||||
if(size & 1ull << 63) {
|
||||
size -= 1ull << 63;
|
||||
metadata = memory.view(memory.size() - 44 - size, size);
|
||||
uint64_t offset = memory.size() - 44 - size;
|
||||
for(auto& byte : metadata) byte ^= offset++;
|
||||
} else {
|
||||
metadata = memory.view(memory.size() - 44 - size, size);
|
||||
}
|
||||
|
||||
auto document = BML::unserialize(metadata);
|
||||
|
||||
if(auto node = document["archive/encryption"]) {
|
||||
if(node.text() == "xchacha20") {
|
||||
encryption.type = node.text();
|
||||
encryption.nonce = Decode::Base<57, uint192_t>(node["nonce"].text());
|
||||
}
|
||||
}
|
||||
|
||||
if(auto node = document["archive/signature"]) {
|
||||
if(node.text() == "ed25519") {
|
||||
signature.type = node.text();
|
||||
signature.publicKey = Decode::Base<57, uint256_t>(node["publicKey"].text());
|
||||
signature.value = Decode::Base<57, uint512_t>(node["value"].text());
|
||||
}
|
||||
}
|
||||
|
||||
if(auto node = document["archive/compression"]) {
|
||||
compression.type = node.text();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Container::decryptXChaCha20(uint256_t privateKey) -> bool {
|
||||
encryption.privateKey = privateKey;
|
||||
Cipher::XChaCha20 xchacha20{encryption.privateKey, encryption.nonce};
|
||||
auto size = memory.readl<uint64_t>(memory.size() - 44, 8);
|
||||
memory = xchacha20.decrypt(memory.view(0, memory.size() - 44 - size));
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Container::verifyEd25519(uint256_t publicKey) -> bool {
|
||||
EllipticCurve::Ed25519 ed25519;
|
||||
auto size = memory.readl<uint64_t>(memory.size() - 44, 8);
|
||||
return ed25519.verify(memory.view(0, memory.size() - 44 - size), signature.value, publicKey);
|
||||
}
|
||||
|
||||
auto Container::decompressLZSA() -> bool {
|
||||
memory = Decode::LZSA(memory);
|
||||
return (bool)memory;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto Container::append(string name, string location) -> shared_pointer<Node> {
|
||||
for(auto& node : nodes) if(node->name == name) return {};
|
||||
if(auto node = Node::create(name, location)) return nodes.append(node), node;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Container::appendPath(string name) -> shared_pointer<Node> {
|
||||
for(auto& node : nodes) if(node->name == name) return {};
|
||||
if(auto node = Node::createPath(name)) return nodes.append(node), node;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Container::appendFile(string name, array_view<uint8_t> memory) -> shared_pointer<Node> {
|
||||
for(auto& node : nodes) if(node->name == name) return {};
|
||||
if(auto node = Node::createFile(name, memory)) return nodes.append(node), node;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Container::remove(string name) -> bool {
|
||||
if(auto offset = nodes.find([&](auto& node) { return node->name == name; })) return nodes.remove(*offset), true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Container::find(string name) -> shared_pointer<Node> {
|
||||
if(auto offset = nodes.find([&](auto& node) { return node->name == name; })) return nodes[*offset];
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Container::sort() -> void {
|
||||
nodes.sort([&](auto& lhs, auto& rhs) { return string::icompare(lhs->name, rhs->name) < 0; });
|
||||
}
|
||||
|
||||
}}}
|
86
nall/beat/archive/create.hpp
Normal file
86
nall/beat/archive/create.hpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/beat/archive/node.hpp>
|
||||
#include <nall/beat/archive/container.hpp>
|
||||
|
||||
namespace nall { namespace Beat { namespace Archive {
|
||||
|
||||
auto create(Container& container, string name) -> vector<uint8_t> {
|
||||
auto& metadata = container.metadata;
|
||||
metadata = {};
|
||||
metadata.append("archive: ", Location::file(name), "\n");
|
||||
|
||||
vector<uint8_t> memory;
|
||||
|
||||
container.sort();
|
||||
for(auto& node : container.nodes) {
|
||||
if(node->isFile()) {
|
||||
node->offset = memory.size();
|
||||
memory.append(node->memory);
|
||||
}
|
||||
metadata.append(node->metadata());
|
||||
}
|
||||
|
||||
metadata.append(" size: ", memory.size(), "\n");
|
||||
|
||||
if(container.compression.type == "lzsa") {
|
||||
memory = Encode::LZSA(memory);
|
||||
metadata.append(" compression: lzsa\n");
|
||||
metadata.append(" size: ", memory.size(), "\n");
|
||||
}
|
||||
|
||||
if(container.signature.type == "ed25519") {
|
||||
EllipticCurve::Ed25519 ed25519;
|
||||
container.signature.publicKey = ed25519.publicKey(container.signature.privateKey);
|
||||
container.signature.value = ed25519.sign(memory, container.signature.privateKey);
|
||||
|
||||
metadata.append(" signature: ed25519\n");
|
||||
metadata.append(" publicKey: ", Encode::Base<57>(container.signature.publicKey), "\n");
|
||||
metadata.append(" value: ", Encode::Base<57>(container.signature.value), "\n");
|
||||
}
|
||||
|
||||
for(auto& byte : metadata) memory.append(byte);
|
||||
memory.appendl((uint64_t)metadata.size(), 8);
|
||||
|
||||
auto sha256 = Hash::SHA256(memory).value();
|
||||
memory.appendl((uint256_t)sha256, 32);
|
||||
|
||||
memory.append('B');
|
||||
memory.append('P');
|
||||
memory.append('A');
|
||||
memory.append('1');
|
||||
|
||||
if(container.encryption.type == "xchacha20") {
|
||||
Cipher::XChaCha20 xchacha20{container.encryption.privateKey, container.encryption.nonce};
|
||||
memory = xchacha20.encrypt(memory);
|
||||
|
||||
metadata = {};
|
||||
metadata.append("archive\n");
|
||||
metadata.append(" encryption: xchacha20\n");
|
||||
metadata.append(" nonce: ", Encode::Base<57>(container.encryption.nonce), "\n");
|
||||
|
||||
if(container.signature.type == "ed25519") {
|
||||
EllipticCurve::Ed25519 ed25519;
|
||||
container.signature.value = ed25519.sign(memory, container.signature.privateKey);
|
||||
|
||||
metadata.append(" signature: ed25519\n");
|
||||
//metadata.append(" publicKey: ", Encode::Base<57>(container.signature.publicKey), "\n");
|
||||
metadata.append(" value: ", Encode::Base<57>(container.signature.value), "\n");
|
||||
}
|
||||
|
||||
for(auto& byte : metadata) memory.append(byte ^ memory.size());
|
||||
memory.appendl((uint64_t)metadata.size() | 1ull << 63, 8);
|
||||
|
||||
auto sha256 = Hash::SHA256(memory).value();
|
||||
memory.appendl((uint256_t)sha256, 32);
|
||||
|
||||
memory.append('B');
|
||||
memory.append('P');
|
||||
memory.append('A');
|
||||
memory.append('1');
|
||||
}
|
||||
|
||||
return memory;
|
||||
}
|
||||
|
||||
}}}
|
27
nall/beat/archive/extract.hpp
Normal file
27
nall/beat/archive/extract.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/beat/archive/node.hpp>
|
||||
#include <nall/beat/archive/container.hpp>
|
||||
|
||||
namespace nall { namespace Beat { namespace Archive {
|
||||
|
||||
auto extract(Container& container) -> bool {
|
||||
function<void (Markup::Node)> extract = [&](auto metadata) {
|
||||
if(metadata.name() != "path" && metadata.name() != "file") return;
|
||||
shared_pointer<Node> node = new Node;
|
||||
if(node->unserialize(container.memory, metadata)) {
|
||||
container.nodes.append(node);
|
||||
}
|
||||
if(metadata.name() != "path") return;
|
||||
for(auto node : metadata) extract(node);
|
||||
};
|
||||
|
||||
container.nodes.reset();
|
||||
auto document = BML::unserialize(container.metadata);
|
||||
for(auto node : document["archive"]) extract(node);
|
||||
container.sort();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}}}
|
332
nall/beat/archive/node.hpp
Normal file
332
nall/beat/archive/node.hpp
Normal file
@@ -0,0 +1,332 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/arithmetic.hpp>
|
||||
#include <nall/array-view.hpp>
|
||||
#include <nall/random.hpp>
|
||||
#include <nall/cipher/chacha20.hpp>
|
||||
#include <nall/elliptic-curve/ed25519.hpp>
|
||||
#include <nall/decode/base.hpp>
|
||||
#include <nall/encode/base.hpp>
|
||||
#include <nall/decode/lzsa.hpp>
|
||||
#include <nall/encode/lzsa.hpp>
|
||||
|
||||
namespace nall { namespace Beat { namespace Archive {
|
||||
|
||||
struct Node {
|
||||
static auto create(string name, string location) -> shared_pointer<Node>;
|
||||
static auto createPath(string name) -> shared_pointer<Node>;
|
||||
static auto createFile(string name, array_view<uint8_t> memory) -> shared_pointer<Node>;
|
||||
|
||||
explicit operator bool() const { return (bool)name; }
|
||||
auto isPath() const -> bool { return name.endsWith("/"); }
|
||||
auto isFile() const -> bool { return !name.endsWith("/"); }
|
||||
auto isCompressed() const -> bool { return (bool)compression.type; }
|
||||
|
||||
auto metadata(bool indented = true) const -> string;
|
||||
auto compressLZSA() -> bool;
|
||||
|
||||
auto unserialize(array_view<uint8_t> container, Markup::Node metadata) -> bool;
|
||||
auto decompress() -> bool;
|
||||
|
||||
auto getTimestamp(string) const -> uint64_t;
|
||||
auto getPermissions() const -> uint;
|
||||
auto getOwner() const -> string;
|
||||
auto getGroup() const -> string;
|
||||
|
||||
//files and paths
|
||||
string name;
|
||||
|
||||
bool timestamps = false;
|
||||
struct Timestamp {
|
||||
string created;
|
||||
string modified;
|
||||
string accessed;
|
||||
} timestamp;
|
||||
|
||||
bool permissions = false;
|
||||
struct Permission {
|
||||
struct Owner {
|
||||
string name;
|
||||
bool readable = false;
|
||||
bool writable = false;
|
||||
bool executable = false;
|
||||
} owner;
|
||||
struct Group {
|
||||
string name;
|
||||
bool readable = false;
|
||||
bool writable = false;
|
||||
bool executable = false;
|
||||
} group;
|
||||
struct Other {
|
||||
bool readable = false;
|
||||
bool writable = false;
|
||||
bool executable = false;
|
||||
} other;
|
||||
} permission;
|
||||
|
||||
//files only
|
||||
vector<uint8_t> memory;
|
||||
uint64_t offset = 0;
|
||||
|
||||
struct Compression {
|
||||
string type;
|
||||
uint size = 0; //decompressed size; memory.size() == compressed size
|
||||
} compression;
|
||||
};
|
||||
|
||||
auto Node::create(string name, string location) -> shared_pointer<Node> {
|
||||
if(!inode::exists(location)) return {};
|
||||
shared_pointer<Node> node = new Node;
|
||||
|
||||
node->name = name;
|
||||
|
||||
node->timestamps = true;
|
||||
node->timestamp.created = chrono::utc::datetime(inode::timestamp(location, inode::time::create));
|
||||
node->timestamp.modified = chrono::utc::datetime(inode::timestamp(location, inode::time::modify));
|
||||
node->timestamp.accessed = chrono::utc::datetime(inode::timestamp(location, inode::time::access));
|
||||
|
||||
uint mode = inode::mode(location);
|
||||
node->permissions = true;
|
||||
node->permission.owner.name = inode::owner(location);
|
||||
node->permission.group.name = inode::group(location);
|
||||
node->permission.owner.readable = mode & 0400;
|
||||
node->permission.owner.writable = mode & 0200;
|
||||
node->permission.owner.executable = mode & 0100;
|
||||
node->permission.group.readable = mode & 0040;
|
||||
node->permission.group.writable = mode & 0020;
|
||||
node->permission.group.executable = mode & 0010;
|
||||
node->permission.other.readable = mode & 0004;
|
||||
node->permission.other.writable = mode & 0002;
|
||||
node->permission.other.executable = mode & 0001;
|
||||
|
||||
if(file::exists(location)) {
|
||||
node->memory = file::read(location);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
auto Node::createPath(string name) -> shared_pointer<Node> {
|
||||
if(!name) return {};
|
||||
shared_pointer<Node> node = new Node;
|
||||
node->name = name;
|
||||
return node;
|
||||
}
|
||||
|
||||
auto Node::createFile(string name, array_view<uint8_t> memory) -> shared_pointer<Node> {
|
||||
if(!name) return {};
|
||||
shared_pointer<Node> node = new Node;
|
||||
node->name = name;
|
||||
node->memory.resize(memory.size());
|
||||
memory::copy(node->memory.data(), memory.data(), memory.size());
|
||||
return node;
|
||||
}
|
||||
|
||||
auto Node::metadata(bool indented) const -> string {
|
||||
string metadata;
|
||||
if(!name) return metadata;
|
||||
|
||||
string indent;
|
||||
if(indented) {
|
||||
indent.append(" ");
|
||||
auto bytes = string{name}.trimRight("/");
|
||||
for(auto& byte : bytes) {
|
||||
if(byte == '/') indent.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
if(isPath()) {
|
||||
metadata.append(indent, "path: ", name, "\n");
|
||||
}
|
||||
|
||||
if(isFile()) {
|
||||
metadata.append(indent, "file: ", name, "\n");
|
||||
}
|
||||
|
||||
if(timestamps) {
|
||||
metadata.append(indent, " timestamp\n");
|
||||
if(timestamp.created != timestamp.modified)
|
||||
metadata.append(indent, " created: ", timestamp.created, "\n");
|
||||
metadata.append(indent, " modified: ", timestamp.modified, "\n");
|
||||
if(timestamp.accessed != timestamp.modified)
|
||||
metadata.append(indent, " accessed: ", timestamp.accessed, "\n");
|
||||
}
|
||||
|
||||
if(permissions) {
|
||||
metadata.append(indent, " permission\n");
|
||||
metadata.append(indent, " owner: ", permission.owner.name, "\n");
|
||||
if(permission.owner.readable)
|
||||
metadata.append(indent, " readable\n");
|
||||
if(permission.owner.writable)
|
||||
metadata.append(indent, " writable\n");
|
||||
if(permission.owner.executable)
|
||||
metadata.append(indent, " executable\n");
|
||||
metadata.append(indent, " group: ", permission.group.name, "\n");
|
||||
if(permission.group.readable)
|
||||
metadata.append(indent, " readable\n");
|
||||
if(permission.group.writable)
|
||||
metadata.append(indent, " writable\n");
|
||||
if(permission.group.executable)
|
||||
metadata.append(indent, " executable\n");
|
||||
metadata.append(indent, " other\n");
|
||||
if(permission.other.readable)
|
||||
metadata.append(indent, " readable\n");
|
||||
if(permission.other.writable)
|
||||
metadata.append(indent, " writable\n");
|
||||
if(permission.other.executable)
|
||||
metadata.append(indent, " executable\n");
|
||||
}
|
||||
|
||||
if(isFile()) {
|
||||
metadata.append(indent, " offset: ", offset, "\n");
|
||||
if(!isCompressed()) {
|
||||
metadata.append(indent, " size: ", memory.size(), "\n");
|
||||
} else {
|
||||
metadata.append(indent, " size: ", compression.size, "\n");
|
||||
metadata.append(indent, " compression: ", compression.type, "\n");
|
||||
metadata.append(indent, " size: ", memory.size(), "\n");
|
||||
}
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
auto Node::unserialize(array_view<uint8_t> container, Markup::Node metadata) -> bool {
|
||||
*this = {};
|
||||
if(!metadata.text()) return false;
|
||||
|
||||
name = metadata.text();
|
||||
|
||||
if(auto node = metadata["timestamp"]) {
|
||||
timestamps = true;
|
||||
if(auto created = node["created" ]) timestamp.created = created.text();
|
||||
if(auto modified = node["modified"]) timestamp.modified = modified.text();
|
||||
if(auto accessed = node["accessed"]) timestamp.accessed = accessed.text();
|
||||
}
|
||||
|
||||
if(auto node = metadata["permission"]) {
|
||||
permissions = true;
|
||||
if(auto owner = node["owner"]) {
|
||||
permission.owner.name = owner.text();
|
||||
permission.owner.readable = (bool)owner["readable"];
|
||||
permission.owner.writable = (bool)owner["writable"];
|
||||
permission.owner.executable = (bool)owner["executable"];
|
||||
}
|
||||
if(auto group = node["group"]) {
|
||||
permission.group.name = group.text();
|
||||
permission.group.readable = (bool)group["readable"];
|
||||
permission.group.writable = (bool)group["writable"];
|
||||
permission.group.executable = (bool)group["executable"];
|
||||
}
|
||||
if(auto other = node["other"]) {
|
||||
permission.other.readable = (bool)other["readable"];
|
||||
permission.other.writable = (bool)other["writable"];
|
||||
permission.other.executable = (bool)other["executable"];
|
||||
}
|
||||
}
|
||||
|
||||
if(isPath()) return true;
|
||||
|
||||
uint offset = metadata["offset"].natural();
|
||||
uint size = metadata["size"].natural();
|
||||
|
||||
if(metadata["compression"]) {
|
||||
size = metadata["compression/size"].natural();
|
||||
compression.type = metadata["compression"].text();
|
||||
}
|
||||
|
||||
if(offset + size >= container.size()) return false;
|
||||
|
||||
memory.reallocate(size);
|
||||
nall::memory::copy(memory.data(), container.view(offset, size), size);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Node::compressLZSA() -> bool {
|
||||
if(!memory) return true; //don't compress empty files
|
||||
if(isCompressed()) return true; //don't recompress files
|
||||
|
||||
auto compressedMemory = Encode::LZSA(memory);
|
||||
if(compressedMemory.size() >= memory.size()) return true; //can't compress smaller than original size
|
||||
|
||||
compression.type = "lzsa";
|
||||
compression.size = memory.size();
|
||||
memory = move(compressedMemory);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Node::decompress() -> bool {
|
||||
if(!isCompressed()) return true;
|
||||
|
||||
if(compression.type == "lzsa") {
|
||||
compression = {};
|
||||
memory = Decode::LZSA(memory);
|
||||
return (bool)memory;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Node::getTimestamp(string type) const -> uint64_t {
|
||||
if(!timestamps) return time(nullptr);
|
||||
|
||||
string value = chrono::utc::datetime();
|
||||
if(type == "created" ) value = timestamp.created;
|
||||
if(type == "modified") value = timestamp.modified;
|
||||
if(type == "accessed") value = timestamp.accessed;
|
||||
|
||||
#if !defined(PLATFORM_WINDOWS)
|
||||
struct tm timeInfo{};
|
||||
if(strptime(value, "%Y-%m-%d %H:%M:%S", &timeInfo) != nullptr) {
|
||||
//todo: not thread safe ...
|
||||
auto tz = getenv("TZ");
|
||||
setenv("TZ", "", 1);
|
||||
timeInfo.tm_isdst = -1;
|
||||
auto result = mktime(&timeInfo);
|
||||
if(tz) setenv("TZ", tz, 1);
|
||||
else unsetenv("TZ");
|
||||
if(result != -1) return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
return time(nullptr);
|
||||
}
|
||||
|
||||
auto Node::getPermissions() const -> uint {
|
||||
if(!permissions) return 0755;
|
||||
uint mode = 0;
|
||||
if(permission.owner.readable ) mode |= 0400;
|
||||
if(permission.owner.writable ) mode |= 0200;
|
||||
if(permission.owner.executable) mode |= 0100;
|
||||
if(permission.group.readable ) mode |= 0040;
|
||||
if(permission.group.writable ) mode |= 0020;
|
||||
if(permission.group.executable) mode |= 0010;
|
||||
if(permission.other.readable ) mode |= 0004;
|
||||
if(permission.other.writable ) mode |= 0002;
|
||||
if(permission.other.executable) mode |= 0001;
|
||||
return mode;
|
||||
}
|
||||
|
||||
auto Node::getOwner() const -> string {
|
||||
if(!permissions || !permission.owner.name) {
|
||||
#if !defined(PLATFORM_WINDOWS)
|
||||
struct passwd* pwd = getpwuid(getuid());
|
||||
assert(pwd);
|
||||
return pwd->pw_name;
|
||||
#endif
|
||||
}
|
||||
return permission.owner.name;
|
||||
}
|
||||
|
||||
auto Node::getGroup() const -> string {
|
||||
if(!permissions || !permission.group.name) {
|
||||
#if !defined(PLATFORM_WINDOWS)
|
||||
struct group* grp = getgrgid(getgid());
|
||||
assert(grp);
|
||||
return grp->gr_name;
|
||||
#endif
|
||||
}
|
||||
return permission.group.name;
|
||||
}
|
||||
|
||||
}}}
|
Reference in New Issue
Block a user