bsnes/nall/beat/archive.hpp

132 lines
3.9 KiB
C++

#pragma once
#include <nall/beat/file.hpp>
namespace nall { namespace Beat {
struct Archive {
static auto create(const string& beatname, const string& pathname, const string& metadata = "") -> bool;
static auto unpack(const string& beatname, const string& pathname) -> bool;
static auto extract(const string& beatname, const string& pathname) -> vector<uint8_t>;
private:
static auto scan(string_vector& result, const string& basename, const string& pathname) -> void;
};
auto Archive::create(const string& beatname, const string& pathname, const string& metadata) -> bool {
if(!beatname.endsWith(".bpa") || !pathname.endsWith("/")) return false; //protect against reversed arguments
File beat{beatname, file::mode::write};
if(!beat) return false; //file not writable?
beat.writes("BPA1");
beat.writevu(metadata.size());
beat.writes(metadata);
string_vector contents;
scan(contents, pathname, pathname);
for(auto& name : contents) {
string location{pathname, name};
bool directory = name.endsWith("/");
bool writable = inode::writable(location);
bool executable = inode::executable(location);
uint info = directory << 0 | writable << 1 | executable << 2 | (name.trimRight("/").size() - 1) << 3;
beat.writevu(info);
beat.writes(name);
if(directory) continue;
File input{location, file::mode::read};
if(input) {
auto size = input.size();
beat.writevu(size);
while(size--) beat.write(input.read());
beat.writel(input.checksum.digest().hex(), 4);
} else {
beat.writevu(0);
beat.writel(0x00000000, 4);
}
}
beat.writel(beat.checksum.digest().hex(), 4);
return true;
}
auto Archive::unpack(const string& beatname, const string& pathname) -> bool {
if(!beatname.endsWith(".bpa") || !pathname.endsWith("/")) return false; //protect against reversed arguments
File beat{beatname, file::mode::read};
if(!beat) return false; //file not readable?
if(beat.reads(4) != "BPA1") return false;
auto size = beat.readvu();
while(size--) beat.read();
directory::create(pathname);
while(beat.offset() < beat.size() - 4) {
auto info = beat.readvu();
auto name = beat.reads((info >> 3) + 1);
if(name.find("\\") || name.find("../")) return false; //block path exploits
string location{pathname, name};
bool directory = info & 1;
bool writable = info & 2;
bool executable = info & 4;
if(directory) {
if(!nall::directory::create(location)) return false;
} else {
File output{location, file::mode::write};
if(!output) return false;
auto size = beat.readvu();
while(size--) output.write(beat.read());
if(beat.readl(4) != output.checksum.digest().hex()) return false;
}
}
uint32_t checksum = beat.checksum.digest().hex();
return beat.readl(4) == checksum;
}
auto Archive::extract(const string& beatname, const string& filename) -> vector<uint8_t> {
File beat{beatname, file::mode::read};
if(!beat) return {}; //file not readable?
if(beat.reads(4) != "BPA1") return {};
auto size = beat.readvu();
beat.seek(beat.offset() + size);
while(beat.offset() < beat.size() - 4) {
auto info = beat.readvu();
auto name = beat.reads((info >> 3) + 1);
if(info & 1) continue; //ignore directories
auto size = beat.readvu();
if(name != filename) {
beat.seek(beat.offset() + size + 4);
continue;
}
vector<uint8_t> result;
result.resize(size);
beat.checksum.reset();
for(auto n : range(size)) result[n] = beat.read();
uint32_t checksum = beat.checksum.digest().hex();
if(beat.readl(4) != checksum) return {};
return result;
}
return {};
}
auto Archive::scan(string_vector& result, const string& basename, const string& pathname) -> void {
for(auto& name : directory::contents(pathname)) {
result.append(string{pathname, name}.trimLeft(basename, 1L));
if(name.endsWith("/")) scan(result, basename, {pathname, name});
}
}
}}