mirror of https://github.com/bsnes-emu/bsnes.git
132 lines
3.9 KiB
C++
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});
|
|
}
|
|
}
|
|
|
|
}}
|