bsnes/nall/beat/archive/node.hpp

333 lines
9.9 KiB
C++

#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;
}
}}}