First commit
This commit is contained in:
commit
ec42524237
159 changed files with 17316 additions and 0 deletions
162
GNUmakefile
Normal file
162
GNUmakefile
Normal file
|
@ -0,0 +1,162 @@
|
|||
# disable built-in rules and variables
|
||||
MAKEFLAGS := Rr
|
||||
.SUFFIXES:
|
||||
|
||||
[A-Z] = A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
|
||||
[a-z] = a b c d e f g h i j k l m n o p q r s t u v w x y z
|
||||
[0-9] = 0 1 2 3 4 5 6 7 8 9
|
||||
[markup] = ` ~ ! @ \# $$ % ^ & * ( ) - _ = + [ { ] } \ | ; : ' " , < . > / ?
|
||||
[all] = $([A-Z]) $([a-z]) $([0-9]) $([markup])
|
||||
[space] :=
|
||||
[space] +=
|
||||
|
||||
# platform detection
|
||||
ifeq ($(platform),)
|
||||
uname := $(shell uname -s)
|
||||
ifeq ($(uname),)
|
||||
platform := windows
|
||||
delete = del $(subst /,\,$1)
|
||||
else ifneq ($(findstring Windows,$(uname)),)
|
||||
platform := windows
|
||||
delete = del $(subst /,\,$1)
|
||||
else ifneq ($(findstring _NT,$(uname)),)
|
||||
platform := windows
|
||||
delete = del $(subst /,\,$1)
|
||||
else ifneq ($(findstring Darwin,$(uname)),)
|
||||
platform := macosx
|
||||
delete = rm -f $1
|
||||
else ifneq ($(findstring Linux,$(uname)),)
|
||||
platform := linux
|
||||
delete = rm -f $1
|
||||
else ifneq ($(findstring BSD,$(uname)),)
|
||||
platform := bsd
|
||||
delete = rm -f $1
|
||||
else
|
||||
$(error unknown platform, please specify manually.)
|
||||
endif
|
||||
endif
|
||||
|
||||
cflags := -x c -std=c99
|
||||
objcflags := -x objective-c -std=c99
|
||||
cppflags := -x c++ -std=c++14
|
||||
objcppflags := -x objective-c++ -std=c++14
|
||||
flags :=
|
||||
link :=
|
||||
|
||||
# compiler detection
|
||||
ifeq ($(compiler),)
|
||||
ifeq ($(platform),windows)
|
||||
compiler := g++
|
||||
cppflags := -x c++ -std=gnu++14
|
||||
else ifeq ($(platform),macosx)
|
||||
compiler := clang++
|
||||
else ifeq ($(platform),linux)
|
||||
compiler := g++-4.9
|
||||
else ifeq ($(platform),bsd)
|
||||
compiler := g++49
|
||||
else
|
||||
compiler := g++
|
||||
endif
|
||||
endif
|
||||
|
||||
# clang settings
|
||||
ifeq ($(findstring clang++,$(compiler)),clang++)
|
||||
flags += -fwrapv
|
||||
# gcc settings
|
||||
else ifeq ($(findstring g++,$(compiler)),g++)
|
||||
flags += -fwrapv
|
||||
endif
|
||||
|
||||
# windows settings
|
||||
ifeq ($(platform),windows)
|
||||
link += -lws2_32 -lole32
|
||||
endif
|
||||
|
||||
# macosx settings
|
||||
ifeq ($(platform),macosx)
|
||||
flags += -stdlib=libc++
|
||||
link += -lc++ -lobjc
|
||||
endif
|
||||
|
||||
# linux settings
|
||||
ifeq ($(platform),linux)
|
||||
link += -ldl
|
||||
endif
|
||||
|
||||
# bsd settings
|
||||
ifeq ($(platform),bsd)
|
||||
flags += -I/usr/local/include
|
||||
link += -Wl,-rpath=/usr/local/lib
|
||||
link += -Wl,-rpath=/usr/local/lib/gcc49
|
||||
endif
|
||||
|
||||
# threading support
|
||||
ifeq ($(threaded),true)
|
||||
ifneq ($(filter $(platform),linux bsd),)
|
||||
flags += -pthread
|
||||
link += -pthread -lrt
|
||||
endif
|
||||
endif
|
||||
|
||||
# paths
|
||||
prefix := $(HOME)/.local
|
||||
|
||||
# function rwildcard(directory, pattern)
|
||||
rwildcard = \
|
||||
$(strip \
|
||||
$(filter $(if $2,$2,%), \
|
||||
$(foreach f, \
|
||||
$(wildcard $1*), \
|
||||
$(eval t = $(call rwildcard,$f/)) \
|
||||
$(if $t,$t,$f) \
|
||||
) \
|
||||
) \
|
||||
)
|
||||
|
||||
# function unique(source)
|
||||
unique = \
|
||||
$(eval __temp :=) \
|
||||
$(strip \
|
||||
$(foreach s,$1,$(if $(filter $s,$(__temp)),,$(eval __temp += $s))) \
|
||||
$(__temp) \
|
||||
)
|
||||
|
||||
# function strtr(source, from, to)
|
||||
strtr = \
|
||||
$(eval __temp := $1) \
|
||||
$(strip \
|
||||
$(foreach c, \
|
||||
$(join $(addsuffix :,$2),$3), \
|
||||
$(eval __temp := \
|
||||
$(subst $(word 1,$(subst :, ,$c)),$(word 2,$(subst :, ,$c)),$(__temp)) \
|
||||
) \
|
||||
) \
|
||||
$(__temp) \
|
||||
)
|
||||
|
||||
# function strupper(source)
|
||||
strupper = $(call strtr,$1,$([a-z]),$([A-Z]))
|
||||
|
||||
# function strlower(source)
|
||||
strlower = $(call strtr,$1,$([A-Z]),$([a-z]))
|
||||
|
||||
# function strlen(source)
|
||||
strlen = \
|
||||
$(eval __temp := $(subst $([space]),_,$1)) \
|
||||
$(words \
|
||||
$(strip \
|
||||
$(foreach c, \
|
||||
$([all]), \
|
||||
$(eval __temp := \
|
||||
$(subst $c,$c ,$(__temp)) \
|
||||
) \
|
||||
) \
|
||||
$(__temp) \
|
||||
) \
|
||||
)
|
||||
|
||||
# function streq(source)
|
||||
streq = $(if $(filter-out xx,x$(subst $1,,$2)$(subst $2,,$1)x),,1)
|
||||
|
||||
# function strne(source)
|
||||
strne = $(if $(filter-out xx,x$(subst $1,,$2)$(subst $2,,$1)x),1,)
|
26
algorithm.hpp
Normal file
26
algorithm.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/traits.hpp>
|
||||
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
namespace nall { namespace {
|
||||
|
||||
template<typename T, typename U> auto min(const T& t, const U& u) -> T {
|
||||
return t < u ? t : u;
|
||||
}
|
||||
|
||||
template<typename T, typename U, typename... P> auto min(const T& t, const U& u, P&&... p) -> T {
|
||||
return t < u ? min(t, forward<P>(p)...) : min(u, forward<P>(p)...);
|
||||
}
|
||||
|
||||
template<typename T, typename U> auto max(const T& t, const U& u) -> T {
|
||||
return t > u ? t : u;
|
||||
}
|
||||
|
||||
template<typename T, typename U, typename... P> auto max(const T& t, const U& u, P&&... p) -> T {
|
||||
return t > u ? max(t, forward<P>(p)...) : max(u, forward<P>(p)...);
|
||||
}
|
||||
|
||||
}}
|
84
any.hpp
Normal file
84
any.hpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
#pragma once
|
||||
|
||||
#include <typeinfo>
|
||||
#include <nall/traits.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct any {
|
||||
any() = default;
|
||||
any(const any& source) { operator=(source); }
|
||||
any(any&& source) { operator=(move(source)); }
|
||||
template<typename T> any(const T& value) { operator=(value); }
|
||||
~any() { reset(); }
|
||||
|
||||
explicit operator bool() const { return container; }
|
||||
auto empty() const -> bool { return !container; }
|
||||
auto reset() -> void { if(container) { delete container; container = nullptr; } }
|
||||
|
||||
auto type() const -> const std::type_info& {
|
||||
return container ? container->type() : typeid(void);
|
||||
}
|
||||
|
||||
template<typename T> auto is() const -> bool {
|
||||
return type() == typeid(typename remove_reference<T>::type);
|
||||
}
|
||||
|
||||
template<typename T> auto get() -> T& {
|
||||
if(!is<T>()) throw;
|
||||
return static_cast<holder<typename remove_reference<T>::type>*>(container)->value;
|
||||
}
|
||||
|
||||
template<typename T> auto get() const -> const T& {
|
||||
if(!is<T>()) throw;
|
||||
return static_cast<holder<typename remove_reference<T>::type>*>(container)->value;
|
||||
}
|
||||
|
||||
template<typename T> auto get(const T& fallback) const -> const T& {
|
||||
if(!is<T>()) return fallback;
|
||||
return static_cast<holder<typename remove_reference<T>::type>*>(container)->value;
|
||||
}
|
||||
|
||||
template<typename T> auto operator=(const T& value) -> any& {
|
||||
using auto_t = type_if<is_array<T>, typename remove_extent<typename add_const<T>::type>::type*, T>;
|
||||
|
||||
if(type() == typeid(auto_t)) {
|
||||
static_cast<holder<auto_t>*>(container)->value = (auto_t)value;
|
||||
} else {
|
||||
if(container) delete container;
|
||||
container = new holder<auto_t>((auto_t)value);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator=(const any& source) -> any& {
|
||||
if(container) { delete container; container = nullptr; }
|
||||
if(source.container) container = source.container->copy();
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator=(any&& source) -> any& {
|
||||
if(container) delete container;
|
||||
container = source.container;
|
||||
source.container = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
struct placeholder {
|
||||
virtual ~placeholder() = default;
|
||||
virtual auto type() const -> const std::type_info& = 0;
|
||||
virtual auto copy() const -> placeholder* = 0;
|
||||
};
|
||||
placeholder* container = nullptr;
|
||||
|
||||
template<typename T> struct holder : placeholder {
|
||||
holder(const T& value) : value(value) {}
|
||||
auto type() const -> const std::type_info& { return typeid(T); }
|
||||
auto copy() const -> placeholder* { return new holder(value); }
|
||||
T value;
|
||||
};
|
||||
};
|
||||
|
||||
}
|
87
atoi.hpp
Normal file
87
atoi.hpp
Normal file
|
@ -0,0 +1,87 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/stdint.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
constexpr inline auto binary_(const char* s, uintmax sum = 0) -> uintmax {
|
||||
return (
|
||||
*s == '0' || *s == '1' ? binary_(s + 1, (sum << 1) | *s - '0') :
|
||||
*s == '\'' ? binary_(s + 1, sum) :
|
||||
sum
|
||||
);
|
||||
}
|
||||
|
||||
constexpr inline auto octal_(const char* s, uintmax sum = 0) -> uintmax {
|
||||
return (
|
||||
*s >= '0' && *s <= '7' ? octal_(s + 1, (sum << 3) | *s - '0') :
|
||||
*s == '\'' ? octal_(s + 1, sum) :
|
||||
sum
|
||||
);
|
||||
}
|
||||
|
||||
constexpr inline auto decimal_(const char* s, uintmax sum = 0) -> uintmax {
|
||||
return (
|
||||
*s >= '0' && *s <= '9' ? decimal_(s + 1, (sum * 10) + *s - '0') :
|
||||
*s == '\'' ? decimal_(s + 1, sum) :
|
||||
sum
|
||||
);
|
||||
}
|
||||
|
||||
constexpr inline auto hex_(const char* s, uintmax sum = 0) -> uintmax {
|
||||
return (
|
||||
*s >= 'A' && *s <= 'F' ? hex_(s + 1, (sum << 4) | *s - 'A' + 10) :
|
||||
*s >= 'a' && *s <= 'f' ? hex_(s + 1, (sum << 4) | *s - 'a' + 10) :
|
||||
*s >= '0' && *s <= '9' ? hex_(s + 1, (sum << 4) | *s - '0') :
|
||||
*s == '\'' ? hex_(s + 1, sum) :
|
||||
sum
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
constexpr inline auto binary(const char* s) -> uintmax {
|
||||
return (
|
||||
*s == '0' && (*(s + 1) == 'B' || *(s + 1) == 'b') ? binary_(s + 2) :
|
||||
*s == '%' ? binary_(s + 1) : binary_(s)
|
||||
);
|
||||
}
|
||||
|
||||
constexpr inline auto octal(const char* s) -> uintmax {
|
||||
return (
|
||||
*s == '0' && (*(s + 1) == 'O' || *(s + 1) == 'o') ? octal_(s + 2) :
|
||||
octal_(s)
|
||||
);
|
||||
}
|
||||
|
||||
constexpr inline auto hex(const char* s) -> uintmax {
|
||||
return (
|
||||
*s == '0' && (*(s + 1) == 'X' || *(s + 1) == 'x') ? hex_(s + 2) :
|
||||
*s == '$' ? hex_(s + 1) : hex_(s)
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
constexpr inline auto natural(const char* s) -> uintmax {
|
||||
return (
|
||||
*s == '0' && (*(s + 1) == 'B' || *(s + 1) == 'b') ? binary_(s + 2) :
|
||||
*s == '0' && (*(s + 1) == 'O' || *(s + 1) == 'o') ? octal_(s + 2) :
|
||||
*s == '0' && (*(s + 1) == 'X' || *(s + 1) == 'x') ? hex_(s + 2) :
|
||||
*s == '%' ? binary_(s + 1) : *s == '$' ? hex_(s + 1) : decimal_(s)
|
||||
);
|
||||
}
|
||||
|
||||
constexpr inline auto integer(const char* s) -> intmax {
|
||||
return (
|
||||
*s == '+' ? +natural(s + 1) : *s == '-' ? -natural(s + 1) : natural(s)
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
inline auto real(const char* s) -> double {
|
||||
return atof(s);
|
||||
}
|
||||
|
||||
}
|
131
beat/archive.hpp
Normal file
131
beat/archive.hpp
Normal file
|
@ -0,0 +1,131 @@
|
|||
#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(lstring& 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);
|
||||
|
||||
lstring 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.rtrim("/").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.value(), 4);
|
||||
} else {
|
||||
beat.writevu(0);
|
||||
beat.writel(0x00000000, 4);
|
||||
}
|
||||
}
|
||||
|
||||
beat.writel(beat.checksum.value(), 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.value()) return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t checksum = beat.checksum.value();
|
||||
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.value();
|
||||
if(beat.readl(4) != checksum) return {};
|
||||
return result;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Archive::scan(lstring& result, const string& basename, const string& pathname) -> void {
|
||||
for(auto& name : directory::contents(pathname)) {
|
||||
result.append(string{pathname, name}.ltrim(basename, 1L));
|
||||
if(name.endsWith("/")) scan(result, basename, {pathname, name});
|
||||
}
|
||||
}
|
||||
|
||||
}}
|
210
beat/delta.hpp
Normal file
210
beat/delta.hpp
Normal file
|
@ -0,0 +1,210 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/file.hpp>
|
||||
#include <nall/filemap.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/string.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct bpsdelta {
|
||||
inline auto source(const uint8_t* data, uint size) -> void;
|
||||
inline auto target(const uint8_t* data, uint size) -> void;
|
||||
|
||||
inline auto source(const string& filename) -> bool;
|
||||
inline auto target(const string& filename) -> bool;
|
||||
inline auto create(const string& filename, const string& metadata = "") -> bool;
|
||||
|
||||
protected:
|
||||
enum : uint { SourceRead, TargetRead, SourceCopy, TargetCopy };
|
||||
enum : uint { Granularity = 1 };
|
||||
|
||||
struct Node {
|
||||
Node() = default;
|
||||
~Node() { if(next) delete next; }
|
||||
uint offset = 0;
|
||||
Node* next = nullptr;
|
||||
};
|
||||
|
||||
filemap sourceFile;
|
||||
const uint8_t* sourceData;
|
||||
uint sourceSize;
|
||||
|
||||
filemap targetFile;
|
||||
const uint8_t* targetData;
|
||||
uint targetSize;
|
||||
};
|
||||
|
||||
auto bpsdelta::source(const uint8_t* data, uint size) -> void {
|
||||
sourceData = data;
|
||||
sourceSize = size;
|
||||
}
|
||||
|
||||
auto bpsdelta::target(const uint8_t* data, uint size) -> void {
|
||||
targetData = data;
|
||||
targetSize = size;
|
||||
}
|
||||
|
||||
auto bpsdelta::source(const string& filename) -> bool {
|
||||
if(sourceFile.open(filename, filemap::mode::read) == false) return false;
|
||||
source(sourceFile.data(), sourceFile.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
auto bpsdelta::target(const string& filename) -> bool {
|
||||
if(targetFile.open(filename, filemap::mode::read) == false) return false;
|
||||
target(targetFile.data(), targetFile.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
auto bpsdelta::create(const string& filename, const string& metadata) -> bool {
|
||||
file modifyFile;
|
||||
if(modifyFile.open(filename, file::mode::write) == false) return false;
|
||||
|
||||
Hash::CRC32 sourceChecksum, modifyChecksum;
|
||||
uint sourceRelativeOffset = 0, targetRelativeOffset = 0, outputOffset = 0;
|
||||
|
||||
auto write = [&](uint8_t data) {
|
||||
modifyFile.write(data);
|
||||
modifyChecksum.data(data);
|
||||
};
|
||||
|
||||
auto encode = [&](uint64_t data) {
|
||||
while(true) {
|
||||
uint64_t x = data & 0x7f;
|
||||
data >>= 7;
|
||||
if(data == 0) {
|
||||
write(0x80 | x);
|
||||
break;
|
||||
}
|
||||
write(x);
|
||||
data--;
|
||||
}
|
||||
};
|
||||
|
||||
write('B');
|
||||
write('P');
|
||||
write('S');
|
||||
write('1');
|
||||
|
||||
encode(sourceSize);
|
||||
encode(targetSize);
|
||||
|
||||
uint markupSize = metadata.length();
|
||||
encode(markupSize);
|
||||
for(uint n = 0; n < markupSize; n++) write(metadata[n]);
|
||||
|
||||
Node* sourceTree[65536];
|
||||
Node* targetTree[65536];
|
||||
for(uint n = 0; n < 65536; n++) sourceTree[n] = nullptr, targetTree[n] = nullptr;
|
||||
|
||||
//source tree creation
|
||||
for(uint offset = 0; offset < sourceSize; offset++) {
|
||||
uint16_t symbol = sourceData[offset + 0];
|
||||
sourceChecksum.data(symbol);
|
||||
if(offset < sourceSize - 1) symbol |= sourceData[offset + 1] << 8;
|
||||
Node *node = new Node;
|
||||
node->offset = offset;
|
||||
node->next = sourceTree[symbol];
|
||||
sourceTree[symbol] = node;
|
||||
}
|
||||
|
||||
uint targetReadLength = 0;
|
||||
|
||||
auto targetReadFlush = [&]() {
|
||||
if(targetReadLength) {
|
||||
encode(TargetRead | ((targetReadLength - 1) << 2));
|
||||
uint offset = outputOffset - targetReadLength;
|
||||
while(targetReadLength) write(targetData[offset++]), targetReadLength--;
|
||||
}
|
||||
};
|
||||
|
||||
while(outputOffset < targetSize) {
|
||||
uint maxLength = 0, maxOffset = 0, mode = TargetRead;
|
||||
|
||||
uint16_t symbol = targetData[outputOffset + 0];
|
||||
if(outputOffset < targetSize - 1) symbol |= targetData[outputOffset + 1] << 8;
|
||||
|
||||
{ //source read
|
||||
uint length = 0, offset = outputOffset;
|
||||
while(offset < sourceSize && offset < targetSize && sourceData[offset] == targetData[offset]) {
|
||||
length++;
|
||||
offset++;
|
||||
}
|
||||
if(length > maxLength) maxLength = length, mode = SourceRead;
|
||||
}
|
||||
|
||||
{ //source copy
|
||||
Node* node = sourceTree[symbol];
|
||||
while(node) {
|
||||
uint length = 0, x = node->offset, y = outputOffset;
|
||||
while(x < sourceSize && y < targetSize && sourceData[x++] == targetData[y++]) length++;
|
||||
if(length > maxLength) maxLength = length, maxOffset = node->offset, mode = SourceCopy;
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
|
||||
{ //target copy
|
||||
Node* node = targetTree[symbol];
|
||||
while(node) {
|
||||
uint length = 0, x = node->offset, y = outputOffset;
|
||||
while(y < targetSize && targetData[x++] == targetData[y++]) length++;
|
||||
if(length > maxLength) maxLength = length, maxOffset = node->offset, mode = TargetCopy;
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
//target tree append
|
||||
node = new Node;
|
||||
node->offset = outputOffset;
|
||||
node->next = targetTree[symbol];
|
||||
targetTree[symbol] = node;
|
||||
}
|
||||
|
||||
{ //target read
|
||||
if(maxLength < 4) {
|
||||
maxLength = min((uint)Granularity, targetSize - outputOffset);
|
||||
mode = TargetRead;
|
||||
}
|
||||
}
|
||||
|
||||
if(mode != TargetRead) targetReadFlush();
|
||||
|
||||
switch(mode) {
|
||||
case SourceRead:
|
||||
encode(SourceRead | ((maxLength - 1) << 2));
|
||||
break;
|
||||
case TargetRead:
|
||||
//delay write to group sequential TargetRead commands into one
|
||||
targetReadLength += maxLength;
|
||||
break;
|
||||
case SourceCopy:
|
||||
case TargetCopy:
|
||||
encode(mode | ((maxLength - 1) << 2));
|
||||
int relativeOffset;
|
||||
if(mode == SourceCopy) {
|
||||
relativeOffset = maxOffset - sourceRelativeOffset;
|
||||
sourceRelativeOffset = maxOffset + maxLength;
|
||||
} else {
|
||||
relativeOffset = maxOffset - targetRelativeOffset;
|
||||
targetRelativeOffset = maxOffset + maxLength;
|
||||
}
|
||||
encode((relativeOffset < 0) | (abs(relativeOffset) << 1));
|
||||
break;
|
||||
}
|
||||
|
||||
outputOffset += maxLength;
|
||||
}
|
||||
|
||||
targetReadFlush();
|
||||
|
||||
for(uint n = 0; n < 32; n += 8) write(sourceChecksum.value() >> n);
|
||||
uint32_t targetChecksum = Hash::CRC32(targetData, targetSize).value();
|
||||
for(uint n = 0; n < 32; n += 8) write(targetChecksum >> n);
|
||||
uint32_t outputChecksum = modifyChecksum.value();
|
||||
for(uint n = 0; n < 32; n += 8) write(outputChecksum >> n);
|
||||
|
||||
modifyFile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
23
beat/file.hpp
Normal file
23
beat/file.hpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall { namespace Beat {
|
||||
|
||||
struct File : file {
|
||||
using file::file;
|
||||
auto read() -> uint8_t override;
|
||||
auto write(uint8_t) -> void override;
|
||||
Hash::CRC32 checksum;
|
||||
};
|
||||
|
||||
auto File::read() -> uint8_t {
|
||||
uint8_t data = file::read();
|
||||
checksum.data(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
auto File::write(uint8_t data) -> void {
|
||||
checksum.data(data);
|
||||
return file::write(data);
|
||||
}
|
||||
|
||||
}}
|
148
beat/linear.hpp
Normal file
148
beat/linear.hpp
Normal file
|
@ -0,0 +1,148 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/file.hpp>
|
||||
#include <nall/filemap.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/string.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct bpslinear {
|
||||
inline auto source(const uint8_t* data, uint size) -> void;
|
||||
inline auto target(const uint8_t* data, uint size) -> void;
|
||||
|
||||
inline auto source(const string& filename) -> bool;
|
||||
inline auto target(const string& filename) -> bool;
|
||||
inline auto create(const string& filename, const string& metadata = "") -> bool;
|
||||
|
||||
protected:
|
||||
enum : uint { SourceRead, TargetRead, SourceCopy, TargetCopy };
|
||||
enum : uint { Granularity = 1 };
|
||||
|
||||
filemap sourceFile;
|
||||
const uint8_t* sourceData;
|
||||
uint sourceSize;
|
||||
|
||||
filemap targetFile;
|
||||
const uint8_t* targetData;
|
||||
uint targetSize;
|
||||
};
|
||||
|
||||
auto bpslinear::source(const uint8_t* data, uint size) -> void {
|
||||
sourceData = data;
|
||||
sourceSize = size;
|
||||
}
|
||||
|
||||
auto bpslinear::target(const uint8_t* data, uint size) -> void {
|
||||
targetData = data;
|
||||
targetSize = size;
|
||||
}
|
||||
|
||||
auto bpslinear::source(const string& filename) -> bool {
|
||||
if(sourceFile.open(filename, filemap::mode::read) == false) return false;
|
||||
source(sourceFile.data(), sourceFile.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
auto bpslinear::target(const string& filename) -> bool {
|
||||
if(targetFile.open(filename, filemap::mode::read) == false) return false;
|
||||
target(targetFile.data(), targetFile.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
auto bpslinear::create(const string& filename, const string& metadata) -> bool {
|
||||
file modifyFile;
|
||||
if(modifyFile.open(filename, file::mode::write) == false) return false;
|
||||
|
||||
Hash::CRC32 modifyChecksum;
|
||||
uint targetRelativeOffset = 0, outputOffset = 0;
|
||||
|
||||
auto write = [&](uint8_t data) {
|
||||
modifyFile.write(data);
|
||||
modifyChecksum.data(data);
|
||||
};
|
||||
|
||||
auto encode = [&](uint64_t data) {
|
||||
while(true) {
|
||||
uint64_t x = data & 0x7f;
|
||||
data >>= 7;
|
||||
if(data == 0) {
|
||||
write(0x80 | x);
|
||||
break;
|
||||
}
|
||||
write(x);
|
||||
data--;
|
||||
}
|
||||
};
|
||||
|
||||
uint targetReadLength = 0;
|
||||
|
||||
auto targetReadFlush = [&]() {
|
||||
if(targetReadLength) {
|
||||
encode(TargetRead | ((targetReadLength - 1) << 2));
|
||||
uint offset = outputOffset - targetReadLength;
|
||||
while(targetReadLength) write(targetData[offset++]), targetReadLength--;
|
||||
}
|
||||
};
|
||||
|
||||
write('B');
|
||||
write('P');
|
||||
write('S');
|
||||
write('1');
|
||||
|
||||
encode(sourceSize);
|
||||
encode(targetSize);
|
||||
|
||||
uint markupSize = metadata.length();
|
||||
encode(markupSize);
|
||||
for(uint n = 0; n < markupSize; n++) write(metadata[n]);
|
||||
|
||||
while(outputOffset < targetSize) {
|
||||
uint sourceLength = 0;
|
||||
for(uint n = 0; outputOffset + n < min(sourceSize, targetSize); n++) {
|
||||
if(sourceData[outputOffset + n] != targetData[outputOffset + n]) break;
|
||||
sourceLength++;
|
||||
}
|
||||
|
||||
uint rleLength = 0;
|
||||
for(uint n = 1; outputOffset + n < targetSize; n++) {
|
||||
if(targetData[outputOffset] != targetData[outputOffset + n]) break;
|
||||
rleLength++;
|
||||
}
|
||||
|
||||
if(rleLength >= 4) {
|
||||
//write byte to repeat
|
||||
targetReadLength++;
|
||||
outputOffset++;
|
||||
targetReadFlush();
|
||||
|
||||
//copy starting from repetition byte
|
||||
encode(TargetCopy | ((rleLength - 1) << 2));
|
||||
uint relativeOffset = (outputOffset - 1) - targetRelativeOffset;
|
||||
encode(relativeOffset << 1);
|
||||
outputOffset += rleLength;
|
||||
targetRelativeOffset = outputOffset - 1;
|
||||
} else if(sourceLength >= 4) {
|
||||
targetReadFlush();
|
||||
encode(SourceRead | ((sourceLength - 1) << 2));
|
||||
outputOffset += sourceLength;
|
||||
} else {
|
||||
targetReadLength += Granularity;
|
||||
outputOffset += Granularity;
|
||||
}
|
||||
}
|
||||
|
||||
targetReadFlush();
|
||||
|
||||
uint32_t sourceChecksum = Hash::CRC32(sourceData, sourceSize).value();
|
||||
for(uint n = 0; n < 32; n += 8) write(sourceChecksum >> n);
|
||||
uint32_t targetChecksum = Hash::CRC32(targetData, targetSize).value();
|
||||
for(uint n = 0; n < 32; n += 8) write(targetChecksum >> n);
|
||||
uint32_t outputChecksum = modifyChecksum.value();
|
||||
for(uint n = 0; n < 32; n += 8) write(outputChecksum >> n);
|
||||
|
||||
modifyFile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
117
beat/metadata.hpp
Normal file
117
beat/metadata.hpp
Normal file
|
@ -0,0 +1,117 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/file.hpp>
|
||||
#include <nall/filemap.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/string.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct bpsmetadata {
|
||||
inline auto load(const string& filename) -> bool;
|
||||
inline auto save(const string& filename, const string& metadata) -> bool;
|
||||
inline auto metadata() const -> string;
|
||||
|
||||
protected:
|
||||
file sourceFile;
|
||||
string metadataString;
|
||||
};
|
||||
|
||||
auto bpsmetadata::load(const string& filename) -> bool {
|
||||
if(sourceFile.open(filename, file::mode::read) == false) return false;
|
||||
|
||||
auto read = [&]() -> uint8_t {
|
||||
return sourceFile.read();
|
||||
};
|
||||
|
||||
auto decode = [&]() -> uint64_t {
|
||||
uint64_t data = 0, shift = 1;
|
||||
while(true) {
|
||||
uint8_t x = read();
|
||||
data += (x & 0x7f) * shift;
|
||||
if(x & 0x80) break;
|
||||
shift <<= 7;
|
||||
data += shift;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
if(read() != 'B') return false;
|
||||
if(read() != 'P') return false;
|
||||
if(read() != 'S') return false;
|
||||
if(read() != '1') return false;
|
||||
decode();
|
||||
decode();
|
||||
uint metadataSize = decode();
|
||||
char data[metadataSize + 1];
|
||||
for(uint n = 0; n < metadataSize; n++) data[n] = read();
|
||||
data[metadataSize] = 0;
|
||||
metadataString = (const char*)data;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto bpsmetadata::save(const string& filename, const string& metadata) -> bool {
|
||||
file targetFile;
|
||||
if(targetFile.open(filename, file::mode::write) == false) return false;
|
||||
if(sourceFile.open() == false) return false;
|
||||
sourceFile.seek(0);
|
||||
|
||||
auto read = [&]() -> uint8_t {
|
||||
return sourceFile.read();
|
||||
};
|
||||
|
||||
auto decode = [&]() -> uint64_t {
|
||||
uint64_t data = 0, shift = 1;
|
||||
while(true) {
|
||||
uint8_t x = read();
|
||||
data += (x & 0x7f) * shift;
|
||||
if(x & 0x80) break;
|
||||
shift <<= 7;
|
||||
data += shift;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
Hash::CRC32 checksum;
|
||||
|
||||
auto write = [&](uint8_t data) {
|
||||
targetFile.write(data);
|
||||
checksum.data(data);
|
||||
};
|
||||
|
||||
auto encode = [&](uint64_t data) {
|
||||
while(true) {
|
||||
uint64_t x = data & 0x7f;
|
||||
data >>= 7;
|
||||
if(data == 0) {
|
||||
write(0x80 | x);
|
||||
break;
|
||||
}
|
||||
write(x);
|
||||
data--;
|
||||
}
|
||||
};
|
||||
|
||||
for(uint n = 0; n < 4; n++) write(read());
|
||||
encode(decode());
|
||||
encode(decode());
|
||||
uint sourceLength = decode();
|
||||
uint targetLength = metadata.length();
|
||||
encode(targetLength);
|
||||
sourceFile.seek(sourceLength, file::index::relative);
|
||||
for(uint n = 0; n < targetLength; n++) write(metadata[n]);
|
||||
uint length = sourceFile.size() - sourceFile.offset() - 4;
|
||||
for(uint n = 0; n < length; n++) write(read());
|
||||
uint32_t outputChecksum = checksum.value();
|
||||
for(uint n = 0; n < 32; n += 8) write(outputChecksum >> n);
|
||||
|
||||
targetFile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto bpsmetadata::metadata() const -> string {
|
||||
return metadataString;
|
||||
}
|
||||
|
||||
}
|
239
beat/multi.hpp
Normal file
239
beat/multi.hpp
Normal file
|
@ -0,0 +1,239 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/beat/patch.hpp>
|
||||
#include <nall/beat/linear.hpp>
|
||||
#include <nall/beat/delta.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct bpsmulti {
|
||||
enum : uint {
|
||||
CreatePath = 0,
|
||||
CreateFile = 1,
|
||||
ModifyFile = 2,
|
||||
MirrorFile = 3,
|
||||
};
|
||||
|
||||
enum : uint {
|
||||
OriginSource = 0,
|
||||
OriginTarget = 1,
|
||||
};
|
||||
|
||||
auto create(const string& patchName, const string& sourcePath, const string& targetPath, bool delta = false, const string& metadata = "") -> bool {
|
||||
if(fp.open()) fp.close();
|
||||
fp.open(patchName, file::mode::write);
|
||||
checksum.reset();
|
||||
|
||||
writeString("BPM1"); //signature
|
||||
writeNumber(metadata.length());
|
||||
writeString(metadata);
|
||||
|
||||
lstring sourceList, targetList;
|
||||
ls(sourceList, sourcePath, sourcePath);
|
||||
ls(targetList, targetPath, targetPath);
|
||||
|
||||
for(auto& targetName : targetList) {
|
||||
if(targetName.endsWith("/")) {
|
||||
targetName.rtrim("/");
|
||||
writeNumber(CreatePath | ((targetName.length() - 1) << 2));
|
||||
writeString(targetName);
|
||||
} else if(auto position = sourceList.find(targetName)) { //if sourceName == targetName
|
||||
file sp, dp;
|
||||
sp.open({sourcePath, targetName}, file::mode::read);
|
||||
dp.open({targetPath, targetName}, file::mode::read);
|
||||
|
||||
bool identical = sp.size() == dp.size();
|
||||
Hash::CRC32 cksum;
|
||||
|
||||
for(uint n = 0; n < sp.size(); n++) {
|
||||
uint8_t byte = sp.read();
|
||||
if(identical && byte != dp.read()) identical = false;
|
||||
cksum.data(byte);
|
||||
}
|
||||
|
||||
if(identical) {
|
||||
writeNumber(MirrorFile | ((targetName.length() - 1) << 2));
|
||||
writeString(targetName);
|
||||
writeNumber(OriginSource);
|
||||
writeChecksum(cksum.value());
|
||||
} else {
|
||||
writeNumber(ModifyFile | ((targetName.length() - 1) << 2));
|
||||
writeString(targetName);
|
||||
writeNumber(OriginSource);
|
||||
|
||||
if(delta == false) {
|
||||
bpslinear patch;
|
||||
patch.source({sourcePath, targetName});
|
||||
patch.target({targetPath, targetName});
|
||||
patch.create({temppath(), "temp.bps"});
|
||||
} else {
|
||||
bpsdelta patch;
|
||||
patch.source({sourcePath, targetName});
|
||||
patch.target({targetPath, targetName});
|
||||
patch.create({temppath(), "temp.bps"});
|
||||
}
|
||||
|
||||
auto buffer = file::read({temppath(), "temp.bps"});
|
||||
writeNumber(buffer.size());
|
||||
for(auto &byte : buffer) write(byte);
|
||||
}
|
||||
} else {
|
||||
writeNumber(CreateFile | ((targetName.length() - 1) << 2));
|
||||
writeString(targetName);
|
||||
auto buffer = file::read({targetPath, targetName});
|
||||
writeNumber(buffer.size());
|
||||
for(auto& byte : buffer) write(byte);
|
||||
writeChecksum(Hash::CRC32(buffer.data(), buffer.size()).value());
|
||||
}
|
||||
}
|
||||
|
||||
//checksum
|
||||
writeChecksum(checksum.value());
|
||||
fp.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto apply(const string& patchName, const string& sourcePath, const string& targetPath) -> bool {
|
||||
directory::remove(targetPath); //start with a clean directory
|
||||
directory::create(targetPath);
|
||||
|
||||
if(fp.open()) fp.close();
|
||||
fp.open(patchName, file::mode::read);
|
||||
checksum.reset();
|
||||
|
||||
if(readString(4) != "BPM1") return false;
|
||||
auto metadataLength = readNumber();
|
||||
while(metadataLength--) read();
|
||||
|
||||
while(fp.offset() < fp.size() - 4) {
|
||||
auto encoding = readNumber();
|
||||
uint action = encoding & 3;
|
||||
uint targetLength = (encoding >> 2) + 1;
|
||||
string targetName = readString(targetLength);
|
||||
|
||||
if(action == CreatePath) {
|
||||
directory::create({targetPath, targetName, "/"});
|
||||
} else if(action == CreateFile) {
|
||||
file fp;
|
||||
fp.open({targetPath, targetName}, file::mode::write);
|
||||
auto fileSize = readNumber();
|
||||
while(fileSize--) fp.write(read());
|
||||
uint32_t cksum = readChecksum();
|
||||
} else if(action == ModifyFile) {
|
||||
auto encoding = readNumber();
|
||||
string originPath = encoding & 1 ? targetPath : sourcePath;
|
||||
string sourceName = (encoding >> 1) == 0 ? targetName : readString(encoding >> 1);
|
||||
auto patchSize = readNumber();
|
||||
vector<uint8_t> buffer;
|
||||
buffer.resize(patchSize);
|
||||
for(uint n = 0; n < patchSize; n++) buffer[n] = read();
|
||||
bpspatch patch;
|
||||
patch.modify(buffer.data(), buffer.size());
|
||||
patch.source({originPath, sourceName});
|
||||
patch.target({targetPath, targetName});
|
||||
if(patch.apply() != bpspatch::result::success) return false;
|
||||
} else if(action == MirrorFile) {
|
||||
auto encoding = readNumber();
|
||||
string originPath = encoding & 1 ? targetPath : sourcePath;
|
||||
string sourceName = (encoding >> 1) == 0 ? targetName : readString(encoding >> 1);
|
||||
file::copy({originPath, sourceName}, {targetPath, targetName});
|
||||
uint32_t cksum = readChecksum();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t cksum = checksum.value();
|
||||
if(read() != (uint8_t)(cksum >> 0)) return false;
|
||||
if(read() != (uint8_t)(cksum >> 8)) return false;
|
||||
if(read() != (uint8_t)(cksum >> 16)) return false;
|
||||
if(read() != (uint8_t)(cksum >> 24)) return false;
|
||||
|
||||
fp.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
file fp;
|
||||
Hash::CRC32 checksum;
|
||||
|
||||
//create() functions
|
||||
auto ls(lstring& list, const string& path, const string& basepath) -> void {
|
||||
lstring paths = directory::folders(path);
|
||||
for(auto& pathname : paths) {
|
||||
list.append(string{path, pathname}.ltrim(basepath, 1L));
|
||||
ls(list, {path, pathname}, basepath);
|
||||
}
|
||||
|
||||
lstring files = directory::files(path);
|
||||
for(auto& filename : files) {
|
||||
list.append(string{path, filename}.ltrim(basepath, 1L));
|
||||
}
|
||||
}
|
||||
|
||||
auto write(uint8_t data) -> void {
|
||||
fp.write(data);
|
||||
checksum.data(data);
|
||||
}
|
||||
|
||||
auto writeNumber(uint64_t data) -> void {
|
||||
while(true) {
|
||||
uint64_t x = data & 0x7f;
|
||||
data >>= 7;
|
||||
if(data == 0) {
|
||||
write(0x80 | x);
|
||||
break;
|
||||
}
|
||||
write(x);
|
||||
data--;
|
||||
}
|
||||
}
|
||||
|
||||
auto writeString(const string& text) -> void {
|
||||
uint length = text.length();
|
||||
for(uint n = 0; n < length; n++) write(text[n]);
|
||||
}
|
||||
|
||||
auto writeChecksum(uint32_t cksum) -> void {
|
||||
write(cksum >> 0);
|
||||
write(cksum >> 8);
|
||||
write(cksum >> 16);
|
||||
write(cksum >> 24);
|
||||
}
|
||||
|
||||
//apply() functions
|
||||
auto read() -> uint8_t {
|
||||
uint8_t data = fp.read();
|
||||
checksum.data(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
auto readNumber() -> uint64_t {
|
||||
uint64_t data = 0, shift = 1;
|
||||
while(true) {
|
||||
uint8_t x = read();
|
||||
data += (x & 0x7f) * shift;
|
||||
if(x & 0x80) break;
|
||||
shift <<= 7;
|
||||
data += shift;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
auto readString(uint length) -> string {
|
||||
string text;
|
||||
text.resize(length + 1);
|
||||
char* p = text.get();
|
||||
while(length--) *p++ = read();
|
||||
return text;
|
||||
}
|
||||
|
||||
auto readChecksum() -> uint32_t {
|
||||
uint32_t checksum = 0;
|
||||
checksum |= read() << 0;
|
||||
checksum |= read() << 8;
|
||||
checksum |= read() << 16;
|
||||
checksum |= read() << 24;
|
||||
return checksum;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
214
beat/patch.hpp
Normal file
214
beat/patch.hpp
Normal file
|
@ -0,0 +1,214 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/file.hpp>
|
||||
#include <nall/filemap.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/string.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct bpspatch {
|
||||
inline auto modify(const uint8_t* data, uint size) -> bool;
|
||||
inline auto source(const uint8_t* data, uint size) -> void;
|
||||
inline auto target(uint8_t* data, uint size) -> void;
|
||||
|
||||
inline auto modify(const string& filename) -> bool;
|
||||
inline auto source(const string& filename) -> bool;
|
||||
inline auto target(const string& filename) -> bool;
|
||||
|
||||
inline auto metadata() const -> string;
|
||||
inline auto size() const -> uint;
|
||||
|
||||
enum result : uint {
|
||||
unknown,
|
||||
success,
|
||||
patch_too_small,
|
||||
patch_invalid_header,
|
||||
source_too_small,
|
||||
target_too_small,
|
||||
source_checksum_invalid,
|
||||
target_checksum_invalid,
|
||||
patch_checksum_invalid,
|
||||
};
|
||||
|
||||
inline auto apply() -> result;
|
||||
|
||||
protected:
|
||||
enum : uint { SourceRead, TargetRead, SourceCopy, TargetCopy };
|
||||
|
||||
filemap modifyFile;
|
||||
const uint8_t* modifyData;
|
||||
uint modifySize;
|
||||
|
||||
filemap sourceFile;
|
||||
const uint8_t* sourceData;
|
||||
uint sourceSize;
|
||||
|
||||
filemap targetFile;
|
||||
uint8_t* targetData;
|
||||
uint targetSize;
|
||||
|
||||
uint modifySourceSize;
|
||||
uint modifyTargetSize;
|
||||
uint modifyMarkupSize;
|
||||
string metadataString;
|
||||
};
|
||||
|
||||
auto bpspatch::modify(const uint8_t* data, uint size) -> bool {
|
||||
if(size < 19) return false;
|
||||
modifyData = data;
|
||||
modifySize = size;
|
||||
|
||||
uint offset = 4;
|
||||
auto decode = [&]() -> uint64_t {
|
||||
uint64_t data = 0, shift = 1;
|
||||
while(true) {
|
||||
uint8_t x = modifyData[offset++];
|
||||
data += (x & 0x7f) * shift;
|
||||
if(x & 0x80) break;
|
||||
shift <<= 7;
|
||||
data += shift;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
modifySourceSize = decode();
|
||||
modifyTargetSize = decode();
|
||||
modifyMarkupSize = decode();
|
||||
|
||||
char buffer[modifyMarkupSize + 1];
|
||||
for(uint n = 0; n < modifyMarkupSize; n++) buffer[n] = modifyData[offset++];
|
||||
buffer[modifyMarkupSize] = 0;
|
||||
metadataString = (const char*)buffer;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto bpspatch::source(const uint8_t* data, uint size) -> void {
|
||||
sourceData = data;
|
||||
sourceSize = size;
|
||||
}
|
||||
|
||||
auto bpspatch::target(uint8_t* data, uint size) -> void {
|
||||
targetData = data;
|
||||
targetSize = size;
|
||||
}
|
||||
|
||||
auto bpspatch::modify(const string& filename) -> bool {
|
||||
if(modifyFile.open(filename, filemap::mode::read) == false) return false;
|
||||
return modify(modifyFile.data(), modifyFile.size());
|
||||
}
|
||||
|
||||
auto bpspatch::source(const string& filename) -> bool {
|
||||
if(sourceFile.open(filename, filemap::mode::read) == false) return false;
|
||||
source(sourceFile.data(), sourceFile.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
auto bpspatch::target(const string& filename) -> bool {
|
||||
file fp;
|
||||
if(fp.open(filename, file::mode::write) == false) return false;
|
||||
fp.truncate(modifyTargetSize);
|
||||
fp.close();
|
||||
|
||||
if(targetFile.open(filename, filemap::mode::readwrite) == false) return false;
|
||||
target(targetFile.data(), targetFile.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
auto bpspatch::metadata() const -> string {
|
||||
return metadataString;
|
||||
}
|
||||
|
||||
auto bpspatch::size() const -> uint {
|
||||
return modifyTargetSize;
|
||||
}
|
||||
|
||||
auto bpspatch::apply() -> result {
|
||||
if(modifySize < 19) return result::patch_too_small;
|
||||
|
||||
Hash::CRC32 modifyChecksum, targetChecksum;
|
||||
uint modifyOffset = 0, sourceRelativeOffset = 0, targetRelativeOffset = 0, outputOffset = 0;
|
||||
|
||||
auto read = [&]() -> uint8_t {
|
||||
uint8_t data = modifyData[modifyOffset++];
|
||||
modifyChecksum.data(data);
|
||||
return data;
|
||||
};
|
||||
|
||||
auto decode = [&]() -> uint64_t {
|
||||
uint64_t data = 0, shift = 1;
|
||||
while(true) {
|
||||
uint8_t x = read();
|
||||
data += (x & 0x7f) * shift;
|
||||
if(x & 0x80) break;
|
||||
shift <<= 7;
|
||||
data += shift;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
auto write = [&](uint8_t data) {
|
||||
targetData[outputOffset++] = data;
|
||||
targetChecksum.data(data);
|
||||
};
|
||||
|
||||
if(read() != 'B') return result::patch_invalid_header;
|
||||
if(read() != 'P') return result::patch_invalid_header;
|
||||
if(read() != 'S') return result::patch_invalid_header;
|
||||
if(read() != '1') return result::patch_invalid_header;
|
||||
|
||||
modifySourceSize = decode();
|
||||
modifyTargetSize = decode();
|
||||
modifyMarkupSize = decode();
|
||||
for(uint n = 0; n < modifyMarkupSize; n++) read();
|
||||
|
||||
if(modifySourceSize > sourceSize) return result::source_too_small;
|
||||
if(modifyTargetSize > targetSize) return result::target_too_small;
|
||||
|
||||
while(modifyOffset < modifySize - 12) {
|
||||
uint length = decode();
|
||||
uint mode = length & 3;
|
||||
length = (length >> 2) + 1;
|
||||
|
||||
switch(mode) {
|
||||
case SourceRead:
|
||||
while(length--) write(sourceData[outputOffset]);
|
||||
break;
|
||||
case TargetRead:
|
||||
while(length--) write(read());
|
||||
break;
|
||||
case SourceCopy:
|
||||
case TargetCopy:
|
||||
int offset = decode();
|
||||
bool negative = offset & 1;
|
||||
offset >>= 1;
|
||||
if(negative) offset = -offset;
|
||||
|
||||
if(mode == SourceCopy) {
|
||||
sourceRelativeOffset += offset;
|
||||
while(length--) write(sourceData[sourceRelativeOffset++]);
|
||||
} else {
|
||||
targetRelativeOffset += offset;
|
||||
while(length--) write(targetData[targetRelativeOffset++]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t modifySourceChecksum = 0, modifyTargetChecksum = 0, modifyModifyChecksum = 0;
|
||||
for(uint n = 0; n < 32; n += 8) modifySourceChecksum |= read() << n;
|
||||
for(uint n = 0; n < 32; n += 8) modifyTargetChecksum |= read() << n;
|
||||
uint32_t checksum = modifyChecksum.value();
|
||||
for(uint n = 0; n < 32; n += 8) modifyModifyChecksum |= read() << n;
|
||||
|
||||
uint32_t sourceChecksum = Hash::CRC32(sourceData, modifySourceSize).value();
|
||||
|
||||
if(sourceChecksum != modifySourceChecksum) return result::source_checksum_invalid;
|
||||
if(targetChecksum.value() != modifyTargetChecksum) return result::target_checksum_invalid;
|
||||
if(checksum != modifyModifyChecksum) return result::patch_checksum_invalid;
|
||||
|
||||
return result::success;
|
||||
}
|
||||
|
||||
}
|
85
bit.hpp
Normal file
85
bit.hpp
Normal file
|
@ -0,0 +1,85 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/stdint.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<unsigned bits> inline auto uclamp(const uintmax_t x) -> uintmax_t {
|
||||
enum : uintmax_t { b = 1ull << (bits - 1), y = b * 2 - 1 };
|
||||
return y + ((x - y) & -(x < y)); //min(x, y);
|
||||
}
|
||||
|
||||
template<unsigned bits> inline auto uclip(const uintmax_t x) -> uintmax_t {
|
||||
enum : uintmax_t { b = 1ull << (bits - 1), m = b * 2 - 1 };
|
||||
return (x & m);
|
||||
}
|
||||
|
||||
template<unsigned bits> inline auto sclamp(const intmax_t x) -> intmax_t {
|
||||
enum : intmax_t { b = 1ull << (bits - 1), m = b - 1 };
|
||||
return (x > m) ? m : (x < -b) ? -b : x;
|
||||
}
|
||||
|
||||
template<unsigned bits> inline auto sclip(const intmax_t x) -> intmax_t {
|
||||
enum : uintmax_t { b = 1ull << (bits - 1), m = b * 2 - 1 };
|
||||
return ((x & m) ^ b) - b;
|
||||
}
|
||||
|
||||
namespace bit {
|
||||
constexpr inline auto mask(const char* s, uintmax_t sum = 0) -> uintmax_t {
|
||||
return (
|
||||
*s == '0' || *s == '1' ? mask(s + 1, (sum << 1) | 1) :
|
||||
*s == ' ' || *s == '_' ? mask(s + 1, sum) :
|
||||
*s ? mask(s + 1, sum << 1) :
|
||||
sum
|
||||
);
|
||||
}
|
||||
|
||||
constexpr inline auto test(const char* s, uintmax_t sum = 0) -> uintmax_t {
|
||||
return (
|
||||
*s == '0' || *s == '1' ? test(s + 1, (sum << 1) | (*s - '0')) :
|
||||
*s == ' ' || *s == '_' ? test(s + 1, sum) :
|
||||
*s ? test(s + 1, sum << 1) :
|
||||
sum
|
||||
);
|
||||
}
|
||||
|
||||
//lowest(0b1110) == 0b0010
|
||||
constexpr inline auto lowest(const uintmax_t x) -> uintmax_t {
|
||||
return x & -x;
|
||||
}
|
||||
|
||||
//clear_lowest(0b1110) == 0b1100
|
||||
constexpr inline auto clear_lowest(const uintmax_t x) -> uintmax_t {
|
||||
return x & (x - 1);
|
||||
}
|
||||
|
||||
//set_lowest(0b0101) == 0b0111
|
||||
constexpr inline auto set_lowest(const uintmax_t x) -> uintmax_t {
|
||||
return x | (x + 1);
|
||||
}
|
||||
|
||||
//count number of bits set in a byte
|
||||
inline auto count(uintmax_t x) -> unsigned {
|
||||
unsigned count = 0;
|
||||
do count += x & 1; while(x >>= 1);
|
||||
return count;
|
||||
}
|
||||
|
||||
//return index of the first bit set (or zero of no bits are set)
|
||||
//first(0b1000) == 3
|
||||
inline auto first(uintmax_t x) -> unsigned {
|
||||
unsigned first = 0;
|
||||
while(x) { if(x & 1) break; x >>= 1; first++; }
|
||||
return first;
|
||||
}
|
||||
|
||||
//round up to next highest single bit:
|
||||
//round(15) == 16, round(16) == 16, round(17) == 32
|
||||
inline auto round(uintmax_t x) -> uintmax_t {
|
||||
if((x & (x - 1)) == 0) return x;
|
||||
while(x & (x - 1)) x &= x - 1;
|
||||
return x << 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
116
bitvector.hpp
Normal file
116
bitvector.hpp
Normal file
|
@ -0,0 +1,116 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/memory.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct bitvector {
|
||||
bitvector() = default;
|
||||
bitvector(uint size) { resize(size); }
|
||||
bitvector(const bitvector& source) { operator=(source); }
|
||||
bitvector(bitvector&& source) { operator=(move(source)); }
|
||||
~bitvector() { reset(); }
|
||||
|
||||
auto operator=(const bitvector& source) -> bitvector& {
|
||||
bits = source.bits;
|
||||
pool = (uint8_t*)memory::resize(pool, bytes());
|
||||
memory::copy(pool, source.pool, bytes());
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator=(bitvector&& source) -> bitvector& {
|
||||
pool = source.pool;
|
||||
bits = source.bits;
|
||||
source.pool = nullptr;
|
||||
source.bits = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool() const { return bits > 0; }
|
||||
auto empty() const -> bool { return bits == 0; }
|
||||
auto size() const -> uint { return bits; }
|
||||
auto bytes() const -> uint { return (bits + 7) / 8; }
|
||||
auto data() -> uint8_t* { return pool; }
|
||||
auto data() const -> const uint8_t* { return pool; }
|
||||
|
||||
auto reset() -> void {
|
||||
if(pool) free(pool);
|
||||
pool = nullptr;
|
||||
bits = 0;
|
||||
}
|
||||
|
||||
auto resize(uint size) -> void {
|
||||
uint from = bits;
|
||||
bits = size;
|
||||
for(uint n = size; n < from; n++) clear(n); //on reduce
|
||||
pool = (uint8_t*)memory::resize(pool, bytes());
|
||||
for(uint n = from; n < size; n++) clear(n); //on expand
|
||||
}
|
||||
|
||||
auto get(uint position) const -> bool {
|
||||
return pool[position >> 3] & (0x80 >> (position & 7));
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
memory::fill(pool, bytes(), 0x00);
|
||||
}
|
||||
|
||||
auto set() -> void {
|
||||
memory::fill(pool, bytes(), 0xff);
|
||||
for(uint n = bits; n < bytes() * 8; n++) clear(n);
|
||||
}
|
||||
|
||||
auto clear(uint position) -> void {
|
||||
pool[position >> 3] &= ~(0x80 >> (position & 7));
|
||||
}
|
||||
|
||||
auto set(uint position) -> void {
|
||||
pool[position >> 3] |= (0x80 >> (position & 7));
|
||||
}
|
||||
|
||||
auto invert(uint position) -> void {
|
||||
get(position) ? clear(position) : set(position);
|
||||
}
|
||||
|
||||
auto set(uint position, bool value) -> void {
|
||||
value ? set(position) : clear(position);
|
||||
}
|
||||
|
||||
struct reference {
|
||||
reference(bitvector& self, uint position) : self(self), position(position) {}
|
||||
operator bool() const { return self.get(position); }
|
||||
auto operator=(bool value) -> reference& { self.set(position, value); return *this; }
|
||||
|
||||
protected:
|
||||
bitvector& self;
|
||||
uint position;
|
||||
};
|
||||
|
||||
auto operator[](uint position) -> reference {
|
||||
return reference(*this, position);
|
||||
}
|
||||
|
||||
auto operator[](uint position) const -> bool {
|
||||
return get(position);
|
||||
}
|
||||
|
||||
struct iterator {
|
||||
iterator(bitvector& self, uint position) : self(self), position(position) {}
|
||||
auto operator!=(const iterator& source) const -> bool { return position != source.position; }
|
||||
auto operator++() -> iterator& { position++; return *this; }
|
||||
auto operator*() -> reference { return self.operator[](position); }
|
||||
|
||||
protected:
|
||||
bitvector& self;
|
||||
uint position;
|
||||
};
|
||||
|
||||
auto begin() -> iterator { return iterator(*this, 0); }
|
||||
auto end() -> iterator { return iterator(*this, bits); }
|
||||
|
||||
protected:
|
||||
uint8_t* pool = nullptr;
|
||||
uint bits = 0;
|
||||
};
|
||||
|
||||
}
|
112
config.hpp
Normal file
112
config.hpp
Normal file
|
@ -0,0 +1,112 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/file.hpp>
|
||||
#include <nall/string.hpp>
|
||||
|
||||
namespace nall {
|
||||
namespace Configuration {
|
||||
|
||||
struct Node {
|
||||
string name;
|
||||
string desc;
|
||||
enum class Type : unsigned { Null, Boolean, Integer, Natural, Double, String } type = Type::Null;
|
||||
void* data = nullptr;
|
||||
vector<Node> children;
|
||||
|
||||
auto empty() const -> bool {
|
||||
return data == nullptr;
|
||||
}
|
||||
|
||||
auto get() const -> string {
|
||||
switch(type) {
|
||||
case Type::Boolean: return {*(bool*)data};
|
||||
case Type::Integer: return {*(int*)data};
|
||||
case Type::Natural: return {*(uint*)data};
|
||||
case Type::Double: return {*(double*)data};
|
||||
case Type::String: return {*(string*)data};
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
auto set(const string& value) -> void {
|
||||
switch(type) {
|
||||
case Type::Boolean: *(bool*)data = (value != "false"); break;
|
||||
case Type::Integer: *(int*)data = integer(value); break;
|
||||
case Type::Natural: *(uint*)data = natural(value); break;
|
||||
case Type::Double: *(double*)data = real(value); break;
|
||||
case Type::String: *(string*)data = value; break;
|
||||
}
|
||||
}
|
||||
|
||||
auto assign() { type = Type::Null; data = nullptr; }
|
||||
auto assign(bool& bind) { type = Type::Boolean; data = (void*)&bind; }
|
||||
auto assign(int& bind) { type = Type::Integer; data = (void*)&bind; }
|
||||
auto assign(uint& bind) { type = Type::Natural; data = (void*)&bind; }
|
||||
auto assign(double& bind) { type = Type::Double; data = (void*)&bind; }
|
||||
auto assign(string& bind) { type = Type::String; data = (void*)&bind; }
|
||||
auto assign(const Node& node) { operator=(node); }
|
||||
|
||||
template<typename T> auto append(T& data, const string& name, const string& desc = "") -> void {
|
||||
Node node;
|
||||
node.assign(data);
|
||||
node.name = name;
|
||||
node.desc = desc;
|
||||
children.append(node);
|
||||
}
|
||||
|
||||
auto find(const string& path) -> maybe<Node&> {
|
||||
auto p = path.split("/");
|
||||
auto name = p.takeFirst();
|
||||
for(auto& child : children) {
|
||||
if(child.name == name) {
|
||||
if(p.size() == 0) return child;
|
||||
return child.find(p.merge("/"));
|
||||
}
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
auto load(Markup::Node path) -> void {
|
||||
for(auto& child : children) {
|
||||
if(auto leaf = path[child.name]) {
|
||||
if(!child.empty()) child.set(leaf.text());
|
||||
child.load(leaf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto save(file& fp, unsigned depth = 0) -> void {
|
||||
for(auto& child : children) {
|
||||
if(child.desc) {
|
||||
for(auto n : range(depth)) fp.print(" ");
|
||||
fp.print("//", child.desc, "\n");
|
||||
}
|
||||
for(auto n : range(depth)) fp.print(" ");
|
||||
fp.print(child.name);
|
||||
if(!child.empty()) fp.print(": ", child.get());
|
||||
fp.print("\n");
|
||||
child.save(fp, depth + 1);
|
||||
if(depth == 0) fp.print("\n");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Document : Node {
|
||||
auto load(const string& filename) -> bool {
|
||||
if(!file::exists(filename)) return false;
|
||||
auto document = BML::unserialize(string::read(filename));
|
||||
Node::load(document);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto save(const string& filename) -> bool {
|
||||
file fp(filename, file::mode::write);
|
||||
if(!fp.open()) return false;
|
||||
Node::save(fp);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
297
database/odbc.hpp
Normal file
297
database/odbc.hpp
Normal file
|
@ -0,0 +1,297 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/string.hpp>
|
||||
|
||||
#include <sql.h>
|
||||
#include <sqltypes.h>
|
||||
#include <sqlext.h>
|
||||
|
||||
namespace nall { namespace Database {
|
||||
|
||||
struct ODBC {
|
||||
struct Statement {
|
||||
Statement(const Statement& source) = delete;
|
||||
auto operator=(const Statement& source) -> Statement& = delete;
|
||||
|
||||
Statement(SQLHANDLE statement) : _statement(statement) {}
|
||||
Statement(Statement&& source) { operator=(move(source)); }
|
||||
|
||||
auto operator=(Statement&& source) -> Statement& {
|
||||
_statement = source._statement;
|
||||
_output = source._output;
|
||||
_values = move(source._values);
|
||||
source._statement = nullptr;
|
||||
source._output = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto columns() -> unsigned {
|
||||
SQLSMALLINT columns = 0;
|
||||
if(statement()) SQLNumResultCols(statement(), &columns);
|
||||
return columns;
|
||||
}
|
||||
|
||||
auto integer(unsigned column) -> int64_t {
|
||||
if(auto value = _values(column)) return value.get<int64_t>(0);
|
||||
int64_t value = 0;
|
||||
SQLGetData(statement(), 1 + column, SQL_C_SBIGINT, &value, 0, nullptr);
|
||||
_values(column) = (int64_t)value;
|
||||
return value;
|
||||
}
|
||||
|
||||
auto natural(unsigned column) -> uint64_t {
|
||||
if(auto value = _values(column)) return value.get<uint64_t>(0);
|
||||
uint64_t value = 0;
|
||||
SQLGetData(statement(), 1 + column, SQL_C_UBIGINT, &value, 0, nullptr);
|
||||
_values(column) = (uint64_t)value;
|
||||
return value;
|
||||
}
|
||||
|
||||
auto real(unsigned column) -> double {
|
||||
if(auto value = _values(column)) return value.get<double>(0.0);
|
||||
double value = 0.0;
|
||||
SQLGetData(statement(), 1 + column, SQL_C_DOUBLE, &value, 0, nullptr);
|
||||
_values(column) = (double)value;
|
||||
return value;
|
||||
}
|
||||
|
||||
auto text(unsigned column) -> string {
|
||||
if(auto value = _values(column)) return value.get<string>({});
|
||||
string value;
|
||||
value.resize(65535);
|
||||
SQLLEN size = 0;
|
||||
SQLGetData(statement(), 1 + column, SQL_C_CHAR, value.get(), value.size(), &size);
|
||||
value.resize(size);
|
||||
_values(column) = (string)value;
|
||||
return value;
|
||||
}
|
||||
|
||||
auto data(unsigned column) -> vector<uint8_t> {
|
||||
if(auto value = _values(column)) return value.get<vector<uint8_t>>({});
|
||||
vector<uint8_t> value;
|
||||
value.resize(65535);
|
||||
SQLLEN size = 0;
|
||||
SQLGetData(statement(), 1 + column, SQL_C_CHAR, value.data(), value.size(), &size);
|
||||
value.resize(size);
|
||||
_values(column) = (vector<uint8_t>)value;
|
||||
return value;
|
||||
}
|
||||
|
||||
auto integer() -> int64_t { return integer(_output++); }
|
||||
auto natural() -> uint64_t { return natural(_output++); }
|
||||
auto real() -> double { return real(_output++); }
|
||||
auto text() -> string { return text(_output++); }
|
||||
auto data() -> vector<uint8_t> { return data(_output++); }
|
||||
|
||||
protected:
|
||||
virtual auto statement() -> SQLHANDLE { return _statement; }
|
||||
|
||||
SQLHANDLE _statement = nullptr;
|
||||
unsigned _output = 0;
|
||||
vector<any> _values; //some ODBC drivers (eg MS-SQL) do not allow the same column to be read more than once
|
||||
};
|
||||
|
||||
struct Query : Statement {
|
||||
Query(const Query& source) = delete;
|
||||
auto operator=(const Query& source) -> Query& = delete;
|
||||
|
||||
Query(SQLHANDLE statement) : Statement(statement) {}
|
||||
Query(Query&& source) : Statement(source._statement) { operator=(move(source)); }
|
||||
|
||||
~Query() {
|
||||
if(statement()) {
|
||||
SQLFreeHandle(SQL_HANDLE_STMT, _statement);
|
||||
_statement = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto operator=(Query&& source) -> Query& {
|
||||
Statement::operator=(move(source));
|
||||
_bindings = move(source._bindings);
|
||||
_result = source._result;
|
||||
_input = source._input;
|
||||
_stepped = source._stepped;
|
||||
source._result = SQL_SUCCESS;
|
||||
source._input = 0;
|
||||
source._stepped = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool() {
|
||||
//this is likely not the best way to test if the query has returned data ...
|
||||
//but I wasn't able to find an ODBC API for this seemingly simple task
|
||||
return statement() && success();
|
||||
}
|
||||
|
||||
//ODBC SQLBindParameter only holds pointers to data values
|
||||
//if the bound paramters go out of scope before the query is executed, binding would reference dangling pointers
|
||||
//so to work around this, we cache all parameters inside Query until the query is executed
|
||||
|
||||
auto& bind(unsigned column, nullptr_t) { return _bindings.append({column, any{(nullptr_t)nullptr}}), *this; }
|
||||
auto& bind(unsigned column, int32_t value) { return _bindings.append({column, any{(int32_t)value}}), *this; }
|
||||
auto& bind(unsigned column, uint32_t value) { return _bindings.append({column, any{(uint32_t)value}}), *this; }
|
||||
auto& bind(unsigned column, int64_t value) { return _bindings.append({column, any{(int64_t)value}}), *this; }
|
||||
auto& bind(unsigned column, uint64_t value) { return _bindings.append({column, any{(uint64_t)value}}), *this; }
|
||||
auto& bind(unsigned column, double value) { return _bindings.append({column, any{(double)value}}), *this; }
|
||||
auto& bind(unsigned column, const string& value) { return _bindings.append({column, any{(string)value}}), *this; }
|
||||
auto& bind(unsigned column, const vector<uint8_t>& value) { return _bindings.append({column, any{(vector<uint8_t>)value}}), *this; }
|
||||
|
||||
auto& bind(nullptr_t) { return bind(_input++, nullptr); }
|
||||
auto& bind(int32_t value) { return bind(_input++, value); }
|
||||
auto& bind(uint32_t value) { return bind(_input++, value); }
|
||||
auto& bind(int64_t value) { return bind(_input++, value); }
|
||||
auto& bind(uint64_t value) { return bind(_input++, value); }
|
||||
auto& bind(double value) { return bind(_input++, value); }
|
||||
auto& bind(const string& value) { return bind(_input++, value); }
|
||||
auto& bind(const vector<uint8_t>& value) { return bind(_input++, value); }
|
||||
|
||||
auto step() -> bool {
|
||||
if(!_stepped) {
|
||||
for(auto& binding : _bindings) {
|
||||
if(binding.value.is<nullptr_t>()) {
|
||||
SQLLEN length = SQL_NULL_DATA;
|
||||
SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_NUMERIC, SQL_NUMERIC, 0, 0, nullptr, 0, &length);
|
||||
} else if(binding.value.is<int32_t>()) {
|
||||
SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &binding.value.get<int32_t>(), 0, nullptr);
|
||||
} else if(binding.value.is<uint32_t>()) {
|
||||
SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, &binding.value.get<uint32_t>(), 0, nullptr);
|
||||
} else if(binding.value.is<int64_t>()) {
|
||||
SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_INTEGER, 0, 0, &binding.value.get<int64_t>(), 0, nullptr);
|
||||
} else if(binding.value.is<uint64_t>()) {
|
||||
SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_UBIGINT, SQL_INTEGER, 0, 0, &binding.value.get<uint64_t>(), 0, nullptr);
|
||||
} else if(binding.value.is<double>()) {
|
||||
SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_DOUBLE, 0, 0, &binding.value.get<double>(), 0, nullptr);
|
||||
} else if(binding.value.is<string>()) {
|
||||
auto& value = binding.value.get<string>();
|
||||
SQLLEN length = SQL_NTS;
|
||||
SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, value.size(), 0, (SQLPOINTER)value.data(), 0, &length);
|
||||
} else if(binding.value.is<vector<uint8_t>>()) {
|
||||
auto& value = binding.value.get<vector<uint8_t>>();
|
||||
SQLLEN length = value.size();
|
||||
SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARBINARY, value.size(), 0, (SQLPOINTER)value.data(), 0, &length);
|
||||
}
|
||||
}
|
||||
|
||||
_stepped = true;
|
||||
_result = SQLExecute(_statement);
|
||||
if(!success()) return false;
|
||||
}
|
||||
|
||||
_values.reset(); //clear previous row's cached read results
|
||||
_result = SQLFetch(_statement);
|
||||
_output = 0;
|
||||
return success();
|
||||
}
|
||||
|
||||
struct Iterator {
|
||||
Iterator(Query& query, bool finished) : query(query), finished(finished) {}
|
||||
auto operator*() -> Statement { return query._statement; }
|
||||
auto operator!=(const Iterator& source) const -> bool { return finished != source.finished; }
|
||||
auto operator++() -> Iterator& { finished = !query.step(); return *this; }
|
||||
|
||||
protected:
|
||||
Query& query;
|
||||
bool finished = false;
|
||||
};
|
||||
|
||||
auto begin() -> Iterator { return Iterator(*this, !step()); }
|
||||
auto end() -> Iterator { return Iterator(*this, true); }
|
||||
|
||||
private:
|
||||
auto success() const -> bool {
|
||||
return _result == SQL_SUCCESS || _result == SQL_SUCCESS_WITH_INFO;
|
||||
}
|
||||
|
||||
auto statement() -> SQLHANDLE override {
|
||||
if(!_stepped) step();
|
||||
return _statement;
|
||||
}
|
||||
|
||||
struct Binding {
|
||||
unsigned column;
|
||||
any value;
|
||||
};
|
||||
vector<Binding> _bindings;
|
||||
|
||||
SQLRETURN _result = SQL_SUCCESS;
|
||||
unsigned _input = 0;
|
||||
bool _stepped = false;
|
||||
};
|
||||
|
||||
ODBC() {
|
||||
_result = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &_environment);
|
||||
if(!success()) return;
|
||||
|
||||
SQLSetEnvAttr(_environment, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
|
||||
}
|
||||
|
||||
ODBC(const string& database, const string& username, const string& password) : ODBC() {
|
||||
open(database, username, password);
|
||||
}
|
||||
|
||||
~ODBC() {
|
||||
if(_environment) {
|
||||
close();
|
||||
SQLFreeHandle(SQL_HANDLE_ENV, _environment);
|
||||
_environment = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
explicit operator bool() const { return _connection; }
|
||||
|
||||
auto open(const string& database, const string& username, const string& password) -> bool {
|
||||
if(!_environment) return false;
|
||||
close();
|
||||
|
||||
_result = SQLAllocHandle(SQL_HANDLE_DBC, _environment, &_connection);
|
||||
if(!success()) return false;
|
||||
|
||||
SQLSetConnectAttr(_connection, SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0);
|
||||
_result = SQLConnectA(_connection,
|
||||
(SQLCHAR*)database.data(), SQL_NTS,
|
||||
(SQLCHAR*)username.data(), SQL_NTS,
|
||||
(SQLCHAR*)password.data(), SQL_NTS
|
||||
);
|
||||
if(!success()) return close(), false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto close() -> void {
|
||||
if(_connection) {
|
||||
SQLDisconnect(_connection);
|
||||
SQLFreeHandle(SQL_HANDLE_DBC, _connection);
|
||||
_connection = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... P> auto execute(const string& statement, P&&... p) -> Query {
|
||||
if(!_connection) return {nullptr};
|
||||
|
||||
SQLHANDLE _statement = nullptr;
|
||||
_result = SQLAllocHandle(SQL_HANDLE_STMT, _connection, &_statement);
|
||||
if(!success()) return {nullptr};
|
||||
|
||||
Query query{_statement};
|
||||
_result = SQLPrepareA(_statement, (SQLCHAR*)statement.data(), SQL_NTS);
|
||||
if(!success()) return {nullptr};
|
||||
|
||||
bind(query, forward<P>(p)...);
|
||||
return query;
|
||||
}
|
||||
|
||||
private:
|
||||
auto success() const -> bool { return _result == SQL_SUCCESS || _result == SQL_SUCCESS_WITH_INFO; }
|
||||
|
||||
auto bind(Query&) -> void {}
|
||||
template<typename T, typename... P> auto bind(Query& query, const T& value, P&&... p) -> void {
|
||||
query.bind(value);
|
||||
bind(query, forward<P>(p)...);
|
||||
}
|
||||
|
||||
SQLHANDLE _environment = nullptr;
|
||||
SQLHANDLE _connection = nullptr;
|
||||
SQLRETURN _result = SQL_SUCCESS;
|
||||
};
|
||||
|
||||
}}
|
203
database/sqlite3.hpp
Normal file
203
database/sqlite3.hpp
Normal file
|
@ -0,0 +1,203 @@
|
|||
#pragma once
|
||||
|
||||
/* SQLite3 C++ RAII wrapper for nall
|
||||
*
|
||||
* Note on code below: it is safe (no-op) to call sqlite3_* functions on null sqlite3 objects
|
||||
*/
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/string.hpp>
|
||||
|
||||
namespace nall { namespace Database {
|
||||
|
||||
struct SQLite3 {
|
||||
struct Statement {
|
||||
Statement(const Statement& source) = delete;
|
||||
auto operator=(const Statement& source) -> Statement& = delete;
|
||||
|
||||
Statement(sqlite3_stmt* statement) : _statement(statement) {}
|
||||
Statement(Statement&& source) { operator=(move(source)); }
|
||||
|
||||
auto operator=(Statement&& source) -> Statement& {
|
||||
_statement = source._statement;
|
||||
_response = source._response;
|
||||
_output = source._output;
|
||||
source._statement = nullptr;
|
||||
source._response = SQLITE_OK;
|
||||
source._output = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool() {
|
||||
return sqlite3_data_count(statement());
|
||||
}
|
||||
|
||||
auto columns() -> unsigned {
|
||||
return sqlite3_column_count(statement());
|
||||
}
|
||||
|
||||
auto integer(unsigned column) -> int64_t {
|
||||
return sqlite3_column_int64(statement(), column);
|
||||
}
|
||||
|
||||
auto natural(unsigned column) -> uint64_t {
|
||||
return sqlite3_column_int64(statement(), column);
|
||||
}
|
||||
|
||||
auto real(unsigned column) -> double {
|
||||
return sqlite3_column_double(statement(), column);
|
||||
}
|
||||
|
||||
auto text(unsigned column) -> string {
|
||||
string result;
|
||||
if(auto text = sqlite3_column_text(statement(), column)) {
|
||||
result.resize(sqlite3_column_bytes(statement(), column));
|
||||
memory::copy(result.get(), text, result.size());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto data(unsigned column) -> vector<uint8_t> {
|
||||
vector<uint8_t> result;
|
||||
if(auto data = sqlite3_column_blob(statement(), column)) {
|
||||
result.resize(sqlite3_column_bytes(statement(), column));
|
||||
memory::copy(result.data(), data, result.size());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto integer() -> int64_t { return integer(_output++); }
|
||||
auto natural() -> uint64_t { return natural(_output++); }
|
||||
auto real() -> double { return real(_output++); }
|
||||
auto text() -> string { return text(_output++); }
|
||||
auto data() -> vector<uint8_t> { return data(_output++); }
|
||||
|
||||
protected:
|
||||
virtual auto statement() -> sqlite3_stmt* { return _statement; }
|
||||
|
||||
sqlite3_stmt* _statement = nullptr;
|
||||
signed _response = SQLITE_OK;
|
||||
unsigned _output = 0;
|
||||
};
|
||||
|
||||
struct Query : Statement {
|
||||
Query(const Query& source) = delete;
|
||||
auto operator=(const Query& source) -> Query& = delete;
|
||||
|
||||
Query(sqlite3_stmt* statement) : Statement(statement) {}
|
||||
Query(Query&& source) : Statement(source._statement) { operator=(move(source)); }
|
||||
|
||||
~Query() {
|
||||
sqlite3_finalize(statement());
|
||||
_statement = nullptr;
|
||||
}
|
||||
|
||||
auto operator=(Query&& source) -> Query& {
|
||||
_statement = source._statement;
|
||||
_input = source._input;
|
||||
source._statement = nullptr;
|
||||
source._input = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto& bind(unsigned column, nullptr_t) { sqlite3_bind_null(_statement, 1 + column); return *this; }
|
||||
auto& bind(unsigned column, int32_t value) { sqlite3_bind_int(_statement, 1 + column, value); return *this; }
|
||||
auto& bind(unsigned column, uint32_t value) { sqlite3_bind_int(_statement, 1 + column, value); return *this; }
|
||||
auto& bind(unsigned column, int64_t value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; }
|
||||
auto& bind(unsigned column, uint64_t value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; }
|
||||
auto& bind(unsigned column, double value) { sqlite3_bind_double(_statement, 1 + column, value); return *this; }
|
||||
auto& bind(unsigned column, const string& value) { sqlite3_bind_text(_statement, 1 + column, value.data(), value.size(), SQLITE_TRANSIENT); return *this; }
|
||||
auto& bind(unsigned column, const vector<uint8_t>& value) { sqlite3_bind_blob(_statement, 1 + column, value.data(), value.size(), SQLITE_TRANSIENT); return *this; }
|
||||
|
||||
auto& bind(nullptr_t) { return bind(_input++, nullptr); }
|
||||
auto& bind(int32_t value) { return bind(_input++, value); }
|
||||
auto& bind(uint32_t value) { return bind(_input++, value); }
|
||||
auto& bind(int64_t value) { return bind(_input++, value); }
|
||||
auto& bind(uint64_t value) { return bind(_input++, value); }
|
||||
auto& bind(double value) { return bind(_input++, value); }
|
||||
auto& bind(const string& value) { return bind(_input++, value); }
|
||||
auto& bind(const vector<uint8_t>& value) { return bind(_input++, value); }
|
||||
|
||||
auto step() -> bool {
|
||||
_stepped = true;
|
||||
return sqlite3_step(_statement) == SQLITE_ROW;
|
||||
}
|
||||
|
||||
struct Iterator {
|
||||
Iterator(Query& query, bool finished) : query(query), finished(finished) {}
|
||||
auto operator*() -> Statement { return query._statement; }
|
||||
auto operator!=(const Iterator& source) const -> bool { return finished != source.finished; }
|
||||
auto operator++() -> Iterator& { finished = !query.step(); return *this; }
|
||||
|
||||
protected:
|
||||
Query& query;
|
||||
bool finished = false;
|
||||
};
|
||||
|
||||
auto begin() -> Iterator { return Iterator(*this, !step()); }
|
||||
auto end() -> Iterator { return Iterator(*this, true); }
|
||||
|
||||
private:
|
||||
auto statement() -> sqlite3_stmt* override {
|
||||
if(!_stepped) step();
|
||||
return _statement;
|
||||
}
|
||||
|
||||
unsigned _input = 0;
|
||||
bool _stepped = false;
|
||||
};
|
||||
|
||||
SQLite3() = default;
|
||||
SQLite3(const string& filename) { open(filename); }
|
||||
~SQLite3() { close(); }
|
||||
|
||||
explicit operator bool() const { return _database; }
|
||||
|
||||
auto open(const string& filename) -> bool {
|
||||
close();
|
||||
sqlite3_open(filename, &_database);
|
||||
return _database;
|
||||
}
|
||||
|
||||
auto close() -> void {
|
||||
sqlite3_close(_database);
|
||||
_database = nullptr;
|
||||
}
|
||||
|
||||
template<typename... P> auto execute(const string& statement, P&&... p) -> Query {
|
||||
if(!_database) return {nullptr};
|
||||
|
||||
sqlite3_stmt* _statement = nullptr;
|
||||
sqlite3_prepare_v2(_database, statement.data(), statement.size(), &_statement, nullptr);
|
||||
if(!_statement) {
|
||||
if(_debug) print("[sqlite3_prepare_v2] ", sqlite3_errmsg(_database), "\n");
|
||||
return {nullptr};
|
||||
}
|
||||
|
||||
Query query{_statement};
|
||||
bind(query, forward<P>(p)...);
|
||||
return query;
|
||||
}
|
||||
|
||||
auto lastInsertID() const -> uint64_t {
|
||||
return _database ? sqlite3_last_insert_rowid(_database) : 0;
|
||||
}
|
||||
|
||||
auto setDebug(bool debug = true) -> void {
|
||||
_debug = debug;
|
||||
}
|
||||
|
||||
protected:
|
||||
auto bind(Query&) -> void {}
|
||||
template<typename T, typename... P> auto bind(Query& query, const T& value, P&&... p) -> void {
|
||||
query.bind(value);
|
||||
bind(query, forward<P>(p)...);
|
||||
}
|
||||
|
||||
bool _debug = false;
|
||||
sqlite3* _database = nullptr;
|
||||
};
|
||||
|
||||
}}
|
46
decode/base64.hpp
Normal file
46
decode/base64.hpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall { namespace Decode {
|
||||
|
||||
inline auto Base64(const string& text) -> vector<uint8_t> {
|
||||
auto value = [](char n) -> uint8_t {
|
||||
if(n >= 'A' && n <= 'Z') return n - 'A' + 0;
|
||||
if(n >= 'a' && n <= 'z') return n - 'a' + 26;
|
||||
if(n >= '0' && n <= '9') return n - '0' + 52;
|
||||
if(n == '+' || n == '-') return 62;
|
||||
if(n == '/' || n == '_') return 63;
|
||||
return 64; //error code
|
||||
};
|
||||
|
||||
vector<uint8_t> result;
|
||||
|
||||
uint8_t buffer, output;
|
||||
for(unsigned i = 0; i < text.size(); i++) {
|
||||
uint8_t buffer = value(text[i]);
|
||||
if(buffer > 63) break;
|
||||
|
||||
switch(i & 3) {
|
||||
case 0:
|
||||
output = buffer << 2;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
result.append(output | buffer >> 4);
|
||||
output = (buffer & 15) << 4;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
result.append(output | buffer >> 2);
|
||||
output = (buffer & 3) << 6;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
result.append(output | buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}}
|
76
decode/bmp.hpp
Normal file
76
decode/bmp.hpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall { namespace Decode {
|
||||
|
||||
struct BMP {
|
||||
BMP() = default;
|
||||
BMP(const string& filename) { load(filename); }
|
||||
BMP(const uint8_t* data, unsigned size) { load(data, size); }
|
||||
|
||||
explicit operator bool() const { return _data; }
|
||||
|
||||
auto reset() -> void {
|
||||
if(_data) { delete[] _data; _data = nullptr; }
|
||||
}
|
||||
|
||||
auto data() -> uint32_t* { return _data; }
|
||||
auto data() const -> const uint32_t* { return _data; }
|
||||
auto width() const -> unsigned { return _width; }
|
||||
auto height() const -> unsigned { return _height; }
|
||||
|
||||
auto load(const string& filename) -> bool {
|
||||
auto buffer = file::read(filename);
|
||||
return load(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
auto load(const uint8_t* data, unsigned size) -> bool {
|
||||
if(size < 0x36) return false;
|
||||
const uint8_t* p = data;
|
||||
if(read(p, 2) != 0x4d42) return false; //signature
|
||||
read(p, 8);
|
||||
unsigned offset = read(p, 4);
|
||||
if(read(p, 4) != 40) return false; //DIB size
|
||||
signed width = read(p, 4);
|
||||
if(width < 0) return false;
|
||||
signed height = read(p, 4);
|
||||
bool flip = height < 0;
|
||||
if(flip) height = -height;
|
||||
read(p, 2);
|
||||
unsigned bitsPerPixel = read(p, 2);
|
||||
if(bitsPerPixel != 24 && bitsPerPixel != 32) return false;
|
||||
if(read(p, 4) != 0) return false; //compression type
|
||||
|
||||
_width = width;
|
||||
_height = height;
|
||||
_data = new uint32_t[width * height];
|
||||
|
||||
unsigned bytesPerPixel = bitsPerPixel / 8;
|
||||
unsigned alignedWidth = width * bytesPerPixel;
|
||||
unsigned paddingLength = 0;
|
||||
while(alignedWidth % 4) alignedWidth++, paddingLength++;
|
||||
|
||||
p = data + offset;
|
||||
for(auto y : range(height)) {
|
||||
uint32_t* output = flip ? _data + (height - 1 - y) * width : _data + y * width;
|
||||
for(auto x : range(width)) {
|
||||
*output++ = read(p, bytesPerPixel) | (bitsPerPixel == 24 ? 255u << 24 : 0);
|
||||
}
|
||||
if(paddingLength) read(p, paddingLength);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t* _data = nullptr;
|
||||
unsigned _width = 0;
|
||||
unsigned _height = 0;
|
||||
|
||||
auto read(const uint8_t*& buffer, unsigned length) -> uintmax_t {
|
||||
uintmax_t result = 0;
|
||||
for(auto n : range(length)) result |= (uintmax_t)*buffer++ << (n << 3);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
}}
|
78
decode/gzip.hpp
Normal file
78
decode/gzip.hpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/file.hpp>
|
||||
#include <nall/decode/inflate.hpp>
|
||||
|
||||
namespace nall { namespace Decode {
|
||||
|
||||
struct GZIP {
|
||||
inline ~GZIP();
|
||||
|
||||
inline auto decompress(const string& filename) -> bool;
|
||||
inline auto decompress(const uint8_t* data, uint size) -> bool;
|
||||
|
||||
string filename;
|
||||
uint8_t* data = nullptr;
|
||||
uint size = 0;
|
||||
};
|
||||
|
||||
GZIP::~GZIP() {
|
||||
if(data) delete[] data;
|
||||
}
|
||||
|
||||
auto GZIP::decompress(const string& filename) -> bool {
|
||||
if(auto memory = file::read(filename)) {
|
||||
return decompress(memory.data(), memory.size());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto GZIP::decompress(const uint8_t* data, uint size) -> bool {
|
||||
if(size < 18) return false;
|
||||
if(data[0] != 0x1f) return false;
|
||||
if(data[1] != 0x8b) return false;
|
||||
uint cm = data[2];
|
||||
uint flg = data[3];
|
||||
uint mtime = data[4];
|
||||
mtime |= data[5] << 8;
|
||||
mtime |= data[6] << 16;
|
||||
mtime |= data[7] << 24;
|
||||
uint xfl = data[8];
|
||||
uint os = data[9];
|
||||
uint p = 10;
|
||||
uint isize = data[size - 4];
|
||||
isize |= data[size - 3] << 8;
|
||||
isize |= data[size - 2] << 16;
|
||||
isize |= data[size - 1] << 24;
|
||||
filename = "";
|
||||
|
||||
if(flg & 0x04) { //FEXTRA
|
||||
uint xlen = data[p + 0];
|
||||
xlen |= data[p + 1] << 8;
|
||||
p += 2 + xlen;
|
||||
}
|
||||
|
||||
if(flg & 0x08) { //FNAME
|
||||
char buffer[PATH_MAX];
|
||||
for(uint n = 0; n < PATH_MAX; n++, p++) {
|
||||
buffer[n] = data[p];
|
||||
if(data[p] == 0) break;
|
||||
}
|
||||
if(data[p++]) return false;
|
||||
filename = buffer;
|
||||
}
|
||||
|
||||
if(flg & 0x10) { //FCOMMENT
|
||||
while(data[p++]);
|
||||
}
|
||||
|
||||
if(flg & 0x02) { //FHCRC
|
||||
p += 2;
|
||||
}
|
||||
|
||||
this->size = isize;
|
||||
this->data = new uint8_t[this->size];
|
||||
return inflate(this->data, this->size, data + p, size - p - 8);
|
||||
}
|
||||
|
||||
}}
|
346
decode/inflate.hpp
Normal file
346
decode/inflate.hpp
Normal file
|
@ -0,0 +1,346 @@
|
|||
#pragma once
|
||||
|
||||
#include <setjmp.h>
|
||||
|
||||
namespace nall { namespace Decode {
|
||||
|
||||
namespace puff {
|
||||
inline auto puff(
|
||||
unsigned char* dest, unsigned long* destlen,
|
||||
unsigned char* source, unsigned long* sourcelen
|
||||
) -> int;
|
||||
}
|
||||
|
||||
inline auto inflate(
|
||||
uint8_t* target, uint targetLength,
|
||||
const uint8_t* source, uint sourceLength
|
||||
) -> bool {
|
||||
unsigned long tl = targetLength, sl = sourceLength;
|
||||
int result = puff::puff((unsigned char*)target, &tl, (unsigned char*)source, &sl);
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
namespace puff {
|
||||
|
||||
enum : uint {
|
||||
MAXBITS = 15,
|
||||
MAXLCODES = 286,
|
||||
MAXDCODES = 30,
|
||||
FIXLCODES = 288,
|
||||
MAXCODES = MAXLCODES + MAXDCODES,
|
||||
};
|
||||
|
||||
struct state {
|
||||
unsigned char* out;
|
||||
unsigned long outlen;
|
||||
unsigned long outcnt;
|
||||
|
||||
unsigned char* in;
|
||||
unsigned long inlen;
|
||||
unsigned long incnt;
|
||||
int bitbuf;
|
||||
int bitcnt;
|
||||
|
||||
jmp_buf env;
|
||||
};
|
||||
|
||||
struct huffman {
|
||||
short* count;
|
||||
short* symbol;
|
||||
};
|
||||
|
||||
inline auto bits(state* s, int need) -> int {
|
||||
long val;
|
||||
|
||||
val = s->bitbuf;
|
||||
while(s->bitcnt < need) {
|
||||
if(s->incnt == s->inlen) longjmp(s->env, 1);
|
||||
val |= (long)(s->in[s->incnt++]) << s->bitcnt;
|
||||
s->bitcnt += 8;
|
||||
}
|
||||
|
||||
s->bitbuf = (int)(val >> need);
|
||||
s->bitcnt -= need;
|
||||
|
||||
return (int)(val & ((1L << need) - 1));
|
||||
}
|
||||
|
||||
inline auto stored(state* s) -> int {
|
||||
uint len;
|
||||
|
||||
s->bitbuf = 0;
|
||||
s->bitcnt = 0;
|
||||
|
||||
if(s->incnt + 4 > s->inlen) return 2;
|
||||
len = s->in[s->incnt++];
|
||||
len |= s->in[s->incnt++] << 8;
|
||||
if(s->in[s->incnt++] != (~len & 0xff) ||
|
||||
s->in[s->incnt++] != ((~len >> 8) & 0xff)
|
||||
) return 2;
|
||||
|
||||
if(s->incnt + len > s->inlen) return 2;
|
||||
if(s->out != nullptr) {
|
||||
if(s->outcnt + len > s->outlen) return 1;
|
||||
while(len--) s->out[s->outcnt++] = s->in[s->incnt++];
|
||||
} else {
|
||||
s->outcnt += len;
|
||||
s->incnt += len;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline auto decode(state* s, huffman* h) -> int {
|
||||
int len, code, first, count, index, bitbuf, left;
|
||||
short* next;
|
||||
|
||||
bitbuf = s->bitbuf;
|
||||
left = s->bitcnt;
|
||||
code = first = index = 0;
|
||||
len = 1;
|
||||
next = h->count + 1;
|
||||
while(true) {
|
||||
while(left--) {
|
||||
code |= bitbuf & 1;
|
||||
bitbuf >>= 1;
|
||||
count = *next++;
|
||||
if(code - count < first) {
|
||||
s->bitbuf = bitbuf;
|
||||
s->bitcnt = (s->bitcnt - len) & 7;
|
||||
return h->symbol[index + (code - first)];
|
||||
}
|
||||
index += count;
|
||||
first += count;
|
||||
first <<= 1;
|
||||
code <<= 1;
|
||||
len++;
|
||||
}
|
||||
left = (MAXBITS + 1) - len;
|
||||
if(left == 0) break;
|
||||
if(s->incnt == s->inlen) longjmp(s->env, 1);
|
||||
bitbuf = s->in[s->incnt++];
|
||||
if(left > 8) left = 8;
|
||||
}
|
||||
|
||||
return -10;
|
||||
}
|
||||
|
||||
inline auto construct(huffman* h, short* length, int n) -> int {
|
||||
int symbol, len, left;
|
||||
short offs[MAXBITS + 1];
|
||||
|
||||
for(len = 0; len <= MAXBITS; len++) h->count[len] = 0;
|
||||
for(symbol = 0; symbol < n; symbol++) h->count[length[symbol]]++;
|
||||
if(h->count[0] == n) return 0;
|
||||
|
||||
left = 1;
|
||||
for(len = 1; len <= MAXBITS; len++) {
|
||||
left <<= 1;
|
||||
left -= h->count[len];
|
||||
if(left < 0) return left;
|
||||
}
|
||||
|
||||
offs[1] = 0;
|
||||
for(len = 1; len < MAXBITS; len++) offs[len + 1] = offs[len] + h->count[len];
|
||||
|
||||
for(symbol = 0; symbol < n; symbol++) {
|
||||
if(length[symbol] != 0) h->symbol[offs[length[symbol]]++] = symbol;
|
||||
}
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
inline auto codes(state* s, huffman* lencode, huffman* distcode) -> int {
|
||||
int symbol, len;
|
||||
uint dist;
|
||||
static const short lens[29] = {
|
||||
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
|
||||
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258
|
||||
};
|
||||
static const short lext[29] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
|
||||
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0
|
||||
};
|
||||
static const short dists[30] = {
|
||||
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
|
||||
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
|
||||
8193, 12289, 16385, 24577
|
||||
};
|
||||
static const short dext[30] = {
|
||||
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
|
||||
7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
|
||||
12, 12, 13, 13
|
||||
};
|
||||
|
||||
do {
|
||||
symbol = decode(s, lencode);
|
||||
if(symbol < 0) return symbol;
|
||||
if(symbol < 256) {
|
||||
if(s->out != nullptr) {
|
||||
if(s->outcnt == s->outlen) return 1;
|
||||
s->out[s->outcnt] = symbol;
|
||||
}
|
||||
s->outcnt++;
|
||||
} else if(symbol > 256) {
|
||||
symbol -= 257;
|
||||
if(symbol >= 29) return -10;
|
||||
len = lens[symbol] + bits(s, lext[symbol]);
|
||||
|
||||
symbol = decode(s, distcode);
|
||||
if(symbol < 0) return symbol;
|
||||
dist = dists[symbol] + bits(s, dext[symbol]);
|
||||
#ifndef INFLATE_ALLOW_INVALID_DISTANCE_TOO_FAR
|
||||
if(dist > s->outcnt) return -11;
|
||||
#endif
|
||||
|
||||
if(s->out != nullptr) {
|
||||
if(s->outcnt + len > s->outlen) return 1;
|
||||
while(len--) {
|
||||
s->out[s->outcnt] =
|
||||
#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOO_FAR
|
||||
dist > s->outcnt ? 0 :
|
||||
#endif
|
||||
s->out[s->outcnt - dist];
|
||||
s->outcnt++;
|
||||
}
|
||||
} else {
|
||||
s->outcnt += len;
|
||||
}
|
||||
}
|
||||
} while(symbol != 256);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline auto fixed(state* s) -> int {
|
||||
static int virgin = 1;
|
||||
static short lencnt[MAXBITS + 1], lensym[FIXLCODES];
|
||||
static short distcnt[MAXBITS + 1], distsym[MAXDCODES];
|
||||
static huffman lencode, distcode;
|
||||
|
||||
if(virgin) {
|
||||
int symbol = 0;
|
||||
short lengths[FIXLCODES];
|
||||
|
||||
lencode.count = lencnt;
|
||||
lencode.symbol = lensym;
|
||||
distcode.count = distcnt;
|
||||
distcode.symbol = distsym;
|
||||
|
||||
for(; symbol < 144; symbol++) lengths[symbol] = 8;
|
||||
for(; symbol < 256; symbol++) lengths[symbol] = 9;
|
||||
for(; symbol < 280; symbol++) lengths[symbol] = 7;
|
||||
for(; symbol < FIXLCODES; symbol++) lengths[symbol] = 8;
|
||||
construct(&lencode, lengths, FIXLCODES);
|
||||
|
||||
for(symbol = 0; symbol < MAXDCODES; symbol++) lengths[symbol] = 5;
|
||||
construct(&distcode, lengths, MAXDCODES);
|
||||
|
||||
virgin = 0;
|
||||
}
|
||||
|
||||
return codes(s, &lencode, &distcode);
|
||||
}
|
||||
|
||||
inline auto dynamic(state* s) -> int {
|
||||
int nlen, ndist, ncode, index, err;
|
||||
short lengths[MAXCODES];
|
||||
short lencnt[MAXBITS + 1], lensym[MAXLCODES];
|
||||
short distcnt[MAXBITS + 1], distsym[MAXDCODES];
|
||||
huffman lencode, distcode;
|
||||
static const short order[19] = {
|
||||
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
|
||||
};
|
||||
|
||||
lencode.count = lencnt;
|
||||
lencode.symbol = lensym;
|
||||
distcode.count = distcnt;
|
||||
distcode.symbol = distsym;
|
||||
|
||||
nlen = bits(s, 5) + 257;
|
||||
ndist = bits(s, 5) + 1;
|
||||
ncode = bits(s, 4) + 4;
|
||||
if(nlen > MAXLCODES || ndist > MAXDCODES) return -3;
|
||||
|
||||
for(index = 0; index < ncode; index++) lengths[order[index]] = bits(s, 3);
|
||||
for(; index < 19; index++) lengths[order[index]] = 0;
|
||||
|
||||
err = construct(&lencode, lengths, 19);
|
||||
if(err != 0) return -4;
|
||||
|
||||
index = 0;
|
||||
while(index < nlen + ndist) {
|
||||
int symbol, len;
|
||||
|
||||
symbol = decode(s, &lencode);
|
||||
if(symbol < 16) {
|
||||
lengths[index++] = symbol;
|
||||
} else {
|
||||
len = 0;
|
||||
if(symbol == 16) {
|
||||
if(index == 0) return -5;
|
||||
len = lengths[index - 1];
|
||||
symbol = 3 + bits(s, 2);
|
||||
} else if(symbol == 17) {
|
||||
symbol = 3 + bits(s, 3);
|
||||
} else {
|
||||
symbol = 11 + bits(s, 7);
|
||||
}
|
||||
if(index + symbol > nlen + ndist) return -6;
|
||||
while(symbol--) lengths[index++] = len;
|
||||
}
|
||||
}
|
||||
|
||||
if(lengths[256] == 0) return -9;
|
||||
|
||||
err = construct(&lencode, lengths, nlen);
|
||||
if(err < 0 || (err > 0 && nlen - lencode.count[0] != 1)) return -7;
|
||||
|
||||
err = construct(&distcode, lengths + nlen, ndist);
|
||||
if(err < 0 || (err > 0 && ndist - distcode.count[0] != 1)) return -8;
|
||||
|
||||
return codes(s, &lencode, &distcode);
|
||||
}
|
||||
|
||||
inline auto puff(
|
||||
unsigned char* dest, unsigned long* destlen,
|
||||
unsigned char* source, unsigned long* sourcelen
|
||||
) -> int {
|
||||
state s;
|
||||
int last, type, err;
|
||||
|
||||
s.out = dest;
|
||||
s.outlen = *destlen;
|
||||
s.outcnt = 0;
|
||||
|
||||
s.in = source;
|
||||
s.inlen = *sourcelen;
|
||||
s.incnt = 0;
|
||||
s.bitbuf = 0;
|
||||
s.bitcnt = 0;
|
||||
|
||||
if(setjmp(s.env) != 0) {
|
||||
err = 2;
|
||||
} else {
|
||||
do {
|
||||
last = bits(&s, 1);
|
||||
type = bits(&s, 2);
|
||||
err = type == 0 ? stored(&s)
|
||||
: type == 1 ? fixed(&s)
|
||||
: type == 2 ? dynamic(&s)
|
||||
: -1;
|
||||
if(err != 0) break;
|
||||
} while(!last);
|
||||
}
|
||||
|
||||
if(err <= 0) {
|
||||
*destlen = s.outcnt;
|
||||
*sourcelen = s.incnt;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}}
|
332
decode/png.hpp
Normal file
332
decode/png.hpp
Normal file
|
@ -0,0 +1,332 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/string.hpp>
|
||||
#include <nall/decode/inflate.hpp>
|
||||
|
||||
namespace nall { namespace Decode {
|
||||
|
||||
struct PNG {
|
||||
inline PNG();
|
||||
inline ~PNG();
|
||||
|
||||
inline auto load(const string& filename) -> bool;
|
||||
inline auto load(const uint8_t* sourceData, uint sourceSize) -> bool;
|
||||
inline auto readbits(const uint8_t*& data) -> uint;
|
||||
|
||||
struct Info {
|
||||
uint width;
|
||||
uint height;
|
||||
uint bitDepth;
|
||||
//colorType:
|
||||
//0 = L (luma)
|
||||
//2 = R,G,B
|
||||
//3 = P (palette)
|
||||
//4 = L,A
|
||||
//6 = R,G,B,A
|
||||
uint colorType;
|
||||
uint compressionMethod;
|
||||
uint filterType;
|
||||
uint interlaceMethod;
|
||||
|
||||
uint bytesPerPixel;
|
||||
uint pitch;
|
||||
|
||||
uint8_t palette[256][3];
|
||||
} info;
|
||||
|
||||
uint8_t* data = nullptr;
|
||||
uint size = 0;
|
||||
|
||||
uint bitpos = 0;
|
||||
|
||||
protected:
|
||||
enum class FourCC : uint {
|
||||
IHDR = 0x49484452,
|
||||
PLTE = 0x504c5445,
|
||||
IDAT = 0x49444154,
|
||||
IEND = 0x49454e44,
|
||||
};
|
||||
|
||||
inline auto interlace(uint pass, uint index) -> uint;
|
||||
inline auto inflateSize() -> uint;
|
||||
inline auto deinterlace(const uint8_t*& inputData, uint pass) -> bool;
|
||||
inline auto filter(uint8_t* outputData, const uint8_t* inputData, uint width, uint height) -> bool;
|
||||
inline auto read(const uint8_t* data, uint length) -> uint;
|
||||
};
|
||||
|
||||
PNG::PNG() {
|
||||
}
|
||||
|
||||
PNG::~PNG() {
|
||||
if(data) delete[] data;
|
||||
}
|
||||
|
||||
auto PNG::load(const string& filename) -> bool {
|
||||
if(auto memory = file::read(filename)) {
|
||||
return load(memory.data(), memory.size());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto PNG::load(const uint8_t* sourceData, uint sourceSize) -> bool {
|
||||
if(sourceSize < 8) return false;
|
||||
if(read(sourceData + 0, 4) != 0x89504e47) return false;
|
||||
if(read(sourceData + 4, 4) != 0x0d0a1a0a) return false;
|
||||
|
||||
uint8_t* compressedData = nullptr;
|
||||
uint compressedSize = 0;
|
||||
|
||||
uint offset = 8;
|
||||
while(offset < sourceSize) {
|
||||
uint length = read(sourceData + offset + 0, 4);
|
||||
uint fourCC = read(sourceData + offset + 4, 4);
|
||||
uint checksum = read(sourceData + offset + 8 + length, 4);
|
||||
|
||||
if(fourCC == (uint)FourCC::IHDR) {
|
||||
info.width = read(sourceData + offset + 8, 4);
|
||||
info.height = read(sourceData + offset + 12, 4);
|
||||
info.bitDepth = read(sourceData + offset + 16, 1);
|
||||
info.colorType = read(sourceData + offset + 17, 1);
|
||||
info.compressionMethod = read(sourceData + offset + 18, 1);
|
||||
info.filterType = read(sourceData + offset + 19, 1);
|
||||
info.interlaceMethod = read(sourceData + offset + 20, 1);
|
||||
|
||||
if(info.bitDepth == 0 || info.bitDepth > 16) return false;
|
||||
if(info.bitDepth & (info.bitDepth - 1)) return false; //not a power of two
|
||||
if(info.compressionMethod != 0) return false;
|
||||
if(info.filterType != 0) return false;
|
||||
if(info.interlaceMethod != 0 && info.interlaceMethod != 1) return false;
|
||||
|
||||
switch(info.colorType) {
|
||||
case 0: info.bytesPerPixel = info.bitDepth * 1; break; //L
|
||||
case 2: info.bytesPerPixel = info.bitDepth * 3; break; //R,G,B
|
||||
case 3: info.bytesPerPixel = info.bitDepth * 1; break; //P
|
||||
case 4: info.bytesPerPixel = info.bitDepth * 2; break; //L,A
|
||||
case 6: info.bytesPerPixel = info.bitDepth * 4; break; //R,G,B,A
|
||||
default: return false;
|
||||
}
|
||||
|
||||
if(info.colorType == 2 || info.colorType == 4 || info.colorType == 6) {
|
||||
if(info.bitDepth != 8 && info.bitDepth != 16) return false;
|
||||
}
|
||||
if(info.colorType == 3 && info.bitDepth == 16) return false;
|
||||
|
||||
info.bytesPerPixel = (info.bytesPerPixel + 7) / 8;
|
||||
info.pitch = (int)info.width * info.bytesPerPixel;
|
||||
}
|
||||
|
||||
if(fourCC == (uint)FourCC::PLTE) {
|
||||
if(length % 3) return false;
|
||||
for(uint n = 0, p = offset + 8; n < length / 3; n++) {
|
||||
info.palette[n][0] = sourceData[p++];
|
||||
info.palette[n][1] = sourceData[p++];
|
||||
info.palette[n][2] = sourceData[p++];
|
||||
}
|
||||
}
|
||||
|
||||
if(fourCC == (uint)FourCC::IDAT) {
|
||||
compressedData = (uint8_t*)realloc(compressedData, compressedSize + length);
|
||||
memcpy(compressedData + compressedSize, sourceData + offset + 8, length);
|
||||
compressedSize += length;
|
||||
}
|
||||
|
||||
if(fourCC == (uint)FourCC::IEND) {
|
||||
break;
|
||||
}
|
||||
|
||||
offset += 4 + 4 + length + 4;
|
||||
}
|
||||
|
||||
uint interlacedSize = inflateSize();
|
||||
auto interlacedData = new uint8_t[interlacedSize];
|
||||
|
||||
bool result = inflate(interlacedData, interlacedSize, compressedData + 2, compressedSize - 6);
|
||||
free(compressedData);
|
||||
|
||||
if(result == false) {
|
||||
delete[] interlacedData;
|
||||
return false;
|
||||
}
|
||||
|
||||
size = info.width * info.height * info.bytesPerPixel;
|
||||
data = new uint8_t[size];
|
||||
|
||||
if(info.interlaceMethod == 0) {
|
||||
if(filter(data, interlacedData, info.width, info.height) == false) {
|
||||
delete[] interlacedData;
|
||||
delete[] data;
|
||||
data = nullptr;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const uint8_t* passData = interlacedData;
|
||||
for(uint pass = 0; pass < 7; pass++) {
|
||||
if(deinterlace(passData, pass) == false) {
|
||||
delete[] interlacedData;
|
||||
delete[] data;
|
||||
data = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete[] interlacedData;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto PNG::interlace(uint pass, uint index) -> uint {
|
||||
static const uint data[7][4] = {
|
||||
//x-distance, y-distance, x-origin, y-origin
|
||||
{8, 8, 0, 0},
|
||||
{8, 8, 4, 0},
|
||||
{4, 8, 0, 4},
|
||||
{4, 4, 2, 0},
|
||||
{2, 4, 0, 2},
|
||||
{2, 2, 1, 0},
|
||||
{1, 2, 0, 1},
|
||||
};
|
||||
return data[pass][index];
|
||||
}
|
||||
|
||||
auto PNG::inflateSize() -> uint {
|
||||
if(info.interlaceMethod == 0) {
|
||||
return info.width * info.height * info.bytesPerPixel + info.height;
|
||||
}
|
||||
|
||||
uint size = 0;
|
||||
for(uint pass = 0; pass < 7; pass++) {
|
||||
uint xd = interlace(pass, 0), yd = interlace(pass, 1);
|
||||
uint xo = interlace(pass, 2), yo = interlace(pass, 3);
|
||||
uint width = (info.width + (xd - xo - 1)) / xd;
|
||||
uint height = (info.height + (yd - yo - 1)) / yd;
|
||||
if(width == 0 || height == 0) continue;
|
||||
size += width * height * info.bytesPerPixel + height;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
auto PNG::deinterlace(const uint8_t*& inputData, uint pass) -> bool {
|
||||
uint xd = interlace(pass, 0), yd = interlace(pass, 1);
|
||||
uint xo = interlace(pass, 2), yo = interlace(pass, 3);
|
||||
uint width = (info.width + (xd - xo - 1)) / xd;
|
||||
uint height = (info.height + (yd - yo - 1)) / yd;
|
||||
if(width == 0 || height == 0) return true;
|
||||
|
||||
uint outputSize = width * height * info.bytesPerPixel;
|
||||
auto outputData = new uint8_t[outputSize];
|
||||
bool result = filter(outputData, inputData, width, height);
|
||||
|
||||
const uint8_t* rd = outputData;
|
||||
for(uint y = yo; y < info.height; y += yd) {
|
||||
uint8_t* wr = data + y * info.pitch;
|
||||
for(uint x = xo; x < info.width; x += xd) {
|
||||
for(uint b = 0; b < info.bytesPerPixel; b++) {
|
||||
wr[x * info.bytesPerPixel + b] = *rd++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputData += outputSize + height;
|
||||
delete[] outputData;
|
||||
return result;
|
||||
}
|
||||
|
||||
auto PNG::filter(uint8_t* outputData, const uint8_t* inputData, uint width, uint height) -> bool {
|
||||
uint8_t* wr = outputData;
|
||||
const uint8_t* rd = inputData;
|
||||
int bpp = info.bytesPerPixel, pitch = width * bpp;
|
||||
for(int y = 0; y < height; y++) {
|
||||
uint8_t filter = *rd++;
|
||||
|
||||
switch(filter) {
|
||||
case 0x00: //None
|
||||
for(int x = 0; x < pitch; x++) {
|
||||
wr[x] = rd[x];
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x01: //Subtract
|
||||
for(int x = 0; x < pitch; x++) {
|
||||
wr[x] = rd[x] + (x - bpp < 0 ? 0 : wr[x - bpp]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x02: //Above
|
||||
for(int x = 0; x < pitch; x++) {
|
||||
wr[x] = rd[x] + (y - 1 < 0 ? 0 : wr[x - pitch]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x03: //Average
|
||||
for(int x = 0; x < pitch; x++) {
|
||||
short a = x - bpp < 0 ? 0 : wr[x - bpp];
|
||||
short b = y - 1 < 0 ? 0 : wr[x - pitch];
|
||||
|
||||
wr[x] = rd[x] + (uint8_t)((a + b) / 2);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x04: //Paeth
|
||||
for(int x = 0; x < pitch; x++) {
|
||||
short a = x - bpp < 0 ? 0 : wr[x - bpp];
|
||||
short b = y - 1 < 0 ? 0 : wr[x - pitch];
|
||||
short c = x - bpp < 0 || y - 1 < 0 ? 0 : wr[x - pitch - bpp];
|
||||
|
||||
short p = a + b - c;
|
||||
short pa = p > a ? p - a : a - p;
|
||||
short pb = p > b ? p - b : b - p;
|
||||
short pc = p > c ? p - c : c - p;
|
||||
|
||||
auto paeth = (uint8_t)((pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c);
|
||||
|
||||
wr[x] = rd[x] + paeth;
|
||||
}
|
||||
break;
|
||||
|
||||
default: //Invalid
|
||||
return false;
|
||||
}
|
||||
|
||||
rd += pitch;
|
||||
wr += pitch;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto PNG::read(const uint8_t* data, uint length) -> uint {
|
||||
uint result = 0;
|
||||
while(length--) result = (result << 8) | (*data++);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto PNG::readbits(const uint8_t*& data) -> uint {
|
||||
uint result = 0;
|
||||
switch(info.bitDepth) {
|
||||
case 1:
|
||||
result = (*data >> bitpos) & 1;
|
||||
bitpos++;
|
||||
if(bitpos == 8) { data++; bitpos = 0; }
|
||||
break;
|
||||
case 2:
|
||||
result = (*data >> bitpos) & 3;
|
||||
bitpos += 2;
|
||||
if(bitpos == 8) { data++; bitpos = 0; }
|
||||
break;
|
||||
case 4:
|
||||
result = (*data >> bitpos) & 15;
|
||||
bitpos += 4;
|
||||
if(bitpos == 8) { data++; bitpos = 0; }
|
||||
break;
|
||||
case 8:
|
||||
result = *data++;
|
||||
break;
|
||||
case 16:
|
||||
result = (data[0] << 8) | (data[1] << 0);
|
||||
data += 2;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}}
|
33
decode/url.hpp
Normal file
33
decode/url.hpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall { namespace Decode {
|
||||
|
||||
//returns empty string on malformed content
|
||||
inline auto URL(const string& input) -> string {
|
||||
string output;
|
||||
for(unsigned n = 0; n < input.size();) {
|
||||
char c = input[n];
|
||||
if(c >= 'A' && c <= 'Z') { output.append(c); n++; continue; }
|
||||
if(c >= 'a' && c <= 'z') { output.append(c); n++; continue; }
|
||||
if(c >= '0' && c <= '9') { output.append(c); n++; continue; }
|
||||
if(c == '-' || c == '_' || c == '.' || c == '~') { output.append(c); n++; continue; }
|
||||
if(c == '+') { output.append(' '); n++; continue; }
|
||||
if(c != '%' || n + 2 >= input.size()) return "";
|
||||
char hi = input[n + 1];
|
||||
char lo = input[n + 2];
|
||||
if(hi >= '0' && hi <= '9') hi -= '0';
|
||||
else if(hi >= 'A' && hi <= 'F') hi -= 'A' - 10;
|
||||
else if(hi >= 'a' && hi <= 'f') hi -= 'a' - 10;
|
||||
else return "";
|
||||
if(lo >= '0' && lo <= '9') lo -= '0';
|
||||
else if(lo >= 'A' && lo <= 'F') lo -= 'A' - 10;
|
||||
else if(lo >= 'a' && lo <= 'f') lo -= 'a' - 10;
|
||||
else return "";
|
||||
char byte = hi * 16 + lo;
|
||||
output.append(byte);
|
||||
n += 3;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
}}
|
123
decode/zip.hpp
Normal file
123
decode/zip.hpp
Normal file
|
@ -0,0 +1,123 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/filemap.hpp>
|
||||
#include <nall/string.hpp>
|
||||
#include <nall/vector.hpp>
|
||||
#include <nall/decode/inflate.hpp>
|
||||
|
||||
namespace nall { namespace Decode {
|
||||
|
||||
struct ZIP {
|
||||
struct File {
|
||||
string name;
|
||||
const uint8_t* data;
|
||||
uint size;
|
||||
uint csize;
|
||||
uint cmode; //0 = uncompressed, 8 = deflate
|
||||
uint crc32;
|
||||
};
|
||||
|
||||
~ZIP() {
|
||||
close();
|
||||
}
|
||||
|
||||
auto open(const string& filename) -> bool {
|
||||
close();
|
||||
if(fm.open(filename, filemap::mode::read) == false) return false;
|
||||
if(open(fm.data(), fm.size()) == false) {
|
||||
fm.close();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto open(const uint8_t* data, uint size) -> bool {
|
||||
if(size < 22) return false;
|
||||
|
||||
filedata = data;
|
||||
filesize = size;
|
||||
|
||||
file.reset();
|
||||
|
||||
const uint8_t* footer = data + size - 22;
|
||||
while(true) {
|
||||
if(footer <= data + 22) return false;
|
||||
if(read(footer, 4) == 0x06054b50) {
|
||||
uint commentlength = read(footer + 20, 2);
|
||||
if(footer + 22 + commentlength == data + size) break;
|
||||
}
|
||||
footer--;
|
||||
}
|
||||
const uint8_t* directory = data + read(footer + 16, 4);
|
||||
|
||||
while(true) {
|
||||
uint signature = read(directory + 0, 4);
|
||||
if(signature != 0x02014b50) break;
|
||||
|
||||
File file;
|
||||
file.cmode = read(directory + 10, 2);
|
||||
file.crc32 = read(directory + 16, 4);
|
||||
file.csize = read(directory + 20, 4);
|
||||
file.size = read(directory + 24, 4);
|
||||
|
||||
uint namelength = read(directory + 28, 2);
|
||||
uint extralength = read(directory + 30, 2);
|
||||
uint commentlength = read(directory + 32, 2);
|
||||
|
||||
char* filename = new char[namelength + 1];
|
||||
memcpy(filename, directory + 46, namelength);
|
||||
filename[namelength] = 0;
|
||||
file.name = filename;
|
||||
delete[] filename;
|
||||
|
||||
uint offset = read(directory + 42, 4);
|
||||
uint offsetNL = read(data + offset + 26, 2);
|
||||
uint offsetEL = read(data + offset + 28, 2);
|
||||
file.data = data + offset + 30 + offsetNL + offsetEL;
|
||||
|
||||
directory += 46 + namelength + extralength + commentlength;
|
||||
|
||||
this->file.append(file);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto extract(File& file) -> vector<uint8_t> {
|
||||
vector<uint8_t> buffer;
|
||||
|
||||
if(file.cmode == 0) {
|
||||
buffer.resize(file.size);
|
||||
memcpy(buffer.data(), file.data, file.size);
|
||||
}
|
||||
|
||||
if(file.cmode == 8) {
|
||||
buffer.resize(file.size);
|
||||
if(inflate(buffer.data(), buffer.size(), file.data, file.csize) == false) {
|
||||
buffer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
auto close() -> void {
|
||||
if(fm.open()) fm.close();
|
||||
}
|
||||
|
||||
protected:
|
||||
filemap fm;
|
||||
const uint8_t* filedata;
|
||||
uint filesize;
|
||||
|
||||
auto read(const uint8_t* data, uint size) -> uint {
|
||||
uint result = 0, shift = 0;
|
||||
while(size--) { result |= *data++ << shift; shift += 8; }
|
||||
return result;
|
||||
}
|
||||
|
||||
public:
|
||||
vector<File> file;
|
||||
};
|
||||
|
||||
}}
|
235
directory.hpp
Normal file
235
directory.hpp
Normal file
|
@ -0,0 +1,235 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/file.hpp>
|
||||
#include <nall/inode.hpp>
|
||||
#include <nall/intrinsics.hpp>
|
||||
#include <nall/sort.hpp>
|
||||
#include <nall/string.hpp>
|
||||
#include <nall/vector.hpp>
|
||||
|
||||
#if defined(PLATFORM_WINDOWS)
|
||||
#include <nall/windows/utf8.hpp>
|
||||
#else
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct directory : inode {
|
||||
static auto create(const string& pathname, unsigned permissions = 0755) -> bool; //recursive
|
||||
static auto remove(const string& pathname) -> bool; //recursive
|
||||
static auto exists(const string& pathname) -> bool;
|
||||
|
||||
static auto folders(const string& pathname, const string& pattern = "*") -> lstring {
|
||||
lstring folders = directory::ufolders(pathname, pattern);
|
||||
folders.sort();
|
||||
return folders;
|
||||
}
|
||||
|
||||
static auto files(const string& pathname, const string& pattern = "*") -> lstring {
|
||||
lstring files = directory::ufiles(pathname, pattern);
|
||||
files.sort();
|
||||
return files;
|
||||
}
|
||||
|
||||
static auto contents(const string& pathname, const string& pattern = "*") -> lstring {
|
||||
lstring folders = directory::ufolders(pathname); //pattern search of contents should only filter files
|
||||
lstring files = directory::ufiles(pathname, pattern);
|
||||
folders.sort();
|
||||
files.sort();
|
||||
for(auto& file : files) folders.append(file);
|
||||
return folders;
|
||||
}
|
||||
|
||||
static auto ifolders(const string& pathname, const string& pattern = "*") -> lstring {
|
||||
lstring folders = ufolders(pathname, pattern);
|
||||
folders.isort();
|
||||
return folders;
|
||||
}
|
||||
|
||||
static auto ifiles(const string& pathname, const string& pattern = "*") -> lstring {
|
||||
lstring files = ufiles(pathname, pattern);
|
||||
files.isort();
|
||||
return files;
|
||||
}
|
||||
|
||||
static auto icontents(const string& pathname, const string& pattern = "*") -> lstring {
|
||||
lstring folders = directory::ufolders(pathname); //pattern search of contents should only filter files
|
||||
lstring files = directory::ufiles(pathname, pattern);
|
||||
folders.isort();
|
||||
files.isort();
|
||||
for(auto& file : files) folders.append(file);
|
||||
return folders;
|
||||
}
|
||||
|
||||
private:
|
||||
//internal functions; these return unsorted lists
|
||||
static auto ufolders(const string& pathname, const string& pattern = "*") -> lstring;
|
||||
static auto ufiles(const string& pathname, const string& pattern = "*") -> lstring;
|
||||
};
|
||||
|
||||
#if defined(PLATFORM_WINDOWS)
|
||||
inline auto directory::create(const string& pathname, unsigned permissions) -> bool {
|
||||
string path;
|
||||
lstring list = string{pathname}.transform("\\", "/").rtrim("/").split("/");
|
||||
bool result = true;
|
||||
for(auto& part : list) {
|
||||
path.append(part, "/");
|
||||
if(directory::exists(path)) continue;
|
||||
result &= (_wmkdir(utf16_t(path)) == 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline auto directory::remove(const string& pathname) -> bool {
|
||||
lstring list = directory::contents(pathname);
|
||||
for(auto& name : list) {
|
||||
if(name.endsWith("/")) directory::remove({pathname, name});
|
||||
else file::remove({pathname, name});
|
||||
}
|
||||
return _wrmdir(utf16_t(pathname)) == 0;
|
||||
}
|
||||
|
||||
inline auto directory::exists(const string& pathname) -> bool {
|
||||
string name = pathname;
|
||||
name.trim("\"", "\"");
|
||||
DWORD result = GetFileAttributes(utf16_t(name));
|
||||
if(result == INVALID_FILE_ATTRIBUTES) return false;
|
||||
return (result & FILE_ATTRIBUTE_DIRECTORY);
|
||||
}
|
||||
|
||||
inline auto directory::ufolders(const string& pathname, const string& pattern) -> lstring {
|
||||
lstring list;
|
||||
string path = pathname;
|
||||
path.transform("/", "\\");
|
||||
if(!path.endsWith("\\")) path.append("\\");
|
||||
path.append("*");
|
||||
HANDLE handle;
|
||||
WIN32_FIND_DATA data;
|
||||
handle = FindFirstFile(utf16_t(path), &data);
|
||||
if(handle != INVALID_HANDLE_VALUE) {
|
||||
if(wcscmp(data.cFileName, L".") && wcscmp(data.cFileName, L"..")) {
|
||||
if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
string name = (const char*)utf8_t(data.cFileName);
|
||||
if(name.match(pattern)) list.append(name);
|
||||
}
|
||||
}
|
||||
while(FindNextFile(handle, &data) != false) {
|
||||
if(wcscmp(data.cFileName, L".") && wcscmp(data.cFileName, L"..")) {
|
||||
if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
string name = (const char*)utf8_t(data.cFileName);
|
||||
if(name.match(pattern)) list.append(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
FindClose(handle);
|
||||
}
|
||||
for(auto& name : list) name.append("/"); //must append after sorting
|
||||
return list;
|
||||
}
|
||||
|
||||
inline auto directory::ufiles(const string& pathname, const string& pattern) -> lstring {
|
||||
lstring list;
|
||||
string path = pathname;
|
||||
path.transform("/", "\\");
|
||||
if(!path.endsWith("\\")) path.append("\\");
|
||||
path.append("*");
|
||||
HANDLE handle;
|
||||
WIN32_FIND_DATA data;
|
||||
handle = FindFirstFile(utf16_t(path), &data);
|
||||
if(handle != INVALID_HANDLE_VALUE) {
|
||||
if((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
|
||||
string name = (const char*)utf8_t(data.cFileName);
|
||||
if(name.match(pattern)) list.append(name);
|
||||
}
|
||||
while(FindNextFile(handle, &data) != false) {
|
||||
if((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
|
||||
string name = (const char*)utf8_t(data.cFileName);
|
||||
if(name.match(pattern)) list.append(name);
|
||||
}
|
||||
}
|
||||
FindClose(handle);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
#else
|
||||
inline auto directory_is_folder(DIR* dp, struct dirent* ep) -> bool {
|
||||
if(ep->d_type == DT_DIR) return true;
|
||||
if(ep->d_type == DT_LNK || ep->d_type == DT_UNKNOWN) {
|
||||
//symbolic links must be resolved to determine type
|
||||
struct stat sp = {0};
|
||||
fstatat(dirfd(dp), ep->d_name, &sp, 0);
|
||||
return S_ISDIR(sp.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline auto directory::create(const string& pathname, unsigned permissions) -> bool {
|
||||
string path;
|
||||
lstring list = string{pathname}.rtrim("/").split("/");
|
||||
bool result = true;
|
||||
for(auto& part : list) {
|
||||
path.append(part, "/");
|
||||
if(directory::exists(path)) continue;
|
||||
result &= (mkdir(path, permissions) == 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline auto directory::remove(const string& pathname) -> bool {
|
||||
lstring list = directory::contents(pathname);
|
||||
for(auto& name : list) {
|
||||
if(name.endsWith("/")) directory::remove({pathname, name});
|
||||
else file::remove({pathname, name});
|
||||
}
|
||||
return rmdir(pathname) == 0;
|
||||
}
|
||||
|
||||
inline auto directory::exists(const string& pathname) -> bool {
|
||||
DIR* dp = opendir(pathname);
|
||||
if(!dp) return false;
|
||||
closedir(dp);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline auto directory::ufolders(const string& pathname, const string& pattern) -> lstring {
|
||||
lstring list;
|
||||
DIR* dp;
|
||||
struct dirent* ep;
|
||||
dp = opendir(pathname);
|
||||
if(dp) {
|
||||
while(ep = readdir(dp)) {
|
||||
if(!strcmp(ep->d_name, ".")) continue;
|
||||
if(!strcmp(ep->d_name, "..")) continue;
|
||||
if(!directory_is_folder(dp, ep)) continue;
|
||||
string name{ep->d_name};
|
||||
if(name.match(pattern)) list.append(std::move(name));
|
||||
}
|
||||
closedir(dp);
|
||||
}
|
||||
for(auto& name : list) name.append("/"); //must append after sorting
|
||||
return list;
|
||||
}
|
||||
|
||||
inline auto directory::ufiles(const string& pathname, const string& pattern) -> lstring {
|
||||
lstring list;
|
||||
DIR* dp;
|
||||
struct dirent* ep;
|
||||
dp = opendir(pathname);
|
||||
if(dp) {
|
||||
while(ep = readdir(dp)) {
|
||||
if(!strcmp(ep->d_name, ".")) continue;
|
||||
if(!strcmp(ep->d_name, "..")) continue;
|
||||
if(directory_is_folder(dp, ep)) continue;
|
||||
string name{ep->d_name};
|
||||
if(name.match(pattern)) list.append(std::move(name));
|
||||
}
|
||||
closedir(dp);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
126
dl.hpp
Normal file
126
dl.hpp
Normal file
|
@ -0,0 +1,126 @@
|
|||
#pragma once
|
||||
|
||||
//dynamic linking support
|
||||
|
||||
#include <nall/intrinsics.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/string.hpp>
|
||||
#include <nall/utility.hpp>
|
||||
|
||||
#if defined(PLATFORM_WINDOWS)
|
||||
#include <windows.h>
|
||||
#include <nall/windows/utf8.hpp>
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct library {
|
||||
library() = default;
|
||||
~library() { close(); }
|
||||
|
||||
library& operator=(const library&) = delete;
|
||||
library(const library&) = delete;
|
||||
|
||||
explicit operator bool() const { return open(); }
|
||||
auto open() const -> bool { return handle; }
|
||||
auto open(const string&, const string& = "") -> bool;
|
||||
auto openAbsolute(const string&) -> bool;
|
||||
auto sym(const string&) -> void*;
|
||||
auto close() -> void;
|
||||
|
||||
private:
|
||||
uintptr_t handle = 0;
|
||||
};
|
||||
|
||||
#if defined(PLATFORM_LINUX) || defined(PLATFORM_BSD)
|
||||
inline auto library::open(const string& name, const string& path) -> bool {
|
||||
if(handle) close();
|
||||
if(path) handle = (uintptr_t)dlopen(string(path, "lib", name, ".so"), RTLD_LAZY);
|
||||
if(!handle) handle = (uintptr_t)dlopen(string(userpath(), ".local/lib/lib", name, ".so"), RTLD_LAZY);
|
||||
if(!handle) handle = (uintptr_t)dlopen(string("/usr/local/lib/lib", name, ".so"), RTLD_LAZY);
|
||||
if(!handle) handle = (uintptr_t)dlopen(string("lib", name, ".so"), RTLD_LAZY);
|
||||
return handle;
|
||||
}
|
||||
|
||||
inline auto library::openAbsolute(const string& name) -> bool {
|
||||
if(handle) close();
|
||||
handle = (uintptr_t)dlopen(name, RTLD_LAZY);
|
||||
return handle;
|
||||
}
|
||||
|
||||
inline auto library::sym(const string& name) -> void* {
|
||||
if(!handle) return nullptr;
|
||||
return dlsym((void*)handle, name);
|
||||
}
|
||||
|
||||
inline auto library::close() -> void {
|
||||
if(!handle) return;
|
||||
dlclose((void*)handle);
|
||||
handle = 0;
|
||||
}
|
||||
#elif defined(PLATFORM_MACOSX)
|
||||
inline auto library::open(const string& name, const string& path) -> bool {
|
||||
if(handle) close();
|
||||
if(path) handle = (uintptr_t)dlopen(string(path, "lib", name, ".dylib"), RTLD_LAZY);
|
||||
if(!handle) handle = (uintptr_t)dlopen(string(userpath(), ".local/lib/lib", name, ".dylib"), RTLD_LAZY);
|
||||
if(!handle) handle = (uintptr_t)dlopen(string("/usr/local/lib/lib", name, ".dylib"), RTLD_LAZY);
|
||||
if(!handle) handle = (uintptr_t)dlopen(string("lib", name, ".dylib"), RTLD_LAZY);
|
||||
return handle;
|
||||
}
|
||||
|
||||
inline auto library::openAbsolute(const string& name) -> bool {
|
||||
if(handle) close();
|
||||
handle = (uintptr_t)dlopen(name, RTLD_LAZY);
|
||||
return handle;
|
||||
}
|
||||
|
||||
inline auto library::sym(const string& name) -> void* {
|
||||
if(!handle) return nullptr;
|
||||
return dlsym((void*)handle, name);
|
||||
}
|
||||
|
||||
inline auto library::close() -> void {
|
||||
if(!handle) return;
|
||||
dlclose((void*)handle);
|
||||
handle = 0;
|
||||
}
|
||||
#elif defined(PLATFORM_WINDOWS)
|
||||
inline auto library::open(const string& name, const string& path) -> bool {
|
||||
if(handle) close();
|
||||
if(path) {
|
||||
string filepath = {path, name, ".dll"};
|
||||
handle = (uintptr_t)LoadLibraryW(utf16_t(filepath));
|
||||
}
|
||||
if(!handle) {
|
||||
string filepath = {name, ".dll"};
|
||||
handle = (uintptr_t)LoadLibraryW(utf16_t(filepath));
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
inline auto library::openAbsolute(const string& name) -> bool {
|
||||
if(handle) close();
|
||||
handle = (uintptr_t)LoadLibraryW(utf16_t(name));
|
||||
return handle;
|
||||
}
|
||||
|
||||
inline auto library::sym(const string& name) -> void* {
|
||||
if(!handle) return nullptr;
|
||||
return (void*)GetProcAddress((HMODULE)handle, name);
|
||||
}
|
||||
|
||||
inline auto library::close() -> void {
|
||||
if(!handle) return;
|
||||
FreeLibrary((HMODULE)handle);
|
||||
handle = 0;
|
||||
}
|
||||
#else
|
||||
inline auto library::open(const string&, const string&) -> bool { return false; }
|
||||
inline auto library::openAbsolute(const string&) -> bool { return false; }
|
||||
inline auto library::sym(const string&) -> void* { return nullptr; }
|
||||
inline auto library::close() -> void {}
|
||||
#endif
|
||||
|
||||
}
|
10
dsp.hpp
Normal file
10
dsp.hpp
Normal file
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/bit.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#ifdef __SSE__
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
#include <nall/dsp/core.hpp>
|
50
dsp/buffer.hpp
Normal file
50
dsp/buffer.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
struct Buffer {
|
||||
Buffer() {
|
||||
}
|
||||
|
||||
~Buffer() {
|
||||
setChannels(0);
|
||||
}
|
||||
|
||||
auto setChannels(uint channels) -> void {
|
||||
if(sample) {
|
||||
for(auto c : range(this->channels)) {
|
||||
if(sample[c]) delete[] sample[c];
|
||||
}
|
||||
delete[] sample;
|
||||
}
|
||||
|
||||
this->channels = channels;
|
||||
if(channels == 0) return;
|
||||
|
||||
sample = new double*[channels];
|
||||
for(auto c : range(channels)) {
|
||||
sample[c] = new double[65536]();
|
||||
}
|
||||
}
|
||||
|
||||
inline auto read(uint channel, int offset = 0) -> double& {
|
||||
return sample[channel][(uint16_t)(rdoffset + offset)];
|
||||
}
|
||||
|
||||
inline auto write(uint channel, int offset = 0) -> double& {
|
||||
return sample[channel][(uint16_t)(wroffset + offset)];
|
||||
}
|
||||
|
||||
inline auto clear() -> void {
|
||||
for(auto c : range(channels)) {
|
||||
for(auto n : range(65536)) {
|
||||
sample[c][n] = 0;
|
||||
}
|
||||
}
|
||||
rdoffset = 0;
|
||||
wroffset = 0;
|
||||
}
|
||||
|
||||
double** sample = nullptr;
|
||||
uint16_t rdoffset = 0;
|
||||
uint16_t wroffset = 0;
|
||||
uint channels = 0;
|
||||
};
|
164
dsp/core.hpp
Normal file
164
dsp/core.hpp
Normal file
|
@ -0,0 +1,164 @@
|
|||
#pragma once
|
||||
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
#include <nall/stdint.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct DSP;
|
||||
|
||||
struct Resampler {
|
||||
Resampler(DSP& dsp) : dsp(dsp) {}
|
||||
virtual ~Resampler() {}
|
||||
|
||||
virtual auto setFrequency() -> void = 0;
|
||||
virtual auto clear() -> void = 0;
|
||||
virtual auto sample() -> void = 0;
|
||||
|
||||
DSP& dsp;
|
||||
double frequency = 44100.0;
|
||||
};
|
||||
|
||||
struct DSP {
|
||||
enum class ResampleEngine : uint {
|
||||
Nearest,
|
||||
Linear,
|
||||
Cosine,
|
||||
Cubic,
|
||||
Hermite,
|
||||
Average,
|
||||
Sinc,
|
||||
};
|
||||
|
||||
inline DSP();
|
||||
inline ~DSP();
|
||||
|
||||
inline auto setChannels(uint channels) -> void;
|
||||
inline auto setPrecision(uint precision) -> void;
|
||||
inline auto setFrequency(double frequency) -> void; //inputFrequency
|
||||
inline auto setVolume(double volume) -> void;
|
||||
inline auto setBalance(double balance) -> void;
|
||||
|
||||
inline auto setResampler(ResampleEngine resamplingEngine) -> void;
|
||||
inline auto setResamplerFrequency(double frequency) -> void; //outputFrequency
|
||||
|
||||
inline auto sample(int channel[]) -> void;
|
||||
inline auto pending() const -> bool;
|
||||
inline auto read(int channel[]) -> void;
|
||||
|
||||
inline auto clear() -> void;
|
||||
|
||||
protected:
|
||||
inline auto write(double channel[]) -> void;
|
||||
inline auto adjustVolume() -> void;
|
||||
inline auto adjustBalance() -> void;
|
||||
inline auto clamp(const uint bits, const int input) -> int;
|
||||
|
||||
struct Settings {
|
||||
uint channels;
|
||||
uint precision;
|
||||
double frequency;
|
||||
double volume;
|
||||
double balance;
|
||||
|
||||
//internal
|
||||
double intensity;
|
||||
double intensityInverse;
|
||||
} settings;
|
||||
|
||||
Resampler* resampler = nullptr;
|
||||
|
||||
#include "buffer.hpp"
|
||||
Buffer buffer;
|
||||
Buffer output;
|
||||
|
||||
friend class ResampleNearest;
|
||||
friend class ResampleLinear;
|
||||
friend class ResampleCosine;
|
||||
friend class ResampleCubic;
|
||||
friend class ResampleAverage;
|
||||
friend class ResampleHermite;
|
||||
friend class ResampleSinc;
|
||||
};
|
||||
|
||||
#include "resample/nearest.hpp"
|
||||
#include "resample/linear.hpp"
|
||||
#include "resample/cosine.hpp"
|
||||
#include "resample/cubic.hpp"
|
||||
#include "resample/hermite.hpp"
|
||||
#include "resample/average.hpp"
|
||||
#include "resample/sinc.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
DSP::DSP() {
|
||||
setResampler(ResampleEngine::Hermite);
|
||||
setResamplerFrequency(44100.0);
|
||||
|
||||
setChannels(2);
|
||||
setPrecision(16);
|
||||
setFrequency(44100.0);
|
||||
setVolume(1.0);
|
||||
setBalance(0.0);
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
DSP::~DSP() {
|
||||
if(resampler) delete resampler;
|
||||
}
|
||||
|
||||
auto DSP::sample(int channel[]) -> void {
|
||||
for(auto c : range(settings.channels)) {
|
||||
buffer.write(c) = (double)channel[c] * settings.intensityInverse;
|
||||
}
|
||||
buffer.wroffset++;
|
||||
resampler->sample();
|
||||
}
|
||||
|
||||
auto DSP::pending() const -> bool {
|
||||
return output.rdoffset != output.wroffset;
|
||||
}
|
||||
|
||||
auto DSP::read(int channel[]) -> void {
|
||||
adjustVolume();
|
||||
adjustBalance();
|
||||
|
||||
for(auto c : range(settings.channels)) {
|
||||
channel[c] = clamp(settings.precision, output.read(c) * settings.intensity);
|
||||
}
|
||||
output.rdoffset++;
|
||||
}
|
||||
|
||||
auto DSP::write(double channel[]) -> void {
|
||||
for(auto c : range(settings.channels)) {
|
||||
output.write(c) = channel[c];
|
||||
}
|
||||
output.wroffset++;
|
||||
}
|
||||
|
||||
auto DSP::adjustVolume() -> void {
|
||||
for(auto c : range(settings.channels)) {
|
||||
output.read(c) *= settings.volume;
|
||||
}
|
||||
}
|
||||
|
||||
auto DSP::adjustBalance() -> void {
|
||||
if(settings.channels != 2) return; //TODO: support > 2 channels
|
||||
if(settings.balance < 0.0) output.read(1) *= 1.0 + settings.balance;
|
||||
if(settings.balance > 0.0) output.read(0) *= 1.0 - settings.balance;
|
||||
}
|
||||
|
||||
auto DSP::clamp(const uint bits, const int x) -> int {
|
||||
const int b = 1U << (bits - 1);
|
||||
const int m = (1U << (bits - 1)) - 1;
|
||||
return (x > m) ? m : (x < -b) ? -b : x;
|
||||
}
|
||||
|
||||
auto DSP::clear() -> void {
|
||||
buffer.clear();
|
||||
output.clear();
|
||||
resampler->clear();
|
||||
}
|
||||
|
||||
}
|
72
dsp/resample/average.hpp
Normal file
72
dsp/resample/average.hpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
#pragma once
|
||||
|
||||
struct ResampleAverage : Resampler {
|
||||
ResampleAverage(DSP& dsp) : Resampler(dsp) {}
|
||||
|
||||
inline auto setFrequency() -> void;
|
||||
inline auto clear() -> void;
|
||||
inline auto sample() -> void;
|
||||
inline auto sampleLinear() -> void;
|
||||
|
||||
private:
|
||||
double fraction;
|
||||
double step;
|
||||
};
|
||||
|
||||
auto ResampleAverage::setFrequency() -> void {
|
||||
fraction = 0.0;
|
||||
step = dsp.settings.frequency / frequency;
|
||||
}
|
||||
|
||||
auto ResampleAverage::clear() -> void {
|
||||
fraction = 0.0;
|
||||
}
|
||||
|
||||
auto ResampleAverage::sample() -> void {
|
||||
//can only average if input frequency >= output frequency
|
||||
if(step < 1.0) return sampleLinear();
|
||||
|
||||
fraction += 1.0;
|
||||
|
||||
double scalar = 1.0;
|
||||
if(fraction > step) scalar = 1.0 - (fraction - step);
|
||||
|
||||
for(auto c : range(dsp.settings.channels)) {
|
||||
dsp.output.write(c) += dsp.buffer.read(c) * scalar;
|
||||
}
|
||||
|
||||
if(fraction >= step) {
|
||||
for(auto c : range(dsp.settings.channels)) {
|
||||
dsp.output.write(c) /= step;
|
||||
}
|
||||
dsp.output.wroffset++;
|
||||
|
||||
fraction -= step;
|
||||
for(auto c : range(dsp.settings.channels)) {
|
||||
dsp.output.write(c) = dsp.buffer.read(c) * fraction;
|
||||
}
|
||||
}
|
||||
|
||||
dsp.buffer.rdoffset++;
|
||||
}
|
||||
|
||||
auto ResampleAverage::sampleLinear() -> void {
|
||||
while(fraction <= 1.0) {
|
||||
double channel[dsp.settings.channels];
|
||||
|
||||
for(auto n : range(dsp.settings.channels)) {
|
||||
double a = dsp.buffer.read(n, -1);
|
||||
double b = dsp.buffer.read(n, -0);
|
||||
|
||||
double mu = fraction;
|
||||
|
||||
channel[n] = a * (1.0 - mu) + b * mu;
|
||||
}
|
||||
|
||||
dsp.write(channel);
|
||||
fraction += step;
|
||||
}
|
||||
|
||||
dsp.buffer.rdoffset++;
|
||||
fraction -= 1.0;
|
||||
}
|
44
dsp/resample/cosine.hpp
Normal file
44
dsp/resample/cosine.hpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
struct ResampleCosine : Resampler {
|
||||
ResampleCosine(DSP& dsp) : Resampler(dsp) {}
|
||||
|
||||
inline auto setFrequency() -> void;
|
||||
inline auto clear() -> void;
|
||||
inline auto sample() -> void;
|
||||
|
||||
private:
|
||||
double fraction;
|
||||
double step;
|
||||
};
|
||||
|
||||
auto ResampleCosine::setFrequency() -> void {
|
||||
fraction = 0.0;
|
||||
step = dsp.settings.frequency / frequency;
|
||||
}
|
||||
|
||||
auto ResampleCosine::clear() -> void {
|
||||
fraction = 0.0;
|
||||
}
|
||||
|
||||
auto ResampleCosine::sample() -> void {
|
||||
while(fraction <= 1.0) {
|
||||
double channel[dsp.settings.channels];
|
||||
|
||||
for(auto n : range(dsp.settings.channels)) {
|
||||
double a = dsp.buffer.read(n, -1);
|
||||
double b = dsp.buffer.read(n, -0);
|
||||
|
||||
double mu = fraction;
|
||||
mu = (1.0 - cos(mu * 3.14159265)) / 2.0;
|
||||
|
||||
channel[n] = a * (1.0 - mu) + b * mu;
|
||||
}
|
||||
|
||||
dsp.write(channel);
|
||||
fraction += step;
|
||||
}
|
||||
|
||||
dsp.buffer.rdoffset++;
|
||||
fraction -= 1.0;
|
||||
}
|
50
dsp/resample/cubic.hpp
Normal file
50
dsp/resample/cubic.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
struct ResampleCubic : Resampler {
|
||||
ResampleCubic(DSP& dsp) : Resampler(dsp) {}
|
||||
|
||||
inline auto setFrequency() -> void;
|
||||
inline auto clear() -> void;
|
||||
inline auto sample() -> void;
|
||||
|
||||
private:
|
||||
double fraction;
|
||||
double step;
|
||||
};
|
||||
|
||||
auto ResampleCubic::setFrequency() -> void {
|
||||
fraction = 0.0;
|
||||
step = dsp.settings.frequency / frequency;
|
||||
}
|
||||
|
||||
auto ResampleCubic::clear() -> void {
|
||||
fraction = 0.0;
|
||||
}
|
||||
|
||||
auto ResampleCubic::sample() -> void {
|
||||
while(fraction <= 1.0) {
|
||||
double channel[dsp.settings.channels];
|
||||
|
||||
for(auto n : range(dsp.settings.channels)) {
|
||||
double a = dsp.buffer.read(n, -3);
|
||||
double b = dsp.buffer.read(n, -2);
|
||||
double c = dsp.buffer.read(n, -1);
|
||||
double d = dsp.buffer.read(n, -0);
|
||||
|
||||
double mu = fraction;
|
||||
|
||||
double A = d - c - a + b;
|
||||
double B = a - b - A;
|
||||
double C = c - a;
|
||||
double D = b;
|
||||
|
||||
channel[n] = A * (mu * 3) + B * (mu * 2) + C * mu + D;
|
||||
}
|
||||
|
||||
dsp.write(channel);
|
||||
fraction += step;
|
||||
}
|
||||
|
||||
dsp.buffer.rdoffset++;
|
||||
fraction -= 1.0;
|
||||
}
|
62
dsp/resample/hermite.hpp
Normal file
62
dsp/resample/hermite.hpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
struct ResampleHermite : Resampler {
|
||||
ResampleHermite(DSP& dsp) : Resampler(dsp) {}
|
||||
|
||||
inline auto setFrequency() -> void;
|
||||
inline auto clear() -> void;
|
||||
inline auto sample() -> void;
|
||||
|
||||
private:
|
||||
double fraction;
|
||||
double step;
|
||||
};
|
||||
|
||||
auto ResampleHermite::setFrequency() -> void {
|
||||
fraction = 0.0;
|
||||
step = dsp.settings.frequency / frequency;
|
||||
}
|
||||
|
||||
auto ResampleHermite::clear() -> void {
|
||||
fraction = 0.0;
|
||||
}
|
||||
|
||||
auto ResampleHermite::sample() -> void {
|
||||
while(fraction <= 1.0) {
|
||||
double channel[dsp.settings.channels];
|
||||
|
||||
for(auto n : range(dsp.settings.channels)) {
|
||||
double a = dsp.buffer.read(n, -3);
|
||||
double b = dsp.buffer.read(n, -2);
|
||||
double c = dsp.buffer.read(n, -1);
|
||||
double d = dsp.buffer.read(n, -0);
|
||||
|
||||
const double tension = 0.0; //-1 = low, 0 = normal, +1 = high
|
||||
const double bias = 0.0; //-1 = left, 0 = even, +1 = right
|
||||
|
||||
double mu1, mu2, mu3, m0, m1, a0, a1, a2, a3;
|
||||
|
||||
mu1 = fraction;
|
||||
mu2 = mu1 * mu1;
|
||||
mu3 = mu2 * mu1;
|
||||
|
||||
m0 = (b - a) * (1.0 + bias) * (1.0 - tension) / 2.0;
|
||||
m0 += (c - b) * (1.0 - bias) * (1.0 - tension) / 2.0;
|
||||
m1 = (c - b) * (1.0 + bias) * (1.0 - tension) / 2.0;
|
||||
m1 += (d - c) * (1.0 - bias) * (1.0 - tension) / 2.0;
|
||||
|
||||
a0 = +2 * mu3 - 3 * mu2 + 1;
|
||||
a1 = mu3 - 2 * mu2 + mu1;
|
||||
a2 = mu3 - mu2;
|
||||
a3 = -2 * mu3 + 3 * mu2;
|
||||
|
||||
channel[n] = (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c);
|
||||
}
|
||||
|
||||
dsp.write(channel);
|
||||
fraction += step;
|
||||
}
|
||||
|
||||
dsp.buffer.rdoffset++;
|
||||
fraction -= 1.0;
|
||||
}
|
600
dsp/resample/lib/sinc.hpp
Normal file
600
dsp/resample/lib/sinc.hpp
Normal file
|
@ -0,0 +1,600 @@
|
|||
// If these types are changed to anything other than "float", you should comment out the SSE detection directives below
|
||||
// so that the SSE code is not used.
|
||||
|
||||
typedef float resample_coeff_t; // note: sizeof(resample_coeff_t) must be == to a power of 2, and not larger than 16
|
||||
typedef float resample_samp_t;
|
||||
|
||||
|
||||
// ...but don't comment this single RESAMPLE_SSEREGPARM define out when disabling SSE.
|
||||
#define RESAMPLE_SSEREGPARM
|
||||
|
||||
#if defined(__SSE__)
|
||||
#define SINCRESAMPLE_USE_SSE 1
|
||||
#ifndef __x86_64__
|
||||
#undef RESAMPLE_SSEREGPARM
|
||||
#define RESAMPLE_SSEREGPARM __attribute__((sseregparm))
|
||||
#endif
|
||||
#else
|
||||
// TODO: altivec here
|
||||
#endif
|
||||
|
||||
namespace ResampleUtility
|
||||
{
|
||||
inline void kaiser_window(double* io, int count, double beta);
|
||||
inline void gen_sinc(double* out, int size, double cutoff, double kaiser);
|
||||
inline void gen_sinc_os(double* out, int size, double cutoff, double kaiser);
|
||||
inline void normalize(double* io, int size, double gain = 1.0);
|
||||
|
||||
inline void* make_aligned(void* ptr, unsigned boundary); // boundary must be a power of 2
|
||||
}
|
||||
|
||||
class SincResampleHR
|
||||
{
|
||||
private:
|
||||
|
||||
inline void Init(unsigned ratio_arg, double desired_bandwidth, double beta, double d);
|
||||
|
||||
inline void write(resample_samp_t sample) RESAMPLE_SSEREGPARM;
|
||||
inline resample_samp_t read(void) RESAMPLE_SSEREGPARM;
|
||||
inline bool output_avail(void);
|
||||
|
||||
private:
|
||||
|
||||
inline resample_samp_t mac(const resample_samp_t *wave, const resample_coeff_t *coeff, unsigned count);
|
||||
|
||||
unsigned ratio;
|
||||
unsigned num_convolutions;
|
||||
|
||||
resample_coeff_t *coeffs;
|
||||
std::vector<unsigned char> coeffs_mem;
|
||||
|
||||
// second half of ringbuffer should be copy of first half.
|
||||
resample_samp_t *rb;
|
||||
std::vector<unsigned char> rb_mem;
|
||||
|
||||
signed rb_readpos;
|
||||
signed rb_writepos;
|
||||
signed rb_in;
|
||||
signed rb_eff_size;
|
||||
|
||||
friend class SincResample;
|
||||
};
|
||||
|
||||
class SincResample
|
||||
{
|
||||
public:
|
||||
|
||||
enum
|
||||
{
|
||||
QUALITY_LOW = 0,
|
||||
QUALITY_MEDIUM = 2,
|
||||
QUALITY_HIGH = 4
|
||||
};
|
||||
|
||||
inline SincResample(double input_rate, double output_rate, double desired_bandwidth, unsigned quality = QUALITY_HIGH);
|
||||
|
||||
inline void write(resample_samp_t sample) RESAMPLE_SSEREGPARM;
|
||||
inline resample_samp_t read(void) RESAMPLE_SSEREGPARM;
|
||||
inline bool output_avail(void);
|
||||
|
||||
private:
|
||||
|
||||
inline void Init(double input_rate, double output_rate, double desired_bandwidth, double beta, double d, unsigned pn_nume, unsigned phases_min);
|
||||
|
||||
inline resample_samp_t mac(const resample_samp_t *wave, const resample_coeff_t *coeffs_a, const resample_coeff_t *coeffs_b, const double ffract, unsigned count) RESAMPLE_SSEREGPARM;
|
||||
|
||||
unsigned num_convolutions;
|
||||
unsigned num_phases;
|
||||
|
||||
unsigned step_int;
|
||||
double step_fract;
|
||||
|
||||
double input_pos_fract;
|
||||
|
||||
|
||||
std::vector<resample_coeff_t *> coeffs; // Pointers into coeff_mem.
|
||||
std::vector<unsigned char> coeff_mem;
|
||||
|
||||
|
||||
std::vector<resample_samp_t> rb; // second half should be copy of first half.
|
||||
signed rb_readpos;
|
||||
signed rb_writepos;
|
||||
signed rb_in;
|
||||
|
||||
bool hr_used;
|
||||
SincResampleHR hr;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Code:
|
||||
//
|
||||
//#include "resample.hpp"
|
||||
|
||||
#if 0
|
||||
namespace bit
|
||||
{
|
||||
inline unsigned round(unsigned x) {
|
||||
if((x & (x - 1)) == 0) return x;
|
||||
while(x & (x - 1)) x &= x - 1;
|
||||
return x << 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void SincResampleHR::Init(unsigned ratio_arg, double desired_bandwidth, double beta, double d)
|
||||
{
|
||||
const unsigned align_boundary = 16;
|
||||
std::vector<double> coeffs_tmp;
|
||||
double cutoff; // 1.0 = f/2
|
||||
|
||||
ratio = ratio_arg;
|
||||
|
||||
//num_convolutions = ((unsigned)ceil(d / ((1.0 - desired_bandwidth) / ratio)) + 1) &~ 1; // round up to be even
|
||||
num_convolutions = ((unsigned)ceil(d / ((1.0 - desired_bandwidth) / ratio)) | 1);
|
||||
|
||||
cutoff = (1.0 / ratio) - (d / num_convolutions);
|
||||
|
||||
//printf("%d %d %.20f\n", ratio, num_convolutions, cutoff);
|
||||
assert(num_convolutions > ratio);
|
||||
|
||||
|
||||
// Generate windowed sinc of POWER
|
||||
coeffs_tmp.resize(num_convolutions);
|
||||
//ResampleUtility::gen_sinc(&coeffs_tmp[0], num_convolutions, cutoff, beta);
|
||||
ResampleUtility::gen_sinc_os(&coeffs_tmp[0], num_convolutions, cutoff, beta);
|
||||
ResampleUtility::normalize(&coeffs_tmp[0], num_convolutions);
|
||||
|
||||
// Copy from coeffs_tmp to coeffs~
|
||||
// We multiply many coefficients at a time in the mac loop, so make sure the last few that don't really
|
||||
// exist are allocated, zero'd mem.
|
||||
|
||||
coeffs_mem.resize(((num_convolutions + 7) &~ 7) * sizeof(resample_coeff_t) + (align_boundary - 1));
|
||||
coeffs = (resample_coeff_t *)ResampleUtility::make_aligned(&coeffs_mem[0], align_boundary);
|
||||
|
||||
|
||||
for(unsigned i = 0; i < num_convolutions; i++)
|
||||
coeffs[i] = coeffs_tmp[i];
|
||||
|
||||
rb_eff_size = nall::bit::round(num_convolutions * 2) >> 1;
|
||||
rb_readpos = 0;
|
||||
rb_writepos = 0;
|
||||
rb_in = 0;
|
||||
|
||||
rb_mem.resize(rb_eff_size * 2 * sizeof(resample_samp_t) + (align_boundary - 1));
|
||||
rb = (resample_samp_t *)ResampleUtility::make_aligned(&rb_mem[0], align_boundary);
|
||||
}
|
||||
|
||||
|
||||
inline bool SincResampleHR::output_avail(void)
|
||||
{
|
||||
return(rb_in >= (signed)num_convolutions);
|
||||
}
|
||||
|
||||
inline void SincResampleHR::write(resample_samp_t sample)
|
||||
{
|
||||
assert(!output_avail());
|
||||
|
||||
rb[rb_writepos] = sample;
|
||||
rb[rb_writepos + rb_eff_size] = sample;
|
||||
rb_writepos = (rb_writepos + 1) & (rb_eff_size - 1);
|
||||
rb_in++;
|
||||
}
|
||||
|
||||
resample_samp_t SincResampleHR::mac(const resample_samp_t *wave, const resample_coeff_t *coeff, unsigned count)
|
||||
{
|
||||
#if SINCRESAMPLE_USE_SSE
|
||||
__m128 accum_veca[2] = { _mm_set1_ps(0), _mm_set1_ps(0) };
|
||||
|
||||
resample_samp_t accum;
|
||||
|
||||
for(unsigned c = 0; c < count; c += 8)
|
||||
{
|
||||
for(unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
__m128 co[2];
|
||||
__m128 w[2];
|
||||
|
||||
co[i] = _mm_load_ps(&coeff[c + i * 4]);
|
||||
w[i] = _mm_load_ps(&wave[c + i * 4]);
|
||||
|
||||
w[i] = _mm_mul_ps(w[i], co[i]);
|
||||
|
||||
accum_veca[i] = _mm_add_ps(w[i], accum_veca[i]);
|
||||
}
|
||||
}
|
||||
|
||||
__m128 accum_vec = _mm_add_ps(accum_veca[0], accum_veca[1]); //_mm_add_ps(_mm_add_ps(accum_veca[0], accum_veca[1]), _mm_add_ps(accum_veca[2], accum_veca[3]));
|
||||
|
||||
accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (3 << 0) | (2 << 2) | (1 << 4) | (0 << 6)));
|
||||
accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (1 << 0) | (0 << 2) | (1 << 4) | (0 << 6)));
|
||||
|
||||
_mm_store_ss(&accum, accum_vec);
|
||||
|
||||
return accum;
|
||||
#else
|
||||
resample_samp_t accum[4] = { 0, 0, 0, 0 };
|
||||
|
||||
for(unsigned c = 0; c < count; c+= 4)
|
||||
{
|
||||
accum[0] += wave[c + 0] * coeff[c + 0];
|
||||
accum[1] += wave[c + 1] * coeff[c + 1];
|
||||
accum[2] += wave[c + 2] * coeff[c + 2];
|
||||
accum[3] += wave[c + 3] * coeff[c + 3];
|
||||
}
|
||||
|
||||
return (accum[0] + accum[1]) + (accum[2] + accum[3]); // don't mess with parentheses(assuming compiler doesn't already, which it may...
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
resample_samp_t SincResampleHR::read(void)
|
||||
{
|
||||
assert(output_avail());
|
||||
resample_samp_t ret;
|
||||
|
||||
ret = mac(&rb[rb_readpos], &coeffs[0], num_convolutions);
|
||||
|
||||
rb_readpos = (rb_readpos + ratio) & (rb_eff_size - 1);
|
||||
rb_in -= ratio;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
SincResample::SincResample(double input_rate, double output_rate, double desired_bandwidth, unsigned quality)
|
||||
{
|
||||
const struct
|
||||
{
|
||||
double beta;
|
||||
double d;
|
||||
unsigned pn_nume;
|
||||
unsigned phases_min;
|
||||
} qtab[5] =
|
||||
{
|
||||
{ 5.658, 3.62, 4096, 4 },
|
||||
{ 6.764, 4.32, 8192, 4 },
|
||||
{ 7.865, 5.0, 16384, 8 },
|
||||
{ 8.960, 5.7, 32768, 16 },
|
||||
{ 10.056, 6.4, 65536, 32 }
|
||||
};
|
||||
|
||||
// Sanity checks
|
||||
assert(ceil(input_rate) > 0);
|
||||
assert(ceil(output_rate) > 0);
|
||||
assert(ceil(input_rate / output_rate) <= 1024);
|
||||
assert(ceil(output_rate / input_rate) <= 1024);
|
||||
|
||||
// The simplistic number-of-phases calculation code doesn't work well enough for when desired_bandwidth is close to 1.0 and when
|
||||
// upsampling.
|
||||
assert(desired_bandwidth >= 0.25 && desired_bandwidth < 0.96);
|
||||
assert(quality >= 0 && quality <= 4);
|
||||
|
||||
hr_used = false;
|
||||
|
||||
#if 1
|
||||
// Round down to the nearest multiple of 4(so wave buffer remains aligned)
|
||||
// It also adjusts the effective intermediate sampling rate up slightly, so that the upper frequencies below f/2
|
||||
// aren't overly attenuated so much. In the future, we might want to do an FFT or something to choose the intermediate rate more accurately
|
||||
// to virtually eliminate over-attenuation.
|
||||
unsigned ioratio_rd = (unsigned)floor(input_rate / (output_rate * (1.0 + (1.0 - desired_bandwidth) / 2) )) & ~3;
|
||||
|
||||
if(ioratio_rd >= 8)
|
||||
{
|
||||
hr.Init(ioratio_rd, desired_bandwidth, qtab[quality].beta, qtab[quality].d); //10.056, 6.4);
|
||||
hr_used = true;
|
||||
|
||||
input_rate /= ioratio_rd;
|
||||
}
|
||||
#endif
|
||||
|
||||
Init(input_rate, output_rate, desired_bandwidth, qtab[quality].beta, qtab[quality].d, qtab[quality].pn_nume, qtab[quality].phases_min);
|
||||
}
|
||||
|
||||
void SincResample::Init(double input_rate, double output_rate, double desired_bandwidth, double beta, double d, unsigned pn_nume, unsigned phases_min)
|
||||
{
|
||||
const unsigned max_mult_atatime = 8; // multiply "granularity". must be power of 2.
|
||||
const unsigned max_mult_minus1 = (max_mult_atatime - 1);
|
||||
const unsigned conv_alignment_bytes = 16; // must be power of 2
|
||||
const double input_to_output_ratio = input_rate / output_rate;
|
||||
const double output_to_input_ratio = output_rate / input_rate;
|
||||
double cutoff; // 1.0 = input_rate / 2
|
||||
std::vector<double> coeff_init_buffer;
|
||||
|
||||
// Round up num_convolutions to be even.
|
||||
if(output_rate > input_rate)
|
||||
num_convolutions = ((unsigned)ceil(d / (1.0 - desired_bandwidth)) + 1) & ~1;
|
||||
else
|
||||
num_convolutions = ((unsigned)ceil(d / (output_to_input_ratio * (1.0 - desired_bandwidth))) + 1) & ~1;
|
||||
|
||||
if(output_rate > input_rate) // Upsampling
|
||||
cutoff = desired_bandwidth;
|
||||
else // Downsampling
|
||||
cutoff = output_to_input_ratio * desired_bandwidth;
|
||||
|
||||
// Round up to be even.
|
||||
num_phases = (std::max<unsigned>(pn_nume / num_convolutions, phases_min) + 1) &~1;
|
||||
|
||||
// Adjust cutoff to account for the multiple phases.
|
||||
cutoff = cutoff / num_phases;
|
||||
|
||||
assert((num_convolutions & 1) == 0);
|
||||
assert((num_phases & 1) == 0);
|
||||
|
||||
// fprintf(stderr, "num_convolutions=%u, num_phases=%u, total expected coeff byte size=%lu\n", num_convolutions, num_phases,
|
||||
// (long)((num_phases + 2) * ((num_convolutions + max_mult_minus1) & ~max_mult_minus1) * sizeof(float) + conv_alignment_bytes));
|
||||
|
||||
coeff_init_buffer.resize(num_phases * num_convolutions);
|
||||
|
||||
coeffs.resize(num_phases + 1 + 1);
|
||||
|
||||
coeff_mem.resize((num_phases + 1 + 1) * ((num_convolutions + max_mult_minus1) &~ max_mult_minus1) * sizeof(resample_coeff_t) + conv_alignment_bytes);
|
||||
|
||||
// Assign aligned pointers into coeff_mem
|
||||
{
|
||||
resample_coeff_t *base_ptr = (resample_coeff_t *)ResampleUtility::make_aligned(&coeff_mem[0], conv_alignment_bytes);
|
||||
|
||||
for(unsigned phase = 0; phase < (num_phases + 1 + 1); phase++)
|
||||
{
|
||||
coeffs[phase] = base_ptr + (((num_convolutions + max_mult_minus1) & ~max_mult_minus1) * phase);
|
||||
}
|
||||
}
|
||||
|
||||
ResampleUtility::gen_sinc(&coeff_init_buffer[0], num_phases * num_convolutions, cutoff, beta);
|
||||
ResampleUtility::normalize(&coeff_init_buffer[0], num_phases * num_convolutions, num_phases);
|
||||
|
||||
// Reorder coefficients to allow for more efficient convolution.
|
||||
for(int phase = -1; phase < ((int)num_phases + 1); phase++)
|
||||
{
|
||||
for(int conv = 0; conv < (int)num_convolutions; conv++)
|
||||
{
|
||||
double coeff;
|
||||
|
||||
if(phase == -1 && conv == 0)
|
||||
coeff = 0;
|
||||
else if(phase == (int)num_phases && conv == ((int)num_convolutions - 1))
|
||||
coeff = 0;
|
||||
else
|
||||
coeff = coeff_init_buffer[conv * num_phases + phase];
|
||||
|
||||
coeffs[phase + 1][conv] = coeff;
|
||||
}
|
||||
}
|
||||
|
||||
// Free a bit of mem
|
||||
coeff_init_buffer.resize(0);
|
||||
|
||||
step_int = floor(input_to_output_ratio);
|
||||
step_fract = input_to_output_ratio - step_int;
|
||||
|
||||
input_pos_fract = 0;
|
||||
|
||||
// Do NOT use rb.size() later in the code, since it'll include the padding.
|
||||
// We should only need one "max_mult_minus1" here, not two, since it won't matter if it over-reads(due to doing "max_mult_atatime" multiplications at a time
|
||||
// rather than just 1, in which case this over-read wouldn't happen), from the first half into the duplicated half,
|
||||
// since those corresponding coefficients will be zero anyway; this is just to handle the case of reading off the end of the duplicated half to
|
||||
// prevent illegal memory accesses.
|
||||
rb.resize(num_convolutions * 2 + max_mult_minus1);
|
||||
|
||||
rb_readpos = 0;
|
||||
rb_writepos = 0;
|
||||
rb_in = 0;
|
||||
}
|
||||
|
||||
resample_samp_t SincResample::mac(const resample_samp_t *wave, const resample_coeff_t *coeffs_a, const resample_coeff_t *coeffs_b, const double ffract, unsigned count)
|
||||
{
|
||||
resample_samp_t accum = 0;
|
||||
#if SINCRESAMPLE_USE_SSE
|
||||
__m128 accum_vec_a[2] = { _mm_set1_ps(0), _mm_set1_ps(0) };
|
||||
__m128 accum_vec_b[2] = { _mm_set1_ps(0), _mm_set1_ps(0) };
|
||||
|
||||
for(unsigned c = 0; c < count; c += 8) //8) //4)
|
||||
{
|
||||
__m128 coeff_a[2];
|
||||
__m128 coeff_b[2];
|
||||
__m128 w[2];
|
||||
__m128 result_a[2], result_b[2];
|
||||
|
||||
for(unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
coeff_a[i] = _mm_load_ps(&coeffs_a[c + (i * 4)]);
|
||||
coeff_b[i] = _mm_load_ps(&coeffs_b[c + (i * 4)]);
|
||||
w[i] = _mm_loadu_ps(&wave[c + (i * 4)]);
|
||||
|
||||
result_a[i] = _mm_mul_ps(coeff_a[i], w[i]);
|
||||
result_b[i] = _mm_mul_ps(coeff_b[i], w[i]);
|
||||
|
||||
accum_vec_a[i] = _mm_add_ps(result_a[i], accum_vec_a[i]);
|
||||
accum_vec_b[i] = _mm_add_ps(result_b[i], accum_vec_b[i]);
|
||||
}
|
||||
}
|
||||
|
||||
__m128 accum_vec, av_a, av_b;
|
||||
__m128 mult_a_vec = _mm_set1_ps(1.0 - ffract);
|
||||
__m128 mult_b_vec = _mm_set1_ps(ffract);
|
||||
|
||||
av_a = _mm_mul_ps(mult_a_vec, /*accum_vec_a[0]);*/ _mm_add_ps(accum_vec_a[0], accum_vec_a[1]));
|
||||
av_b = _mm_mul_ps(mult_b_vec, /*accum_vec_b[0]);*/ _mm_add_ps(accum_vec_b[0], accum_vec_b[1]));
|
||||
|
||||
accum_vec = _mm_add_ps(av_a, av_b);
|
||||
|
||||
accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (3 << 0) | (2 << 2) | (1 << 4) | (0 << 6)));
|
||||
accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (1 << 0) | (0 << 2) | (1 << 4) | (0 << 6)));
|
||||
|
||||
_mm_store_ss(&accum, accum_vec);
|
||||
#else
|
||||
resample_coeff_t mult_a = 1.0 - ffract;
|
||||
resample_coeff_t mult_b = ffract;
|
||||
|
||||
for(unsigned c = 0; c < count; c += 4)
|
||||
{
|
||||
accum += wave[c + 0] * (coeffs_a[c + 0] * mult_a + coeffs_b[c + 0] * mult_b);
|
||||
accum += wave[c + 1] * (coeffs_a[c + 1] * mult_a + coeffs_b[c + 1] * mult_b);
|
||||
accum += wave[c + 2] * (coeffs_a[c + 2] * mult_a + coeffs_b[c + 2] * mult_b);
|
||||
accum += wave[c + 3] * (coeffs_a[c + 3] * mult_a + coeffs_b[c + 3] * mult_b);
|
||||
}
|
||||
#endif
|
||||
|
||||
return accum;
|
||||
}
|
||||
|
||||
inline bool SincResample::output_avail(void)
|
||||
{
|
||||
return(rb_in >= (int)num_convolutions);
|
||||
}
|
||||
|
||||
resample_samp_t SincResample::read(void)
|
||||
{
|
||||
assert(output_avail());
|
||||
double phase = input_pos_fract * num_phases - 0.5;
|
||||
signed phase_int = (signed)floor(phase);
|
||||
double phase_fract = phase - phase_int;
|
||||
unsigned phase_a = num_phases - 1 - phase_int;
|
||||
unsigned phase_b = phase_a - 1;
|
||||
resample_samp_t ret;
|
||||
|
||||
ret = mac(&rb[rb_readpos], &coeffs[phase_a + 1][0], &coeffs[phase_b + 1][0], phase_fract, num_convolutions);
|
||||
|
||||
unsigned int_increment = step_int;
|
||||
|
||||
input_pos_fract += step_fract;
|
||||
int_increment += floor(input_pos_fract);
|
||||
input_pos_fract -= floor(input_pos_fract);
|
||||
|
||||
rb_readpos = (rb_readpos + int_increment) % num_convolutions;
|
||||
rb_in -= int_increment;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline void SincResample::write(resample_samp_t sample)
|
||||
{
|
||||
assert(!output_avail());
|
||||
|
||||
if(hr_used)
|
||||
{
|
||||
hr.write(sample);
|
||||
|
||||
if(hr.output_avail())
|
||||
{
|
||||
sample = hr.read();
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
rb[rb_writepos + 0 * num_convolutions] = sample;
|
||||
rb[rb_writepos + 1 * num_convolutions] = sample;
|
||||
rb_writepos = (rb_writepos + 1) % num_convolutions;
|
||||
rb_in++;
|
||||
}
|
||||
|
||||
void ResampleUtility::kaiser_window( double* io, int count, double beta)
|
||||
{
|
||||
int const accuracy = 24; //16; //12;
|
||||
|
||||
double* end = io + count;
|
||||
|
||||
double beta2 = beta * beta * (double) -0.25;
|
||||
double to_fract = beta2 / ((double) count * count);
|
||||
double i = 0;
|
||||
double rescale = 0; // Doesn't need an initializer, to shut up gcc
|
||||
|
||||
for ( ; io < end; ++io, i += 1 )
|
||||
{
|
||||
double x = i * i * to_fract - beta2;
|
||||
double u = x;
|
||||
double k = x + 1;
|
||||
|
||||
double n = 2;
|
||||
do
|
||||
{
|
||||
u *= x / (n * n);
|
||||
n += 1;
|
||||
k += u;
|
||||
}
|
||||
while ( k <= u * (1 << accuracy) );
|
||||
|
||||
if ( !i )
|
||||
rescale = 1 / k; // otherwise values get large
|
||||
|
||||
*io *= k * rescale;
|
||||
}
|
||||
}
|
||||
|
||||
void ResampleUtility::gen_sinc(double* out, int size, double cutoff, double kaiser)
|
||||
{
|
||||
assert( size % 2 == 0 ); // size must be even
|
||||
|
||||
int const half_size = size / 2;
|
||||
double* const mid = &out [half_size];
|
||||
|
||||
// Generate right half of sinc
|
||||
for ( int i = 0; i < half_size; i++ )
|
||||
{
|
||||
double angle = (i * 2 + 1) * (Math::Pi / 2);
|
||||
mid [i] = sin( angle * cutoff ) / angle;
|
||||
}
|
||||
|
||||
kaiser_window( mid, half_size, kaiser );
|
||||
|
||||
// Mirror for left half
|
||||
for ( int i = 0; i < half_size; i++ )
|
||||
out [i] = mid [half_size - 1 - i];
|
||||
}
|
||||
|
||||
void ResampleUtility::gen_sinc_os(double* out, int size, double cutoff, double kaiser)
|
||||
{
|
||||
assert( size % 2 == 1); // size must be odd
|
||||
|
||||
for(int i = 0; i < size; i++)
|
||||
{
|
||||
if(i == (size / 2))
|
||||
out[i] = 2 * Math::Pi * (cutoff / 2); //0.078478; //1.0; //sin(2 * M_PI * (cutoff / 2) * (i - size / 2)) / (i - (size / 2));
|
||||
else
|
||||
out[i] = sin(2 * Math::Pi * (cutoff / 2) * (i - size / 2)) / (i - (size / 2));
|
||||
|
||||
// out[i] *= 0.3635819 - 0.4891775 * cos(2 * M_PI * i / (size - 1)) + 0.1365995 * cos(4 * M_PI * i / (size - 1)) - 0.0106411 * cos(6 * M_PI * i / (size - 1));
|
||||
//0.42 - 0.5 * cos(2 * M_PI * i / (size - 1)) + 0.08 * cos(4 * M_PI * i / (size - 1));
|
||||
|
||||
// printf("%d %f\n", i, out[i]);
|
||||
}
|
||||
|
||||
kaiser_window(&out[size / 2], size / 2 + 1, kaiser);
|
||||
|
||||
// Mirror for left half
|
||||
for ( int i = 0; i < size / 2; i++ )
|
||||
out [i] = out [size - 1 - i];
|
||||
|
||||
}
|
||||
|
||||
void ResampleUtility::normalize(double* io, int size, double gain)
|
||||
{
|
||||
double sum = 0;
|
||||
for ( int i = 0; i < size; i++ )
|
||||
sum += io [i];
|
||||
|
||||
double scale = gain / sum;
|
||||
for ( int i = 0; i < size; i++ )
|
||||
io [i] *= scale;
|
||||
}
|
||||
|
||||
void* ResampleUtility::make_aligned(void* ptr, unsigned boundary)
|
||||
{
|
||||
unsigned char* null_ptr = (unsigned char *)nullptr;
|
||||
unsigned char* uc_ptr = (unsigned char *)ptr;
|
||||
|
||||
uc_ptr += (boundary - ((uc_ptr - null_ptr) & (boundary - 1))) & (boundary - 1);
|
||||
|
||||
//while((uc_ptr - null_ptr) & (boundary - 1))
|
||||
// uc_ptr++;
|
||||
|
||||
//printf("%16llx %16llx\n", (unsigned long long)ptr, (unsigned long long)uc_ptr);
|
||||
|
||||
assert((uc_ptr - (unsigned char *)ptr) < boundary && (uc_ptr >= (unsigned char *)ptr));
|
||||
|
||||
return uc_ptr;
|
||||
}
|
43
dsp/resample/linear.hpp
Normal file
43
dsp/resample/linear.hpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
struct ResampleLinear : Resampler {
|
||||
ResampleLinear(DSP& dsp) : Resampler(dsp) {}
|
||||
|
||||
inline auto setFrequency() -> void;
|
||||
inline auto clear() -> void;
|
||||
inline auto sample() -> void;
|
||||
|
||||
private:
|
||||
double fraction;
|
||||
double step;
|
||||
};
|
||||
|
||||
auto ResampleLinear::setFrequency() -> void {
|
||||
fraction = 0.0;
|
||||
step = dsp.settings.frequency / frequency;
|
||||
}
|
||||
|
||||
auto ResampleLinear::clear() -> void {
|
||||
fraction = 0.0;
|
||||
}
|
||||
|
||||
auto ResampleLinear::sample() -> void {
|
||||
while(fraction <= 1.0) {
|
||||
double channel[dsp.settings.channels];
|
||||
|
||||
for(auto n : range(dsp.settings.channels)) {
|
||||
double a = dsp.buffer.read(n, -1);
|
||||
double b = dsp.buffer.read(n, -0);
|
||||
|
||||
double mu = fraction;
|
||||
|
||||
channel[n] = a * (1.0 - mu) + b * mu;
|
||||
}
|
||||
|
||||
dsp.write(channel);
|
||||
fraction += step;
|
||||
}
|
||||
|
||||
dsp.buffer.rdoffset++;
|
||||
fraction -= 1.0;
|
||||
}
|
43
dsp/resample/nearest.hpp
Normal file
43
dsp/resample/nearest.hpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
struct ResampleNearest : Resampler {
|
||||
ResampleNearest(DSP& dsp) : Resampler(dsp) {}
|
||||
|
||||
inline auto setFrequency() -> void;
|
||||
inline auto clear() -> void;
|
||||
inline auto sample() -> void;
|
||||
|
||||
private:
|
||||
double fraction;
|
||||
double step;
|
||||
};
|
||||
|
||||
auto ResampleNearest::setFrequency() -> void {
|
||||
fraction = 0.0;
|
||||
step = dsp.settings.frequency / frequency;
|
||||
}
|
||||
|
||||
auto ResampleNearest::clear() -> void {
|
||||
fraction = 0.0;
|
||||
}
|
||||
|
||||
auto ResampleNearest::sample() -> void {
|
||||
while(fraction <= 1.0) {
|
||||
double channel[dsp.settings.channels];
|
||||
|
||||
for(auto n : range(dsp.settings.channels)) {
|
||||
double a = dsp.buffer.read(n, -1);
|
||||
double b = dsp.buffer.read(n, -0);
|
||||
|
||||
double mu = fraction;
|
||||
|
||||
channel[n] = mu < 0.5 ? a : b;
|
||||
}
|
||||
|
||||
dsp.write(channel);
|
||||
fraction += step;
|
||||
}
|
||||
|
||||
dsp.buffer.rdoffset++;
|
||||
fraction -= 1.0;
|
||||
}
|
62
dsp/resample/sinc.hpp
Normal file
62
dsp/resample/sinc.hpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#include "lib/sinc.hpp"
|
||||
|
||||
struct ResampleSinc : Resampler {
|
||||
inline ResampleSinc(DSP& dsp);
|
||||
inline ~ResampleSinc();
|
||||
|
||||
inline auto setFrequency() -> void;
|
||||
inline auto clear() -> void;
|
||||
inline auto sample() -> void;
|
||||
|
||||
private:
|
||||
inline void remakeSinc();
|
||||
SincResample* sincResampler[8] = {0};
|
||||
};
|
||||
|
||||
ResampleSinc::ResampleSinc(DSP& dsp) : Resampler(dsp) {
|
||||
for(auto n : range(8)) {
|
||||
sincResampler[n] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
ResampleSinc::~ResampleSinc() {
|
||||
for(auto n : range(8)) {
|
||||
if(sincResampler[n]) delete sincResampler[n];
|
||||
}
|
||||
}
|
||||
|
||||
auto ResampleSinc::setFrequency() -> void {
|
||||
remakeSinc();
|
||||
}
|
||||
|
||||
auto ResampleSinc::clear() -> void {
|
||||
remakeSinc();
|
||||
}
|
||||
|
||||
auto ResampleSinc::sample() -> void {
|
||||
for(auto c : range(dsp.settings.channels)) {
|
||||
sincResampler[c]->write(dsp.buffer.read(c));
|
||||
}
|
||||
|
||||
if(sincResampler[0]->output_avail()) {
|
||||
do {
|
||||
for(auto c : range(dsp.settings.channels)) {
|
||||
dsp.output.write(c) = sincResampler[c]->read();
|
||||
}
|
||||
dsp.output.wroffset++;
|
||||
} while(sincResampler[0]->output_avail());
|
||||
}
|
||||
|
||||
dsp.buffer.rdoffset++;
|
||||
}
|
||||
|
||||
auto ResampleSinc::remakeSinc() -> void {
|
||||
assert(dsp.settings.channels < 8);
|
||||
|
||||
for(auto c : range(dsp.settings.channels)) {
|
||||
if(sincResampler[c]) delete sincResampler[c];
|
||||
sincResampler[c] = new SincResample(dsp.settings.frequency, frequency, 0.85, SincResample::QUALITY_HIGH);
|
||||
}
|
||||
}
|
46
dsp/settings.hpp
Normal file
46
dsp/settings.hpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
auto DSP::setChannels(uint channels) -> void {
|
||||
channels = max(1u, channels);
|
||||
buffer.setChannels(channels);
|
||||
output.setChannels(channels);
|
||||
settings.channels = channels;
|
||||
}
|
||||
|
||||
auto DSP::setPrecision(uint precision) -> void {
|
||||
settings.precision = precision;
|
||||
settings.intensity = 1 << (settings.precision - 1);
|
||||
settings.intensityInverse = 1.0 / settings.intensity;
|
||||
}
|
||||
|
||||
auto DSP::setFrequency(double frequency) -> void {
|
||||
settings.frequency = frequency;
|
||||
resampler->setFrequency();
|
||||
}
|
||||
|
||||
auto DSP::setVolume(double volume) -> void {
|
||||
settings.volume = volume;
|
||||
}
|
||||
|
||||
auto DSP::setBalance(double balance) -> void {
|
||||
settings.balance = balance;
|
||||
}
|
||||
|
||||
auto DSP::setResampler(ResampleEngine engine) -> void {
|
||||
if(resampler) delete resampler;
|
||||
|
||||
switch(engine) { default:
|
||||
case ResampleEngine::Nearest: resampler = new ResampleNearest(*this); return;
|
||||
case ResampleEngine::Linear: resampler = new ResampleLinear (*this); return;
|
||||
case ResampleEngine::Cosine: resampler = new ResampleCosine (*this); return;
|
||||
case ResampleEngine::Cubic: resampler = new ResampleCubic (*this); return;
|
||||
case ResampleEngine::Hermite: resampler = new ResampleHermite(*this); return;
|
||||
case ResampleEngine::Average: resampler = new ResampleAverage(*this); return;
|
||||
case ResampleEngine::Sinc: resampler = new ResampleSinc (*this); return;
|
||||
}
|
||||
}
|
||||
|
||||
auto DSP::setResamplerFrequency(double frequency) -> void {
|
||||
resampler->frequency = frequency;
|
||||
resampler->setFrequency();
|
||||
}
|
95
emulation/super-famicom-usart.hpp
Normal file
95
emulation/super-famicom-usart.hpp
Normal file
|
@ -0,0 +1,95 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/nall.hpp>
|
||||
#include <nall/serial.hpp>
|
||||
using namespace nall;
|
||||
|
||||
#include <signal.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
static function<auto () -> bool> usart_quit;
|
||||
static function<auto (uint microseconds) -> void> usart_usleep;
|
||||
static function<auto () -> bool> usart_readable;
|
||||
static function<auto () -> uint8> usart_read;
|
||||
static function<auto () -> bool> usart_writable;
|
||||
static function<auto (uint8 data) -> void> usart_write;
|
||||
|
||||
extern "C" auto usart_init(
|
||||
function<auto () -> bool> quit,
|
||||
function<auto (uint microseconds) -> void> usleep,
|
||||
function<auto () -> bool> readable,
|
||||
function<auto () -> uint8> read,
|
||||
function<auto () -> bool> writable,
|
||||
function<auto (uint8 data) -> void> write
|
||||
) -> void {
|
||||
usart_quit = quit;
|
||||
usart_usleep = usleep;
|
||||
usart_readable = readable;
|
||||
usart_read = read;
|
||||
usart_writable = writable;
|
||||
usart_write = write;
|
||||
}
|
||||
|
||||
extern "C" auto usart_main(nall::lstring) -> void;
|
||||
|
||||
//
|
||||
|
||||
static serial usart;
|
||||
static bool usart_is_virtual = true;
|
||||
static bool usart_sigint = false;
|
||||
|
||||
static auto usart_virtual() -> bool {
|
||||
return usart_is_virtual;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
static auto usarthw_quit() -> bool {
|
||||
return usart_sigint;
|
||||
}
|
||||
|
||||
static auto usarthw_usleep(uint microseconds) -> void {
|
||||
usleep(microseconds);
|
||||
}
|
||||
|
||||
static auto usarthw_readable() -> bool {
|
||||
return usart.readable();
|
||||
}
|
||||
|
||||
static auto usarthw_read() -> uint8 {
|
||||
while(true) {
|
||||
uint8 buffer[1];
|
||||
int length = usart.read((uint8_t*)&buffer, 1);
|
||||
if(length > 0) return buffer[0];
|
||||
}
|
||||
}
|
||||
|
||||
static auto usarthw_writable() -> bool {
|
||||
return usart.writable();
|
||||
}
|
||||
|
||||
static auto usarthw_write(uint8 data) -> void {
|
||||
uint8 buffer[1] = {data};
|
||||
usart.write((uint8*)&buffer, 1);
|
||||
}
|
||||
|
||||
static auto sigint(int) -> void {
|
||||
signal(SIGINT, SIG_DFL);
|
||||
usart_sigint = true;
|
||||
}
|
||||
|
||||
#include <nall/main.hpp>
|
||||
auto nall::main(lstring args) -> void {
|
||||
setpriority(PRIO_PROCESS, 0, -20); //requires superuser privileges; otherwise priority = +0
|
||||
signal(SIGINT, sigint);
|
||||
|
||||
if(!usart.open("/dev/ttyACM0", 57600, true)) {
|
||||
return print("error: unable to open USART hardware device\n");
|
||||
}
|
||||
|
||||
usart_is_virtual = false;
|
||||
usart_init(usarthw_quit, usarthw_usleep, usarthw_readable, usarthw_read, usarthw_writable, usarthw_write);
|
||||
usart_main(args);
|
||||
usart.close();
|
||||
}
|
67
encode/base64.hpp
Normal file
67
encode/base64.hpp
Normal file
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall { namespace Encode {
|
||||
|
||||
inline auto Base64(const uint8_t* data, unsigned size, const string& format = "MIME") -> string {
|
||||
vector<uint8_t> result;
|
||||
|
||||
char lookup[65];
|
||||
for(unsigned n = 0; n < 26; n++) lookup[ 0 + n] = 'A' + n;
|
||||
for(unsigned n = 0; n < 26; n++) lookup[26 + n] = 'a' + n;
|
||||
for(unsigned n = 0; n < 10; n++) lookup[52 + n] = '0' + n;
|
||||
|
||||
if(format == "MIME") {
|
||||
lookup[62] = '+';
|
||||
lookup[63] = '/';
|
||||
lookup[64] = '=';
|
||||
} else if(format == "URI") {
|
||||
lookup[62] = '-';
|
||||
lookup[63] = '_';
|
||||
lookup[64] = 0;
|
||||
} else return "";
|
||||
|
||||
unsigned overflow = (3 - (size % 3)) % 3; //bytes to round to nearest multiple of 3
|
||||
uint8_t buffer;
|
||||
|
||||
for(unsigned i = 0; i < size; i++) {
|
||||
switch(i % 3) {
|
||||
case 0:
|
||||
buffer = data[i] >> 2;
|
||||
result.append(lookup[buffer]);
|
||||
buffer = (data[i] & 3) << 4;
|
||||
result.append(lookup[buffer]);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
buffer |= data[i] >> 4;
|
||||
result.last() = lookup[buffer];
|
||||
buffer = (data[i] & 15) << 2;
|
||||
result.append(lookup[buffer]);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
buffer |= data[i] >> 6;
|
||||
result.last() = lookup[buffer];
|
||||
buffer = (data[i] & 63);
|
||||
result.append(lookup[buffer]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(lookup[64]) {
|
||||
if(overflow >= 1) result.append(lookup[64]);
|
||||
if(overflow >= 2) result.append(lookup[64]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline auto Base64(const vector<uint8_t>& buffer, const string& format = "MIME") -> string {
|
||||
return Base64(buffer.data(), buffer.size(), format);
|
||||
}
|
||||
|
||||
inline auto Base64(const string& text, const string& format = "MIME") -> string {
|
||||
return Base64(text.binary(), text.size(), format);
|
||||
}
|
||||
|
||||
}}
|
46
encode/bmp.hpp
Normal file
46
encode/bmp.hpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall { namespace Encode {
|
||||
|
||||
struct BMP {
|
||||
static auto create(const string& filename, const uint32_t* data, unsigned width, unsigned height, bool alpha) -> bool {
|
||||
file fp{filename, file::mode::write};
|
||||
if(!fp) return false;
|
||||
|
||||
unsigned bitsPerPixel = alpha ? 32 : 24;
|
||||
unsigned bytesPerPixel = bitsPerPixel / 8;
|
||||
unsigned alignedWidth = width * bytesPerPixel;
|
||||
unsigned paddingLength = 0;
|
||||
unsigned imageSize = alignedWidth * height;
|
||||
unsigned fileSize = 0x36 + imageSize;
|
||||
while(alignedWidth % 4) alignedWidth++, paddingLength++;
|
||||
|
||||
fp.writel(0x4d42, 2); //signature
|
||||
fp.writel(fileSize, 4); //file size
|
||||
fp.writel(0, 2); //reserved
|
||||
fp.writel(0, 2); //reserved
|
||||
fp.writel(0x36, 4); //offset
|
||||
|
||||
fp.writel(40, 4); //DIB size
|
||||
fp.writel(width, 4); //width
|
||||
fp.writel(-height, 4); //height
|
||||
fp.writel(1, 2); //color planes
|
||||
fp.writel(bitsPerPixel, 2); //bits per pixel
|
||||
fp.writel(0, 4); //compression method (BI_RGB)
|
||||
fp.writel(imageSize, 4); //image data size
|
||||
fp.writel(3780, 4); //horizontal resolution
|
||||
fp.writel(3780, 4); //vertical resolution
|
||||
fp.writel(0, 4); //palette size
|
||||
fp.writel(0, 4); //important color count
|
||||
|
||||
for(auto y : range(height)) {
|
||||
const uint32_t* p = data + y * width;
|
||||
for(auto x : range(width)) fp.writel(*p++, bytesPerPixel);
|
||||
if(paddingLength) fp.writel(0, paddingLength);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}}
|
22
encode/url.hpp
Normal file
22
encode/url.hpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall { namespace Encode {
|
||||
|
||||
inline auto URL(const string& input) -> string {
|
||||
string output;
|
||||
for(auto c : input) {
|
||||
if(c >= 'A' && c <= 'Z') { output.append(c); continue; }
|
||||
if(c >= 'a' && c <= 'z') { output.append(c); continue; }
|
||||
if(c >= '0' && c <= '9') { output.append(c); continue; }
|
||||
if(c == '-' || c == '_' || c == '.' || c == '~') { output.append(c); continue; }
|
||||
if(c == ' ') { output.append('+'); continue; }
|
||||
unsigned hi = (c >> 4) & 15;
|
||||
unsigned lo = (c >> 0) & 15;
|
||||
output.append('%');
|
||||
output.append((char)(hi < 10 ? ('0' + hi) : ('a' + hi - 10)));
|
||||
output.append((char)(lo < 10 ? ('0' + lo) : ('a' + lo - 10)));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
}}
|
92
encode/zip.hpp
Normal file
92
encode/zip.hpp
Normal file
|
@ -0,0 +1,92 @@
|
|||
#pragma once
|
||||
|
||||
//creates uncompressed ZIP archives
|
||||
|
||||
#include <nall/string.hpp>
|
||||
#include <nall/hash/crc32.hpp>
|
||||
|
||||
namespace nall { namespace Encode {
|
||||
|
||||
struct ZIP {
|
||||
ZIP(const string& filename) {
|
||||
fp.open(filename, file::mode::write);
|
||||
time_t currentTime = time(nullptr);
|
||||
tm* info = localtime(¤tTime);
|
||||
dosTime = (info->tm_hour << 11) | (info->tm_min << 5) | (info->tm_sec >> 1);
|
||||
dosDate = ((info->tm_year - 80) << 9) | ((1 + info->tm_mon) << 5) + (info->tm_mday);
|
||||
}
|
||||
|
||||
//append path: append("path/");
|
||||
//append file: append("path/file", data, size);
|
||||
auto append(string filename, const uint8_t* data = nullptr, unsigned size = 0u) -> void {
|
||||
filename.transform("\\", "/");
|
||||
uint32_t checksum = Hash::CRC32(data, size).value();
|
||||
directory.append({filename, checksum, size, fp.offset()});
|
||||
|
||||
fp.writel(0x04034b50, 4); //signature
|
||||
fp.writel(0x0014, 2); //minimum version (2.0)
|
||||
fp.writel(0x0000, 2); //general purpose bit flags
|
||||
fp.writel(0x0000, 2); //compression method (0 = uncompressed)
|
||||
fp.writel(dosTime, 2);
|
||||
fp.writel(dosDate, 2);
|
||||
fp.writel(checksum, 4);
|
||||
fp.writel(size, 4); //compressed size
|
||||
fp.writel(size, 4); //uncompressed size
|
||||
fp.writel(filename.length(), 2); //file name length
|
||||
fp.writel(0x0000, 2); //extra field length
|
||||
fp.print(filename); //file name
|
||||
|
||||
fp.write(data, size); //file data
|
||||
}
|
||||
|
||||
~ZIP() {
|
||||
//central directory
|
||||
unsigned baseOffset = fp.offset();
|
||||
for(auto& entry : directory) {
|
||||
fp.writel(0x02014b50, 4); //signature
|
||||
fp.writel(0x0014, 2); //version made by (2.0)
|
||||
fp.writel(0x0014, 2); //version needed to extract (2.0)
|
||||
fp.writel(0x0000, 2); //general purpose bit flags
|
||||
fp.writel(0x0000, 2); //compression method (0 = uncompressed)
|
||||
fp.writel(dosTime, 2);
|
||||
fp.writel(dosDate, 2);
|
||||
fp.writel(entry.checksum, 4);
|
||||
fp.writel(entry.size, 4); //compressed size
|
||||
fp.writel(entry.size, 4); //uncompressed size
|
||||
fp.writel(entry.filename.length(), 2); //file name length
|
||||
fp.writel(0x0000, 2); //extra field length
|
||||
fp.writel(0x0000, 2); //file comment length
|
||||
fp.writel(0x0000, 2); //disk number start
|
||||
fp.writel(0x0000, 2); //internal file attributes
|
||||
fp.writel(0x00000000, 4); //external file attributes
|
||||
fp.writel(entry.offset, 4); //relative offset of file header
|
||||
fp.print(entry.filename);
|
||||
}
|
||||
unsigned finishOffset = fp.offset();
|
||||
|
||||
//end of central directory
|
||||
fp.writel(0x06054b50, 4); //signature
|
||||
fp.writel(0x0000, 2); //number of this disk
|
||||
fp.writel(0x0000, 2); //disk where central directory starts
|
||||
fp.writel(directory.size(), 2); //number of central directory records on this disk
|
||||
fp.writel(directory.size(), 2); //total number of central directory records
|
||||
fp.writel(finishOffset - baseOffset, 4); //size of central directory
|
||||
fp.writel(baseOffset, 4); //offset of central directory
|
||||
fp.writel(0x0000, 2); //comment length
|
||||
|
||||
fp.close();
|
||||
}
|
||||
|
||||
protected:
|
||||
file fp;
|
||||
uint16_t dosTime, dosDate;
|
||||
struct entry_t {
|
||||
string filename;
|
||||
uint32_t checksum;
|
||||
uint32_t size;
|
||||
uint32_t offset;
|
||||
};
|
||||
vector<entry_t> directory;
|
||||
};
|
||||
|
||||
}}
|
39
endian.hpp
Normal file
39
endian.hpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/intrinsics.hpp>
|
||||
|
||||
#if defined(ENDIAN_LSB)
|
||||
//little-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x04030201
|
||||
#define order_lsb2(a,b) a,b
|
||||
#define order_lsb3(a,b,c) a,b,c
|
||||
#define order_lsb4(a,b,c,d) a,b,c,d
|
||||
#define order_lsb5(a,b,c,d,e) a,b,c,d,e
|
||||
#define order_lsb6(a,b,c,d,e,f) a,b,c,d,e,f
|
||||
#define order_lsb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g
|
||||
#define order_lsb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h
|
||||
#define order_msb2(a,b) b,a
|
||||
#define order_msb3(a,b,c) c,b,a
|
||||
#define order_msb4(a,b,c,d) d,c,b,a
|
||||
#define order_msb5(a,b,c,d,e) e,d,c,b,a
|
||||
#define order_msb6(a,b,c,d,e,f) f,e,d,c,b,a
|
||||
#define order_msb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a
|
||||
#define order_msb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a
|
||||
#elif defined(ENDIAN_MSB)
|
||||
//big-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x01020304
|
||||
#define order_lsb2(a,b) b,a
|
||||
#define order_lsb3(a,b,c) c,b,a
|
||||
#define order_lsb4(a,b,c,d) d,c,b,a
|
||||
#define order_lsb5(a,b,c,d,e) e,d,c,b,a
|
||||
#define order_lsb6(a,b,c,d,e,f) f,e,d,c,b,a
|
||||
#define order_lsb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a
|
||||
#define order_lsb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a
|
||||
#define order_msb2(a,b) a,b
|
||||
#define order_msb3(a,b,c) a,b,c
|
||||
#define order_msb4(a,b,c,d) a,b,c,d
|
||||
#define order_msb5(a,b,c,d,e) a,b,c,d,e
|
||||
#define order_msb6(a,b,c,d,e,f) a,b,c,d,e,f
|
||||
#define order_msb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g
|
||||
#define order_msb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h
|
||||
#else
|
||||
#error "Unknown endian. Please specify in nall/intrinsics.hpp"
|
||||
#endif
|
333
file.hpp
Normal file
333
file.hpp
Normal file
|
@ -0,0 +1,333 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/inode.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/string.hpp>
|
||||
#include <nall/utility.hpp>
|
||||
#include <nall/varint.hpp>
|
||||
#include <nall/hash/sha256.hpp>
|
||||
#include <nall/stream/memory.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct file : inode, varint {
|
||||
enum class mode : uint { read, write, modify, append, readwrite = modify, writeread = append };
|
||||
enum class index : uint { absolute, relative };
|
||||
|
||||
static auto copy(const string& sourcename, const string& targetname) -> bool {
|
||||
if(sourcename == targetname) return true;
|
||||
file rd, wr;
|
||||
if(rd.open(sourcename, mode::read) == false) return false;
|
||||
if(wr.open(targetname, mode::write) == false) return false;
|
||||
for(uint n = 0; n < rd.size(); n++) wr.write(rd.read());
|
||||
return true;
|
||||
}
|
||||
|
||||
//attempt to rename file first
|
||||
//this will fail if paths point to different file systems; fall back to copy+remove in this case
|
||||
static auto move(const string& sourcename, const string& targetname) -> bool {
|
||||
if(sourcename == targetname) return true;
|
||||
if(rename(sourcename, targetname)) return true;
|
||||
if(!writable(sourcename)) return false;
|
||||
if(copy(sourcename, targetname)) {
|
||||
remove(sourcename);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static auto truncate(const string& filename, uint size) -> bool {
|
||||
#if defined(API_POSIX)
|
||||
return truncate(filename, size) == 0;
|
||||
#elif defined(API_WINDOWS)
|
||||
if(auto fp = _wfopen(utf16_t(filename), L"rb+")) {
|
||||
bool result = _chsize(fileno(fp), size) == 0;
|
||||
fclose(fp);
|
||||
return result;
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
//returns false if specified filename is a directory
|
||||
static auto exists(const string& filename) -> bool {
|
||||
#if defined(API_POSIX)
|
||||
struct stat data;
|
||||
if(stat(filename, &data) != 0) return false;
|
||||
#elif defined(API_WINDOWS)
|
||||
struct __stat64 data;
|
||||
if(_wstat64(utf16_t(filename), &data) != 0) return false;
|
||||
#endif
|
||||
return !(data.st_mode & S_IFDIR);
|
||||
}
|
||||
|
||||
static auto size(const string& filename) -> uintmax {
|
||||
#if defined(API_POSIX)
|
||||
struct stat data;
|
||||
stat(filename, &data);
|
||||
#elif defined(API_WINDOWS)
|
||||
struct __stat64 data;
|
||||
_wstat64(utf16_t(filename), &data);
|
||||
#endif
|
||||
return S_ISREG(data.st_mode) ? data.st_size : 0u;
|
||||
}
|
||||
|
||||
static auto read(const string& filename) -> vector<uint8_t> {
|
||||
vector<uint8_t> memory;
|
||||
file fp;
|
||||
if(fp.open(filename, mode::read)) {
|
||||
memory.resize(fp.size());
|
||||
fp.read(memory.data(), memory.size());
|
||||
}
|
||||
return memory;
|
||||
}
|
||||
|
||||
static auto read(const string& filename, uint8_t* data, uint size) -> bool {
|
||||
file fp;
|
||||
if(fp.open(filename, mode::read) == false) return false;
|
||||
fp.read(data, size);
|
||||
fp.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
static auto write(const string& filename, const string& text) -> bool {
|
||||
return write(filename, (const uint8_t*)text.data(), text.size());
|
||||
}
|
||||
|
||||
static auto write(const string& filename, const vector<uint8_t>& buffer) -> bool {
|
||||
return write(filename, buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
static auto write(const string& filename, const uint8_t* data, uint size) -> bool {
|
||||
file fp;
|
||||
if(fp.open(filename, mode::write) == false) return false;
|
||||
fp.write(data, size);
|
||||
fp.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
static auto create(const string& filename) -> bool {
|
||||
//create an empty file (will replace existing files)
|
||||
file fp;
|
||||
if(fp.open(filename, mode::write) == false) return false;
|
||||
fp.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
static auto sha256(const string& filename) -> string {
|
||||
auto buffer = read(filename);
|
||||
return Hash::SHA256(buffer.data(), buffer.size()).digest();
|
||||
}
|
||||
|
||||
auto read() -> uint8_t {
|
||||
if(!fp) return 0xff; //file not open
|
||||
if(file_mode == mode::write) return 0xff; //reads not permitted
|
||||
if(file_offset >= file_size) return 0xff; //cannot read past end of file
|
||||
buffer_sync();
|
||||
return buffer[(file_offset++) & buffer_mask];
|
||||
}
|
||||
|
||||
auto readl(uint length = 1) -> uintmax {
|
||||
uintmax data = 0;
|
||||
for(int i = 0; i < length; i++) {
|
||||
data |= (uintmax)read() << (i << 3);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
auto readm(uint length = 1) -> uintmax {
|
||||
uintmax data = 0;
|
||||
while(length--) {
|
||||
data <<= 8;
|
||||
data |= read();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
auto reads(uint length) -> string {
|
||||
string result;
|
||||
result.resize(length);
|
||||
for(auto& byte : result) byte = read();
|
||||
return result;
|
||||
}
|
||||
|
||||
auto read(uint8_t* buffer, uint length) -> void {
|
||||
while(length--) *buffer++ = read();
|
||||
}
|
||||
|
||||
auto write(uint8_t data) -> void {
|
||||
if(!fp) return; //file not open
|
||||
if(file_mode == mode::read) return; //writes not permitted
|
||||
buffer_sync();
|
||||
buffer[(file_offset++) & buffer_mask] = data;
|
||||
buffer_dirty = true;
|
||||
if(file_offset > file_size) file_size = file_offset;
|
||||
}
|
||||
|
||||
auto writel(uintmax data, uint length = 1) -> void {
|
||||
while(length--) {
|
||||
write(data);
|
||||
data >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
auto writem(uintmax data, uint length = 1) -> void {
|
||||
for(int i = length - 1; i >= 0; i--) {
|
||||
write(data >> (i << 3));
|
||||
}
|
||||
}
|
||||
|
||||
auto writes(const string& s) -> void {
|
||||
for(auto byte : s) write(byte);
|
||||
}
|
||||
|
||||
auto write(const uint8_t* buffer, uint length) -> void {
|
||||
while(length--) write(*buffer++);
|
||||
}
|
||||
|
||||
template<typename... Args> auto print(Args... args) -> void {
|
||||
string data(args...);
|
||||
const char* p = data;
|
||||
while(*p) write(*p++);
|
||||
}
|
||||
|
||||
auto flush() -> void {
|
||||
buffer_flush();
|
||||
fflush(fp);
|
||||
}
|
||||
|
||||
auto seek(int offset, index index_ = index::absolute) -> void {
|
||||
if(!fp) return; //file not open
|
||||
buffer_flush();
|
||||
|
||||
intmax req_offset = file_offset;
|
||||
switch(index_) {
|
||||
case index::absolute: req_offset = offset; break;
|
||||
case index::relative: req_offset += offset; break;
|
||||
}
|
||||
|
||||
if(req_offset < 0) req_offset = 0; //cannot seek before start of file
|
||||
if(req_offset > file_size) {
|
||||
if(file_mode == mode::read) { //cannot seek past end of file
|
||||
req_offset = file_size;
|
||||
} else { //pad file to requested location
|
||||
file_offset = file_size;
|
||||
while(file_size < req_offset) write(0x00);
|
||||
}
|
||||
}
|
||||
|
||||
file_offset = req_offset;
|
||||
}
|
||||
|
||||
auto offset() const -> uint {
|
||||
if(!fp) return 0; //file not open
|
||||
return file_offset;
|
||||
}
|
||||
|
||||
auto size() const -> uint {
|
||||
if(!fp) return 0; //file not open
|
||||
return file_size;
|
||||
}
|
||||
|
||||
auto truncate(uint size) -> bool {
|
||||
if(!fp) return false; //file not open
|
||||
#if defined(API_POSIX)
|
||||
return ftruncate(fileno(fp), size) == 0;
|
||||
#elif defined(API_WINDOWS)
|
||||
return _chsize(fileno(fp), size) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
auto end() -> bool {
|
||||
if(!fp) return true; //file not open
|
||||
return file_offset >= file_size;
|
||||
}
|
||||
|
||||
auto open() const -> bool {
|
||||
return fp;
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return open();
|
||||
}
|
||||
|
||||
auto open(const string& filename, mode mode_) -> bool {
|
||||
if(fp) return false;
|
||||
|
||||
switch(file_mode = mode_) {
|
||||
#if defined(API_POSIX)
|
||||
case mode::read: fp = fopen(filename, "rb" ); break;
|
||||
case mode::write: fp = fopen(filename, "wb+"); break; //need read permission for buffering
|
||||
case mode::readwrite: fp = fopen(filename, "rb+"); break;
|
||||
case mode::writeread: fp = fopen(filename, "wb+"); break;
|
||||
#elif defined(API_WINDOWS)
|
||||
case mode::read: fp = _wfopen(utf16_t(filename), L"rb" ); break;
|
||||
case mode::write: fp = _wfopen(utf16_t(filename), L"wb+"); break;
|
||||
case mode::readwrite: fp = _wfopen(utf16_t(filename), L"rb+"); break;
|
||||
case mode::writeread: fp = _wfopen(utf16_t(filename), L"wb+"); break;
|
||||
#endif
|
||||
}
|
||||
if(!fp) return false;
|
||||
buffer_offset = -1; //invalidate buffer
|
||||
file_offset = 0;
|
||||
fseek(fp, 0, SEEK_END);
|
||||
file_size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto close() -> void {
|
||||
if(!fp) return;
|
||||
buffer_flush();
|
||||
fclose(fp);
|
||||
fp = nullptr;
|
||||
}
|
||||
|
||||
auto operator=(const file&) -> file& = delete;
|
||||
file(const file&) = delete;
|
||||
file() = default;
|
||||
|
||||
file(const string& filename, mode mode_) {
|
||||
open(filename, mode_);
|
||||
}
|
||||
|
||||
~file() {
|
||||
close();
|
||||
}
|
||||
|
||||
private:
|
||||
enum { buffer_size = 1 << 12, buffer_mask = buffer_size - 1 };
|
||||
char buffer[buffer_size] = {0};
|
||||
int buffer_offset = -1; //invalidate buffer
|
||||
bool buffer_dirty = false;
|
||||
FILE* fp = nullptr;
|
||||
uint file_offset = 0;
|
||||
uint file_size = 0;
|
||||
mode file_mode = mode::read;
|
||||
|
||||
auto buffer_sync() -> void {
|
||||
if(!fp) return; //file not open
|
||||
if(buffer_offset != (file_offset & ~buffer_mask)) {
|
||||
buffer_flush();
|
||||
buffer_offset = file_offset & ~buffer_mask;
|
||||
fseek(fp, buffer_offset, SEEK_SET);
|
||||
uint length = (buffer_offset + buffer_size) <= file_size ? buffer_size : (file_size & buffer_mask);
|
||||
if(length) auto unused = fread(buffer, 1, length, fp);
|
||||
}
|
||||
}
|
||||
|
||||
auto buffer_flush() -> void {
|
||||
if(!fp) return; //file not open
|
||||
if(file_mode == mode::read) return; //buffer cannot be written to
|
||||
if(buffer_offset < 0) return; //buffer unused
|
||||
if(buffer_dirty == false) return; //buffer unmodified since read
|
||||
fseek(fp, buffer_offset, SEEK_SET);
|
||||
uint length = (buffer_offset + buffer_size) <= file_size ? buffer_size : (file_size & buffer_mask);
|
||||
if(length) auto unused = fwrite(buffer, 1, length, fp);
|
||||
buffer_offset = -1; //invalidate buffer
|
||||
buffer_dirty = false;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
214
filemap.hpp
Normal file
214
filemap.hpp
Normal file
|
@ -0,0 +1,214 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/file.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/windows/utf8.hpp>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct filemap {
|
||||
enum class mode : unsigned { read, write, readwrite, writeread };
|
||||
|
||||
filemap() { p_ctor(); }
|
||||
filemap(const string& filename, mode mode_) { p_ctor(); p_open(filename, mode_); }
|
||||
~filemap() { p_dtor(); }
|
||||
|
||||
explicit operator bool() const { return open(); }
|
||||
auto open() const -> bool { return p_open(); }
|
||||
auto open(const string& filename, mode mode_) -> bool { return p_open(filename, mode_); }
|
||||
auto close() -> void { return p_close(); }
|
||||
auto size() const -> unsigned { return p_size; }
|
||||
auto data() -> uint8_t* { return p_handle; }
|
||||
auto data() const -> const uint8_t* { return p_handle; }
|
||||
|
||||
private:
|
||||
uint8_t* p_handle = nullptr;
|
||||
unsigned p_size = 0;
|
||||
|
||||
#if defined(API_WINDOWS)
|
||||
//=============
|
||||
//MapViewOfFile
|
||||
//=============
|
||||
|
||||
HANDLE p_filehandle;
|
||||
HANDLE p_maphandle;
|
||||
|
||||
auto p_open() const -> bool {
|
||||
return p_handle;
|
||||
}
|
||||
|
||||
auto p_open(const string& filename, mode mode_) -> bool {
|
||||
if(file::exists(filename) && file::size(filename) == 0) {
|
||||
p_handle = nullptr;
|
||||
p_size = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
int desired_access, creation_disposition, flprotect, map_access;
|
||||
|
||||
switch(mode_) {
|
||||
default: return false;
|
||||
case mode::read:
|
||||
desired_access = GENERIC_READ;
|
||||
creation_disposition = OPEN_EXISTING;
|
||||
flprotect = PAGE_READONLY;
|
||||
map_access = FILE_MAP_READ;
|
||||
break;
|
||||
case mode::write:
|
||||
//write access requires read access
|
||||
desired_access = GENERIC_WRITE;
|
||||
creation_disposition = CREATE_ALWAYS;
|
||||
flprotect = PAGE_READWRITE;
|
||||
map_access = FILE_MAP_ALL_ACCESS;
|
||||
break;
|
||||
case mode::readwrite:
|
||||
desired_access = GENERIC_READ | GENERIC_WRITE;
|
||||
creation_disposition = OPEN_EXISTING;
|
||||
flprotect = PAGE_READWRITE;
|
||||
map_access = FILE_MAP_ALL_ACCESS;
|
||||
break;
|
||||
case mode::writeread:
|
||||
desired_access = GENERIC_READ | GENERIC_WRITE;
|
||||
creation_disposition = CREATE_NEW;
|
||||
flprotect = PAGE_READWRITE;
|
||||
map_access = FILE_MAP_ALL_ACCESS;
|
||||
break;
|
||||
}
|
||||
|
||||
p_filehandle = CreateFileW(utf16_t(filename), desired_access, FILE_SHARE_READ, nullptr,
|
||||
creation_disposition, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if(p_filehandle == INVALID_HANDLE_VALUE) return false;
|
||||
|
||||
p_size = GetFileSize(p_filehandle, nullptr);
|
||||
|
||||
p_maphandle = CreateFileMapping(p_filehandle, nullptr, flprotect, 0, p_size, nullptr);
|
||||
if(p_maphandle == INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(p_filehandle);
|
||||
p_filehandle = INVALID_HANDLE_VALUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
p_handle = (uint8_t*)MapViewOfFile(p_maphandle, map_access, 0, 0, p_size);
|
||||
return p_handle;
|
||||
}
|
||||
|
||||
auto p_close() -> void {
|
||||
if(p_handle) {
|
||||
UnmapViewOfFile(p_handle);
|
||||
p_handle = nullptr;
|
||||
}
|
||||
|
||||
if(p_maphandle != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(p_maphandle);
|
||||
p_maphandle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
if(p_filehandle != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(p_filehandle);
|
||||
p_filehandle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
auto p_ctor() -> void {
|
||||
p_filehandle = INVALID_HANDLE_VALUE;
|
||||
p_maphandle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
auto p_dtor() -> void {
|
||||
close();
|
||||
}
|
||||
|
||||
#else
|
||||
//====
|
||||
//mmap
|
||||
//====
|
||||
|
||||
int p_fd;
|
||||
|
||||
auto p_open() const -> bool {
|
||||
return p_handle;
|
||||
}
|
||||
|
||||
auto p_open(const string& filename, mode mode_) -> bool {
|
||||
if(file::exists(filename) && file::size(filename) == 0) {
|
||||
p_handle = nullptr;
|
||||
p_size = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
int open_flags, mmap_flags;
|
||||
|
||||
switch(mode_) {
|
||||
default: return false;
|
||||
case mode::read:
|
||||
open_flags = O_RDONLY;
|
||||
mmap_flags = PROT_READ;
|
||||
break;
|
||||
case mode::write:
|
||||
open_flags = O_RDWR | O_CREAT; //mmap() requires read access
|
||||
mmap_flags = PROT_WRITE;
|
||||
break;
|
||||
case mode::readwrite:
|
||||
open_flags = O_RDWR;
|
||||
mmap_flags = PROT_READ | PROT_WRITE;
|
||||
break;
|
||||
case mode::writeread:
|
||||
open_flags = O_RDWR | O_CREAT;
|
||||
mmap_flags = PROT_READ | PROT_WRITE;
|
||||
break;
|
||||
}
|
||||
|
||||
p_fd = ::open(filename, open_flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
|
||||
if(p_fd < 0) return false;
|
||||
|
||||
struct stat p_stat;
|
||||
fstat(p_fd, &p_stat);
|
||||
p_size = p_stat.st_size;
|
||||
|
||||
p_handle = (uint8_t*)mmap(nullptr, p_size, mmap_flags, MAP_SHARED, p_fd, 0);
|
||||
if(p_handle == MAP_FAILED) {
|
||||
p_handle = nullptr;
|
||||
::close(p_fd);
|
||||
p_fd = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
return p_handle;
|
||||
}
|
||||
|
||||
auto p_close() -> void {
|
||||
if(p_handle) {
|
||||
munmap(p_handle, p_size);
|
||||
p_handle = nullptr;
|
||||
}
|
||||
|
||||
if(p_fd >= 0) {
|
||||
::close(p_fd);
|
||||
p_fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
auto p_ctor() -> void {
|
||||
p_fd = -1;
|
||||
}
|
||||
|
||||
auto p_dtor() -> void {
|
||||
p_close();
|
||||
}
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
70
function.hpp
Normal file
70
function.hpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/traits.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename T> struct function;
|
||||
|
||||
template<typename R, typename... P> struct function<auto (P...) -> R> {
|
||||
//value = true if auto L::operator()(P...) -> R exists
|
||||
template<typename L> struct is_compatible {
|
||||
template<typename T> static auto exists(T*) -> const typename is_same<R, decltype(declval<T>().operator()(declval<P>()...))>::type;
|
||||
template<typename T> static auto exists(...) -> const false_type;
|
||||
static constexpr bool value = decltype(exists<L>(0))::value;
|
||||
};
|
||||
|
||||
function() = default;
|
||||
function(const function& source) { operator=(source); }
|
||||
function(void* function) { if(function) callback = new global((auto (*)(P...) -> R)function); }
|
||||
function(auto (*function)(P...) -> R) { callback = new global(function); }
|
||||
template<typename C> function(auto (C::*function)(P...) -> R, C* object) { callback = new member<C>(function, object); }
|
||||
template<typename C> function(auto (C::*function)(P...) const -> R, C* object) { callback = new member<C>((auto (C::*)(P...) -> R)function, object); }
|
||||
template<typename L, typename = enable_if<is_compatible<L>>> function(const L& object) { callback = new lambda<L>(object); }
|
||||
~function() { if(callback) delete callback; }
|
||||
|
||||
explicit operator bool() const { return callback; }
|
||||
auto operator()(P... p) const -> R { return (*callback)(forward<P>(p)...); }
|
||||
auto reset() -> void { if(callback) { delete callback; callback = nullptr; } }
|
||||
|
||||
auto operator=(const function& source) -> function& {
|
||||
if(this != &source) {
|
||||
if(callback) { delete callback; callback = nullptr; }
|
||||
if(source.callback) callback = source.callback->copy();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
struct container {
|
||||
virtual auto operator()(P... p) const -> R = 0;
|
||||
virtual auto copy() const -> container* = 0;
|
||||
virtual ~container() = default;
|
||||
};
|
||||
|
||||
container* callback = nullptr;
|
||||
|
||||
struct global : container {
|
||||
auto (*function)(P...) -> R;
|
||||
auto operator()(P... p) const -> R { return function(forward<P>(p)...); }
|
||||
auto copy() const -> container* { return new global(function); }
|
||||
global(auto (*function)(P...) -> R) : function(function) {}
|
||||
};
|
||||
|
||||
template<typename C> struct member : container {
|
||||
auto (C::*function)(P...) -> R;
|
||||
C* object;
|
||||
auto operator()(P... p) const -> R { return (object->*function)(forward<P>(p)...); }
|
||||
auto copy() const -> container* { return new member(function, object); }
|
||||
member(auto (C::*function)(P...) -> R, C* object) : function(function), object(object) {}
|
||||
};
|
||||
|
||||
template<typename L> struct lambda : container {
|
||||
mutable L object;
|
||||
auto operator()(P... p) const -> R { return object(forward<P>(p)...); }
|
||||
auto copy() const -> container* { return new lambda(object); }
|
||||
lambda(const L& object) : object(object) {}
|
||||
};
|
||||
};
|
||||
|
||||
}
|
40
hash/crc16.hpp
Normal file
40
hash/crc16.hpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/range.hpp>
|
||||
|
||||
namespace nall {
|
||||
struct string;
|
||||
namespace Hash {
|
||||
|
||||
struct CRC16 {
|
||||
CRC16() { reset(); }
|
||||
CRC16(const void* values, unsigned size) : CRC16() { data(values, size); }
|
||||
|
||||
auto reset() -> void {
|
||||
checksum = ~0;
|
||||
}
|
||||
|
||||
auto data(uint8_t value) -> void {
|
||||
for(auto n : range(8)) {
|
||||
if((checksum & 1) ^ (value & 1)) checksum = (checksum >> 1) ^ 0x8408;
|
||||
else checksum >>= 1;
|
||||
value >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto data(const void* values, unsigned size) -> void {
|
||||
auto p = (const uint8_t*)values;
|
||||
while(size--) data(*p++);
|
||||
}
|
||||
|
||||
auto value() -> uint16_t {
|
||||
return ~checksum;
|
||||
}
|
||||
|
||||
inline auto digest() -> string;
|
||||
|
||||
private:
|
||||
uint16_t checksum;
|
||||
};
|
||||
|
||||
}}
|
82
hash/crc32.hpp
Normal file
82
hash/crc32.hpp
Normal file
|
@ -0,0 +1,82 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/range.hpp>
|
||||
|
||||
namespace nall {
|
||||
struct string;
|
||||
namespace Hash {
|
||||
|
||||
const uint32_t _crc32_table[256] = {
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
|
||||
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
|
||||
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
|
||||
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
|
||||
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
|
||||
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
|
||||
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
|
||||
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
|
||||
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
|
||||
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
|
||||
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
||||
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
||||
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
|
||||
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
|
||||
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
||||
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
|
||||
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
|
||||
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
|
||||
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
|
||||
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
||||
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
||||
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
|
||||
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
||||
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
|
||||
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
|
||||
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
|
||||
};
|
||||
|
||||
struct CRC32 {
|
||||
CRC32() { reset(); }
|
||||
CRC32(const void* values, unsigned size) : CRC32() { data(values, size); }
|
||||
|
||||
auto reset() -> void {
|
||||
checksum = ~0;
|
||||
}
|
||||
|
||||
auto data(uint8_t value) -> void {
|
||||
checksum = ((checksum >> 8) & 0xffffff) ^ _crc32_table[(checksum ^ value) & 0xff];
|
||||
}
|
||||
|
||||
auto data(const void* values, unsigned size) -> void {
|
||||
auto p = (const uint8_t*)values;
|
||||
while(size--) data(*p++);
|
||||
}
|
||||
|
||||
auto value() const -> uint32_t {
|
||||
return ~checksum;
|
||||
}
|
||||
|
||||
inline auto digest() -> string;
|
||||
|
||||
private:
|
||||
uint32_t checksum;
|
||||
};
|
||||
|
||||
}}
|
108
hash/sha256.hpp
Normal file
108
hash/sha256.hpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/range.hpp>
|
||||
|
||||
namespace nall {
|
||||
struct string;
|
||||
namespace Hash {
|
||||
|
||||
struct SHA256 {
|
||||
SHA256() { reset(); }
|
||||
SHA256(const void* values, unsigned size) : SHA256() { data(values, size); }
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto n : input) n = 0;
|
||||
for(auto n : w) n = 0;
|
||||
for(auto n : range(8)) h[n] = square(n);
|
||||
queued = length = 0;
|
||||
}
|
||||
|
||||
auto data(uint8_t value) -> void {
|
||||
byte(value);
|
||||
length++;
|
||||
}
|
||||
|
||||
auto data(const void* values, unsigned size) -> void {
|
||||
length += size;
|
||||
auto p = (const uint8_t*)values;
|
||||
while(size--) byte(*p++);
|
||||
}
|
||||
|
||||
auto value() const -> vector<uint8_t> {
|
||||
SHA256 self(*this);
|
||||
self.finish();
|
||||
vector<uint8_t> result;
|
||||
for(auto n : range(32)) result.append(self.h[n >> 2] >> ((3 - (n & 3)) << 3));
|
||||
return result;
|
||||
}
|
||||
|
||||
inline auto digest() const -> nall::string;
|
||||
|
||||
private:
|
||||
auto byte(uint8_t value) -> void {
|
||||
auto shift = (3 - (queued & 3)) * 8;
|
||||
input[queued >> 2] &= ~(0xff << shift);
|
||||
input[queued >> 2] |= (value << shift);
|
||||
if(++queued == 64) block(), queued = 0;
|
||||
}
|
||||
|
||||
auto block() -> void {
|
||||
for(auto n : range(16)) w[n] = input[n];
|
||||
for(auto n : range(16, 64)) {
|
||||
uint32_t a = ror(w[n - 15], 7) ^ ror(w[n - 15], 18) ^ (w[n - 15] >> 3);
|
||||
uint32_t b = ror(w[n - 2], 17) ^ ror(w[n - 2], 19) ^ (w[n - 2] >> 10);
|
||||
w[n] = w[n - 16] + w[n - 7] + a + b;
|
||||
}
|
||||
uint32_t t[8];
|
||||
for(auto n : range(8)) t[n] = h[n];
|
||||
for(auto n : range(64)) {
|
||||
uint32_t a = ror(t[0], 2) ^ ror(t[0], 13) ^ ror(t[0], 22);
|
||||
uint32_t b = ror(t[4], 6) ^ ror(t[4], 11) ^ ror(t[4], 25);
|
||||
uint32_t c = (t[0] & t[1]) ^ (t[0] & t[2]) ^ (t[1] & t[2]);
|
||||
uint32_t d = (t[4] & t[5]) ^ (~t[4] & t[6]);
|
||||
uint32_t e = t[7] + w[n] + cube(n) + b + d;
|
||||
t[7] = t[6]; t[6] = t[5]; t[5] = t[4]; t[4] = t[3] + e;
|
||||
t[3] = t[2]; t[2] = t[1]; t[1] = t[0]; t[0] = a + c + e;
|
||||
}
|
||||
for(auto n : range(8)) h[n] += t[n];
|
||||
}
|
||||
|
||||
auto finish() -> void {
|
||||
byte(0x80);
|
||||
while(queued != 56) byte(0x00);
|
||||
for(auto n : range(8)) byte((length << 3) >> ((7 - n) << 3));
|
||||
}
|
||||
|
||||
auto ror(uint32_t x, uint32_t n) -> uint32_t {
|
||||
return (x >> n) | (x << 32 - n);
|
||||
}
|
||||
|
||||
auto square(unsigned n) -> uint32_t {
|
||||
static const uint32_t value[8] = {
|
||||
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
||||
};
|
||||
return value[n];
|
||||
}
|
||||
|
||||
auto cube(unsigned n) -> uint32_t {
|
||||
static const uint32_t value[64] = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
||||
};
|
||||
return value[n];
|
||||
}
|
||||
|
||||
uint32_t input[16];
|
||||
uint32_t queued;
|
||||
uint32_t w[64];
|
||||
uint32_t h[8];
|
||||
uint64_t length;
|
||||
};
|
||||
|
||||
}}
|
133
hashset.hpp
Normal file
133
hashset.hpp
Normal file
|
@ -0,0 +1,133 @@
|
|||
#pragma once
|
||||
|
||||
//hashset
|
||||
//
|
||||
//search: O(1) average; O(n) worst
|
||||
//insert: O(1) average; O(n) worst
|
||||
//remove: O(1) average; O(n) worst
|
||||
//
|
||||
//requirements:
|
||||
// auto T::hash() const -> unsigned;
|
||||
// auto T::operator==(const T&) const -> bool;
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename T>
|
||||
struct hashset {
|
||||
hashset() = default;
|
||||
hashset(unsigned length) : length(bit::round(length)) {}
|
||||
hashset(const hashset& source) { operator=(source); }
|
||||
hashset(hashset&& source) { operator=(move(source)); }
|
||||
~hashset() { reset(); }
|
||||
|
||||
auto operator=(const hashset& source) -> hashset& {
|
||||
reset();
|
||||
if(source.pool) {
|
||||
for(unsigned n = 0; n < source.count; n++) {
|
||||
insert(*source.pool[n]);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator=(hashset&& source) -> hashset& {
|
||||
reset();
|
||||
pool = source.pool;
|
||||
length = source.length;
|
||||
count = source.count;
|
||||
source.pool = nullptr;
|
||||
source.length = 8;
|
||||
source.count = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto capacity() const -> unsigned { return length; }
|
||||
auto size() const -> unsigned { return count; }
|
||||
auto empty() const -> bool { return count == 0; }
|
||||
|
||||
auto reset() -> void {
|
||||
if(pool) {
|
||||
for(unsigned n = 0; n < length; n++) {
|
||||
if(pool[n]) {
|
||||
delete pool[n];
|
||||
pool[n] = nullptr;
|
||||
}
|
||||
}
|
||||
delete pool;
|
||||
pool = nullptr;
|
||||
}
|
||||
length = 8;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
auto reserve(unsigned size) -> void {
|
||||
//ensure all items will fit into pool (with <= 50% load) and amortize growth
|
||||
size = bit::round(max(size, count << 1));
|
||||
T** copy = new T*[size]();
|
||||
|
||||
if(pool) {
|
||||
for(unsigned n = 0; n < length; n++) {
|
||||
if(pool[n]) {
|
||||
unsigned hash = (*pool[n]).hash() & (size - 1);
|
||||
while(copy[hash]) if(++hash >= size) hash = 0;
|
||||
copy[hash] = pool[n];
|
||||
pool[n] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete pool;
|
||||
pool = copy;
|
||||
length = size;
|
||||
}
|
||||
|
||||
auto find(const T& value) -> maybe<T&> {
|
||||
if(!pool) return nothing;
|
||||
|
||||
unsigned hash = value.hash() & (length - 1);
|
||||
while(pool[hash]) {
|
||||
if(value == *pool[hash]) return *pool[hash];
|
||||
if(++hash >= length) hash = 0;
|
||||
}
|
||||
|
||||
return nothing;
|
||||
}
|
||||
|
||||
auto insert(const T& value) -> maybe<T&> {
|
||||
if(!pool) pool = new T*[length]();
|
||||
|
||||
//double pool size when load is >= 50%
|
||||
if(count >= (length >> 1)) reserve(length << 1);
|
||||
count++;
|
||||
|
||||
unsigned hash = value.hash() & (length - 1);
|
||||
while(pool[hash]) if(++hash >= length) hash = 0;
|
||||
pool[hash] = new T(value);
|
||||
|
||||
return *pool[hash];
|
||||
}
|
||||
|
||||
auto remove(const T& value) -> bool {
|
||||
if(!pool) return false;
|
||||
|
||||
unsigned hash = value.hash() & (length - 1);
|
||||
while(pool[hash]) {
|
||||
if(value == *pool[hash]) {
|
||||
delete pool[hash];
|
||||
pool[hash] = nullptr;
|
||||
count--;
|
||||
return true;
|
||||
}
|
||||
if(++hash >= length) hash = 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
T** pool = nullptr;
|
||||
unsigned length = 8; //length of pool
|
||||
unsigned count = 0; //number of objects inside of the pool
|
||||
};
|
||||
|
||||
}
|
107
hid.hpp
Normal file
107
hid.hpp
Normal file
|
@ -0,0 +1,107 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall { namespace HID {
|
||||
|
||||
struct Input {
|
||||
Input(const string& name) : _name(name) {}
|
||||
|
||||
auto name() const -> string { return _name; }
|
||||
auto value() const -> int16_t { return _value; }
|
||||
auto setValue(int16_t value) -> void { _value = value; }
|
||||
|
||||
private:
|
||||
string _name;
|
||||
int16_t _value = 0;
|
||||
friend class Group;
|
||||
};
|
||||
|
||||
struct Group : vector<Input> {
|
||||
Group(const string& name) : _name(name) {}
|
||||
|
||||
auto name() const -> string { return _name; }
|
||||
auto input(unsigned id) -> Input& { return operator[](id); }
|
||||
auto append(const string& name) -> void { vector::append({name}); }
|
||||
|
||||
auto find(const string& name) const -> maybe<unsigned> {
|
||||
for(auto id : range(size())) {
|
||||
if(operator[](id)._name == name) return id;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
private:
|
||||
string _name;
|
||||
friend class Device;
|
||||
};
|
||||
|
||||
struct Device : vector<Group> {
|
||||
Device(const string& name) : _name(name) {}
|
||||
|
||||
auto pathID() const -> uint32_t { return (uint32_t)(_id >> 32); }
|
||||
auto deviceID() const -> uint32_t { return (uint32_t)(_id >> 0); }
|
||||
auto vendorID() const -> uint16_t { return (uint16_t)(_id >> 16); }
|
||||
auto productID() const -> uint16_t { return (uint16_t)(_id >> 0); }
|
||||
|
||||
virtual auto isNull() const -> bool { return false; }
|
||||
virtual auto isKeyboard() const -> bool { return false; }
|
||||
virtual auto isMouse() const -> bool { return false; }
|
||||
virtual auto isJoypad() const -> bool { return false; }
|
||||
|
||||
auto name() const -> string { return _name; }
|
||||
auto id() const -> uint64_t { return _id; }
|
||||
auto setID(uint64_t id) -> void { _id = id; }
|
||||
auto group(unsigned id) -> Group& { return operator[](id); }
|
||||
auto append(const string& name) -> void { vector::append({name}); }
|
||||
|
||||
auto find(const string& name) const -> maybe<unsigned> {
|
||||
for(auto id : range(size())) {
|
||||
if(operator[](id)._name == name) return id;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
private:
|
||||
string _name;
|
||||
uint64_t _id = 0;
|
||||
};
|
||||
|
||||
struct Null : Device {
|
||||
Null() : Device("Null") {}
|
||||
auto isNull() const -> bool { return true; }
|
||||
};
|
||||
|
||||
struct Keyboard : Device {
|
||||
enum GroupID : unsigned { Button };
|
||||
|
||||
Keyboard() : Device("Keyboard") { append("Button"); }
|
||||
auto isKeyboard() const -> bool { return true; }
|
||||
auto buttons() -> Group& { return group(GroupID::Button); }
|
||||
};
|
||||
|
||||
struct Mouse : Device {
|
||||
enum GroupID : unsigned { Axis, Button };
|
||||
|
||||
Mouse() : Device("Mouse") { append("Axis"), append("Button"); }
|
||||
auto isMouse() const -> bool { return true; }
|
||||
auto axes() -> Group& { return group(GroupID::Axis); }
|
||||
auto buttons() -> Group& { return group(GroupID::Button); }
|
||||
};
|
||||
|
||||
struct Joypad : Device {
|
||||
enum GroupID : unsigned { Axis, Hat, Trigger, Button };
|
||||
|
||||
Joypad() : Device("Joypad") { append("Axis"), append("Hat"), append("Trigger"), append("Button"); }
|
||||
auto isJoypad() const -> bool { return true; }
|
||||
auto axes() -> Group& { return group(GroupID::Axis); }
|
||||
auto hats() -> Group& { return group(GroupID::Hat); }
|
||||
auto triggers() -> Group& { return group(GroupID::Trigger); }
|
||||
auto buttons() -> Group& { return group(GroupID::Button); }
|
||||
|
||||
auto rumble() const -> bool { return _rumble; }
|
||||
auto setRumble(bool rumble) -> void { _rumble = rumble; }
|
||||
|
||||
private:
|
||||
bool _rumble = false;
|
||||
};
|
||||
|
||||
}}
|
56
http/client.hpp
Normal file
56
http/client.hpp
Normal file
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/http/role.hpp>
|
||||
|
||||
namespace nall { namespace HTTP {
|
||||
|
||||
struct Client : Role {
|
||||
inline auto open(const string& hostname, unsigned port = 80) -> bool;
|
||||
inline auto upload(const Request& request) -> bool;
|
||||
inline auto download(const Request& request) -> Response;
|
||||
inline auto close() -> void;
|
||||
~Client() { close(); }
|
||||
|
||||
private:
|
||||
signed fd = -1;
|
||||
addrinfo* info = nullptr;
|
||||
};
|
||||
|
||||
auto Client::open(const string& hostname, unsigned port) -> bool {
|
||||
addrinfo hint = {0};
|
||||
hint.ai_family = AF_UNSPEC;
|
||||
hint.ai_socktype = SOCK_STREAM;
|
||||
hint.ai_flags = AI_ADDRCONFIG;
|
||||
|
||||
if(getaddrinfo(hostname, string{port}, &hint, &info) != 0) return close(), false;
|
||||
|
||||
fd = socket(info->ai_family, info->ai_socktype, info->ai_protocol);
|
||||
if(fd < 0) return close(), false;
|
||||
|
||||
if(connect(fd, info->ai_addr, info->ai_addrlen) < 0) return close(), false;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Client::upload(const Request& request) -> bool {
|
||||
return Role::upload(fd, request);
|
||||
}
|
||||
|
||||
auto Client::download(const Request& request) -> Response {
|
||||
Response response(request);
|
||||
Role::download(fd, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
auto Client::close() -> void {
|
||||
if(fd) {
|
||||
::close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
|
||||
if(info) {
|
||||
freeaddrinfo(info);
|
||||
info = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
}}
|
99
http/message.hpp
Normal file
99
http/message.hpp
Normal file
|
@ -0,0 +1,99 @@
|
|||
#pragma once
|
||||
|
||||
//httpMessage: base class for httpRequest and httpResponse
|
||||
//provides shared functionality
|
||||
|
||||
namespace nall { namespace HTTP {
|
||||
|
||||
struct Variable {
|
||||
string name;
|
||||
string value;
|
||||
};
|
||||
|
||||
struct SharedVariable {
|
||||
SharedVariable(const string& name = "", const string& value = "") : shared(new Variable{name, value}) {}
|
||||
|
||||
explicit operator bool() const { return (bool)shared->name; }
|
||||
auto operator()() const { return shared->value; }
|
||||
auto& operator=(const string& value) { shared->value = value; return *this; }
|
||||
|
||||
auto name() const { return shared->name; }
|
||||
auto value() const { return shared->value; }
|
||||
|
||||
auto& setName(const string& name) { shared->name = name; return *this; }
|
||||
auto& setValue(const string& value = "") { shared->value = value; return *this; }
|
||||
|
||||
shared_pointer<Variable> shared;
|
||||
};
|
||||
|
||||
struct Variables {
|
||||
auto operator[](const string& name) const -> SharedVariable {
|
||||
for(auto& variable : variables) {
|
||||
if(variable.shared->name.iequals(name)) return variable;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
auto operator()(const string& name) -> SharedVariable {
|
||||
for(auto& variable : variables) {
|
||||
if(variable.shared->name.iequals(name)) return variable;
|
||||
}
|
||||
return append(name);
|
||||
}
|
||||
|
||||
auto find(const string& name) const -> vector<SharedVariable> {
|
||||
vector<SharedVariable> result;
|
||||
for(auto& variable : variables) {
|
||||
if(variable.shared->name.iequals(name)) result.append(variable);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto assign(const string& name, const string& value = "") -> SharedVariable {
|
||||
for(auto& variable : variables) {
|
||||
if(variable.shared->name.iequals(name)) {
|
||||
variable.shared->value = value;
|
||||
return variable;
|
||||
}
|
||||
}
|
||||
return append(name, value);
|
||||
}
|
||||
|
||||
auto append(const string& name, const string& value = "") -> SharedVariable {
|
||||
SharedVariable variable{name, value};
|
||||
variables.append(variable);
|
||||
return variable;
|
||||
}
|
||||
|
||||
auto remove(const string& name) -> void {
|
||||
for(auto n : rrange(variables)) {
|
||||
if(variables[n].shared->name.iequals(name)) variables.remove(n);
|
||||
}
|
||||
}
|
||||
|
||||
auto size() const { return variables.size(); }
|
||||
auto begin() const { return variables.begin(); }
|
||||
auto end() const { return variables.end(); }
|
||||
auto begin() { return variables.begin(); }
|
||||
auto end() { return variables.end(); }
|
||||
|
||||
vector<SharedVariable> variables;
|
||||
};
|
||||
|
||||
struct Message {
|
||||
using type = Message;
|
||||
|
||||
virtual auto head(const function<bool (const uint8_t* data, unsigned size)>& callback) const -> bool = 0;
|
||||
virtual auto setHead() -> bool = 0;
|
||||
|
||||
virtual auto body(const function<bool (const uint8_t* data, unsigned size)>& callback) const -> bool = 0;
|
||||
virtual auto setBody() -> bool = 0;
|
||||
|
||||
Variables header;
|
||||
|
||||
//private:
|
||||
string _head;
|
||||
string _body;
|
||||
};
|
||||
|
||||
}}
|
184
http/request.hpp
Normal file
184
http/request.hpp
Normal file
|
@ -0,0 +1,184 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/decode/url.hpp>
|
||||
#include <nall/encode/url.hpp>
|
||||
#include <nall/http/message.hpp>
|
||||
|
||||
namespace nall { namespace HTTP {
|
||||
|
||||
struct Request : Message {
|
||||
using type = Request;
|
||||
|
||||
enum class RequestType : unsigned { None, Head, Get, Post };
|
||||
|
||||
explicit operator bool() const { return requestType() != RequestType::None; }
|
||||
|
||||
inline auto head(const function<bool (const uint8_t* data, unsigned size)>& callback) const -> bool override;
|
||||
inline auto setHead() -> bool override;
|
||||
|
||||
inline auto body(const function<bool (const uint8_t* data, unsigned size)>& callback) const -> bool override;
|
||||
inline auto setBody() -> bool override;
|
||||
|
||||
auto ipv4() const -> bool { return _ipv6 == false; }
|
||||
auto ipv6() const -> bool { return _ipv6 == true; }
|
||||
auto ip() const -> string { return _ip; }
|
||||
|
||||
auto requestType() const -> RequestType { return _requestType; }
|
||||
auto setRequestType(RequestType value) -> void { _requestType = value; }
|
||||
|
||||
auto path() const -> string { return _path; }
|
||||
auto setPath(const string& value) -> void { _path = value; }
|
||||
|
||||
Variables cookie;
|
||||
Variables get;
|
||||
Variables post;
|
||||
|
||||
//private:
|
||||
bool _ipv6 = false;
|
||||
string _ip;
|
||||
RequestType _requestType = RequestType::None;
|
||||
string _path;
|
||||
};
|
||||
|
||||
auto Request::head(const function<bool (const uint8_t*, unsigned)>& callback) const -> bool {
|
||||
if(!callback) return false;
|
||||
string output;
|
||||
|
||||
string request = path();
|
||||
if(get.size()) {
|
||||
request.append("?");
|
||||
for(auto& variable : get) {
|
||||
request.append(Encode::URL(variable.name()), "=", Encode::URL(variable.value()), "&");
|
||||
}
|
||||
request.rtrim("&", 1L);
|
||||
}
|
||||
|
||||
switch(requestType()) {
|
||||
case RequestType::Head: output.append("HEAD ", request, " HTTP/1.1\r\n"); break;
|
||||
case RequestType::Get : output.append("GET ", request, " HTTP/1.1\r\n"); break;
|
||||
case RequestType::Post: output.append("POST ", request, " HTTP/1.1\r\n"); break;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
for(auto& variable : header) {
|
||||
output.append(variable.name(), ": ", variable.value(), "\r\n");
|
||||
}
|
||||
output.append("\r\n");
|
||||
|
||||
return callback(output.binary(), output.size());
|
||||
}
|
||||
|
||||
auto Request::setHead() -> bool {
|
||||
lstring headers = _head.split("\n");
|
||||
string request = headers.takeFirst().rtrim("\r", 1L);
|
||||
string requestHost;
|
||||
|
||||
if(request.iendsWith(" HTTP/1.0")) request.irtrim(" HTTP/1.0", 1L);
|
||||
else if(request.iendsWith(" HTTP/1.1")) request.irtrim(" HTTP/1.1", 1L);
|
||||
else return false;
|
||||
|
||||
if(request.ibeginsWith("HEAD ")) request.iltrim("HEAD ", 1L), setRequestType(RequestType::Head);
|
||||
else if(request.ibeginsWith("GET " )) request.iltrim("GET ", 1L), setRequestType(RequestType::Get );
|
||||
else if(request.ibeginsWith("POST ")) request.iltrim("POST ", 1L), setRequestType(RequestType::Post);
|
||||
else return false;
|
||||
|
||||
//decode absolute URIs
|
||||
request.strip().iltrim("http://", 1L);
|
||||
if(!request.beginsWith("/")) {
|
||||
lstring components = request.split("/", 1L);
|
||||
requestHost = components(0);
|
||||
request = {"/", components(1)};
|
||||
}
|
||||
|
||||
lstring components = request.split("?", 1L);
|
||||
setPath(components(0));
|
||||
|
||||
if(auto queryString = components(1)) {
|
||||
for(auto& block : queryString.split("&")) {
|
||||
auto p = block.split("=", 1L);
|
||||
auto name = Decode::URL(p(0));
|
||||
auto value = Decode::URL(p(1));
|
||||
if(name) get.append(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& header : headers) {
|
||||
if(header.beginsWith(" ") || header.beginsWith("\t")) continue;
|
||||
auto part = header.split(":", 1L).strip();
|
||||
if(!part[0] || part.size() != 2) continue;
|
||||
this->header.append(part[0], part[1]);
|
||||
|
||||
if(part[0].iequals("Cookie")) {
|
||||
for(auto& block : part[1].split(";")) {
|
||||
auto p = block.split("=", 1L).strip();
|
||||
auto name = p(0);
|
||||
auto value = p(1).trim("\"", "\"", 1L);
|
||||
if(name) cookie.append(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(requestHost) header.assign("Host", requestHost); //request URI overrides host header
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Request::body(const function<bool (const uint8_t*, unsigned)>& callback) const -> bool {
|
||||
if(!callback) return false;
|
||||
|
||||
if(_body) {
|
||||
return callback(_body.binary(), _body.size());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Request::setBody() -> bool {
|
||||
if(requestType() == RequestType::Post) {
|
||||
auto contentType = header["Content-Type"].value();
|
||||
if(contentType.iequals("application/x-www-form-urlencoded")) {
|
||||
for(auto& block : _body.split("&")) {
|
||||
auto p = block.rtrim("\r").split("=", 1L);
|
||||
auto name = Decode::URL(p(0));
|
||||
auto value = Decode::URL(p(1));
|
||||
if(name) post.append(name, value);
|
||||
}
|
||||
} else if(contentType.imatch("multipart/form-data; boundary=?*")) {
|
||||
auto boundary = contentType.iltrim("multipart/form-data; boundary=", 1L).trim("\"", "\"", 1L);
|
||||
auto blocks = _body.split({"--", boundary}, 1024L); //limit blocks to prevent memory exhaustion
|
||||
for(auto& block : blocks) block.trim("\r\n", "\r\n", 1L);
|
||||
if(blocks.size() < 2 || (blocks.takeFirst(), !blocks.takeLast().beginsWith("--"))) return false;
|
||||
for(auto& block : blocks) {
|
||||
string name;
|
||||
string filename;
|
||||
string contentType;
|
||||
|
||||
auto segments = block.split("\r\n\r\n", 1L);
|
||||
for(auto& segment : segments(0).split("\r\n")) {
|
||||
auto statement = segment.split(":", 1L);
|
||||
if(statement(0).ibeginsWith("Content-Disposition")) {
|
||||
for(auto& component : statement(1).split(";")) {
|
||||
auto part = component.split("=", 1L).strip();
|
||||
if(part(0).iequals("name")) {
|
||||
name = part(1).trim("\"", "\"", 1L);
|
||||
} else if(part(0).iequals("filename")) {
|
||||
filename = part(1).trim("\"", "\"", 1L);
|
||||
}
|
||||
}
|
||||
} else if(statement(0).ibeginsWith("Content-Type")) {
|
||||
contentType = statement(1).strip();
|
||||
}
|
||||
}
|
||||
|
||||
if(name) {
|
||||
post.append(name, segments(1));
|
||||
post.append({name, ".filename"}, filename);
|
||||
post.append({name, ".content-type"}, contentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}}
|
246
http/response.hpp
Normal file
246
http/response.hpp
Normal file
|
@ -0,0 +1,246 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/http/message.hpp>
|
||||
|
||||
namespace nall { namespace HTTP {
|
||||
|
||||
struct Response : Message {
|
||||
using type = Response;
|
||||
|
||||
Response() = default;
|
||||
Response(const Request& request) { setRequest(request); }
|
||||
|
||||
explicit operator bool() const { return responseType() != 0; }
|
||||
auto operator()(unsigned responseType) -> type& { return setResponseType(responseType); }
|
||||
|
||||
inline auto head(const function<bool (const uint8_t* data, unsigned size)>& callback) const -> bool override;
|
||||
inline auto setHead() -> bool override;
|
||||
|
||||
inline auto body(const function<bool (const uint8_t* data, unsigned size)>& callback) const -> bool override;
|
||||
inline auto setBody() -> bool override;
|
||||
|
||||
auto request() const -> const Request* { return _request; }
|
||||
auto setRequest(const Request& value) -> type& { _request = &value; return *this; }
|
||||
|
||||
auto responseType() const -> unsigned { return _responseType; }
|
||||
auto setResponseType(unsigned value) -> type& { _responseType = value; return *this; }
|
||||
|
||||
auto hasData() const -> bool { return (bool)_data; }
|
||||
auto data() const -> const vector<uint8_t>& { return _data; }
|
||||
inline auto setData(const vector<uint8_t>& value) -> type&;
|
||||
|
||||
auto hasFile() const -> bool { return (bool)_file; }
|
||||
auto file() const -> const string& { return _file; }
|
||||
inline auto setFile(const string& value) -> type&;
|
||||
|
||||
auto hasText() const -> bool { return (bool)_text; }
|
||||
auto text() const -> const string& { return _text; }
|
||||
inline auto setText(const string& value) -> type&;
|
||||
|
||||
inline auto hasBody() const -> bool;
|
||||
inline auto findContentLength() const -> unsigned;
|
||||
inline auto findContentType() const -> string;
|
||||
inline auto findContentType(const string& suffix) const -> string;
|
||||
inline auto findResponseType() const -> string;
|
||||
inline auto setFileETag() -> void;
|
||||
|
||||
const Request* _request = nullptr;
|
||||
unsigned _responseType = 0;
|
||||
vector<uint8_t> _data;
|
||||
string _file;
|
||||
string _text;
|
||||
};
|
||||
|
||||
auto Response::head(const function<bool (const uint8_t*, unsigned)>& callback) const -> bool {
|
||||
if(!callback) return false;
|
||||
string output;
|
||||
|
||||
if(auto request = this->request()) {
|
||||
if(auto eTag = header["ETag"]) {
|
||||
if(eTag.value() == request->header["If-None-Match"].value()) {
|
||||
output.append("HTTP/1.1 304 Not Modified\r\n");
|
||||
output.append("Connection: close\r\n");
|
||||
output.append("\r\n");
|
||||
return callback(output.binary(), output.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.append("HTTP/1.1 ", findResponseType(), "\r\n");
|
||||
for(auto& variable : header) {
|
||||
output.append(variable.name(), ": ", variable.value(), "\r\n");
|
||||
}
|
||||
if(hasBody()) {
|
||||
if(!header["Content-Length"] && !header["Transfer-Encoding"].value().iequals("chunked")) {
|
||||
output.append("Content-Length: ", findContentLength(), "\r\n");
|
||||
}
|
||||
if(!header["Content-Type"]) {
|
||||
output.append("Content-Type: ", findContentType(), "\r\n");
|
||||
}
|
||||
}
|
||||
if(!header["Connection"]) {
|
||||
output.append("Connection: close\r\n");
|
||||
}
|
||||
output.append("\r\n");
|
||||
|
||||
return callback(output.binary(), output.size());
|
||||
}
|
||||
|
||||
auto Response::setHead() -> bool {
|
||||
lstring headers = _head.split("\n");
|
||||
string response = headers.takeFirst().rtrim("\r");
|
||||
|
||||
if(response.ibeginsWith("HTTP/1.0 ")) response.iltrim("HTTP/1.0 ", 1L);
|
||||
else if(response.ibeginsWith("HTTP/1.1 ")) response.iltrim("HTTP/1.1 ", 1L);
|
||||
else return false;
|
||||
|
||||
setResponseType(natural(response));
|
||||
|
||||
for(auto& header : headers) {
|
||||
if(header.beginsWith(" ") || header.beginsWith("\t")) continue;
|
||||
lstring variable = header.split(":", 1L).strip();
|
||||
if(variable.size() != 2) continue;
|
||||
this->header.append(variable[0], variable[1]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Response::body(const function<bool (const uint8_t*, unsigned)>& callback) const -> bool {
|
||||
if(!callback) return false;
|
||||
if(!hasBody()) return true;
|
||||
bool chunked = header["Transfer-Encoding"].value() == "chunked";
|
||||
|
||||
if(chunked) {
|
||||
string prefix = {hex(findContentLength()), "\r\n"};
|
||||
if(!callback(prefix.binary(), prefix.size())) return false;
|
||||
}
|
||||
|
||||
if(_body) {
|
||||
if(!callback(_body.binary(), _body.size())) return false;
|
||||
} else if(hasData()) {
|
||||
if(!callback(data().data(), data().size())) return false;
|
||||
} else if(hasFile()) {
|
||||
filemap map(file(), filemap::mode::read);
|
||||
if(!callback(map.data(), map.size())) return false;
|
||||
} else if(hasText()) {
|
||||
if(!callback(text().binary(), text().size())) return false;
|
||||
} else {
|
||||
string response = findResponseType();
|
||||
if(!callback(response.binary(), response.size())) return false;
|
||||
}
|
||||
|
||||
if(chunked) {
|
||||
string suffix = {"\r\n0\r\n\r\n"};
|
||||
if(!callback(suffix.binary(), suffix.size())) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Response::setBody() -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Response::hasBody() const -> bool {
|
||||
if(auto request = this->request()) {
|
||||
if(request->requestType() == Request::RequestType::Head) return false;
|
||||
}
|
||||
if(responseType() == 301) return false;
|
||||
if(responseType() == 302) return false;
|
||||
if(responseType() == 303) return false;
|
||||
if(responseType() == 304) return false;
|
||||
if(responseType() == 307) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Response::findContentLength() const -> unsigned {
|
||||
if(auto contentLength = header["Content-Length"]) return contentLength.value().natural();
|
||||
if(_body) return _body.size();
|
||||
if(hasData()) return data().size();
|
||||
if(hasFile()) return file::size(file());
|
||||
if(hasText()) return text().size();
|
||||
return findResponseType().size();
|
||||
}
|
||||
|
||||
auto Response::findContentType() const -> string {
|
||||
if(auto contentType = header["Content-Type"]) return contentType.value();
|
||||
if(hasData()) return "application/octet-stream";
|
||||
if(hasFile()) return findContentType(suffixname(file()));
|
||||
return "text/html; charset=utf-8";
|
||||
}
|
||||
|
||||
auto Response::findContentType(const string& s) const -> string {
|
||||
if(s == ".7z" ) return "application/x-7z-compressed";
|
||||
if(s == ".avi" ) return "video/avi";
|
||||
if(s == ".bml" ) return "text/plain; charset=utf-8";
|
||||
if(s == ".bz2" ) return "application/x-bzip2";
|
||||
if(s == ".css" ) return "text/css; charset=utf-8";
|
||||
if(s == ".gif" ) return "image/gif";
|
||||
if(s == ".gz" ) return "application/gzip";
|
||||
if(s == ".htm" ) return "text/html; charset=utf-8";
|
||||
if(s == ".html") return "text/html; charset=utf-8";
|
||||
if(s == ".jpg" ) return "image/jpeg";
|
||||
if(s == ".jpeg") return "image/jpeg";
|
||||
if(s == ".js" ) return "application/javascript";
|
||||
if(s == ".mka" ) return "audio/x-matroska";
|
||||
if(s == ".mkv" ) return "video/x-matroska";
|
||||
if(s == ".mp3" ) return "audio/mpeg";
|
||||
if(s == ".mp4" ) return "video/mp4";
|
||||
if(s == ".mpeg") return "video/mpeg";
|
||||
if(s == ".mpg" ) return "video/mpeg";
|
||||
if(s == ".ogg" ) return "audio/ogg";
|
||||
if(s == ".pdf" ) return "application/pdf";
|
||||
if(s == ".png" ) return "image/png";
|
||||
if(s == ".rar" ) return "application/x-rar-compressed";
|
||||
if(s == ".svg" ) return "image/svg+xml";
|
||||
if(s == ".tar" ) return "application/x-tar";
|
||||
if(s == ".txt" ) return "text/plain; charset=utf-8";
|
||||
if(s == ".wav" ) return "audio/vnd.wave";
|
||||
if(s == ".webm") return "video/webm";
|
||||
if(s == ".xml" ) return "text/xml; charset=utf-8";
|
||||
if(s == ".xz" ) return "application/x-xz";
|
||||
if(s == ".zip" ) return "application/zip";
|
||||
return "application/octet-stream"; //binary
|
||||
}
|
||||
|
||||
auto Response::findResponseType() const -> string {
|
||||
switch(responseType()) {
|
||||
case 200: return "200 OK";
|
||||
case 301: return "301 Moved Permanently";
|
||||
case 302: return "302 Found";
|
||||
case 303: return "303 See Other";
|
||||
case 304: return "304 Not Modified";
|
||||
case 307: return "307 Temporary Redirect";
|
||||
case 400: return "400 Bad Request";
|
||||
case 403: return "403 Forbidden";
|
||||
case 404: return "404 Not Found";
|
||||
case 500: return "500 Internal Server Error";
|
||||
case 501: return "501 Not Implemented";
|
||||
case 503: return "503 Service Unavailable";
|
||||
}
|
||||
return "501 Not Implemented";
|
||||
}
|
||||
|
||||
auto Response::setData(const vector<uint8_t>& value) -> type& {
|
||||
_data = value;
|
||||
header.assign("Content-Length", value.size());
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto Response::setFile(const string& value) -> type& {
|
||||
_file = value;
|
||||
string eTag = {"\"", string::datetime(file::timestamp(value, file::time::modify)), "\""};
|
||||
header.assign("Content-Length", file::size(value));
|
||||
header.assign("Cache-Control", "public");
|
||||
header.assign("ETag", eTag);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto Response::setText(const string& value) -> type& {
|
||||
_text = value;
|
||||
header.assign("Content-Length", value.size());
|
||||
return *this;
|
||||
}
|
||||
|
||||
}}
|
158
http/role.hpp
Normal file
158
http/role.hpp
Normal file
|
@ -0,0 +1,158 @@
|
|||
#pragma once
|
||||
|
||||
//Role: base class for Client and Server
|
||||
//provides shared functionality
|
||||
|
||||
#include <nall/http/request.hpp>
|
||||
#include <nall/http/response.hpp>
|
||||
|
||||
namespace nall { namespace HTTP {
|
||||
|
||||
struct Role {
|
||||
struct Settings {
|
||||
signed connectionLimit = 1 * 1024; //server
|
||||
signed headSizeLimit = 16 * 1024; //client, server
|
||||
signed bodySizeLimit = 8192 * 1024; //client, server
|
||||
signed chunkSize = 32 * 1024; //client, server
|
||||
signed threadStackSize = 128 * 1024; //server
|
||||
signed timeoutReceive = 15 * 1000; //server
|
||||
signed timeoutSend = 15 * 1000; //server
|
||||
} settings;
|
||||
|
||||
inline auto configure(const string& parameters) -> bool;
|
||||
inline auto download(signed fd, Message& message) -> bool;
|
||||
inline auto upload(signed fd, const Message& message) -> bool;
|
||||
};
|
||||
|
||||
auto Role::configure(const string& parameters) -> bool {
|
||||
auto document = BML::unserialize(parameters);
|
||||
for(auto parameter : document) {
|
||||
auto name = parameter.name();
|
||||
auto value = parameter.integer();
|
||||
|
||||
if(0);
|
||||
else if(name == "connectionLimit") settings.connectionLimit = value;
|
||||
else if(name == "headSizeLimit") settings.headSizeLimit = value;
|
||||
else if(name == "bodySizeLimit") settings.bodySizeLimit = value;
|
||||
else if(name == "chunkSize") settings.chunkSize = value;
|
||||
else if(name == "threadStackSize") settings.threadStackSize = value;
|
||||
else if(name == "timeoutReceive") settings.timeoutReceive = value;
|
||||
else if(name == "timeoutSend") settings.timeoutSend = value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Role::download(signed fd, Message& message) -> bool {
|
||||
auto& head = message._head;
|
||||
auto& body = message._body;
|
||||
string chunk;
|
||||
uint8_t packet[settings.chunkSize], *p = nullptr;
|
||||
|
||||
head.reset(), head.reserve(4095);
|
||||
body.reset(), body.reserve(4095);
|
||||
|
||||
bool headReceived = false;
|
||||
bool chunked = false;
|
||||
bool chunkReceived = false;
|
||||
bool chunkFooterReceived = true;
|
||||
signed length = 0;
|
||||
signed chunkLength = 0;
|
||||
signed contentLength = 0;
|
||||
|
||||
while(true) {
|
||||
if(auto limit = settings.headSizeLimit) if(head.size() >= limit) return false;
|
||||
if(auto limit = settings.bodySizeLimit) if(body.size() >= limit) return false;
|
||||
|
||||
if(headReceived && !chunked && body.size() >= contentLength) {
|
||||
body.resize(contentLength);
|
||||
break;
|
||||
}
|
||||
|
||||
if(length == 0) {
|
||||
length = recv(fd, packet, settings.chunkSize, MSG_NOSIGNAL);
|
||||
if(length <= 0) return false;
|
||||
p = packet;
|
||||
}
|
||||
|
||||
if(!headReceived) {
|
||||
head.append((char)*p++);
|
||||
--length;
|
||||
|
||||
if(head.endsWith("\r\n\r\n") || head.endsWith("\n\n")) {
|
||||
headReceived = true;
|
||||
if(!message.setHead()) return false;
|
||||
chunked = message.header["Transfer-Encoding"].value().iequals("chunked");
|
||||
contentLength = message.header["Content-Length"].value().natural();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if(chunked && !chunkReceived) {
|
||||
char n = *p++;
|
||||
--length;
|
||||
|
||||
if(!chunkFooterReceived) {
|
||||
if(n == '\n') chunkFooterReceived = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
chunk.append(n);
|
||||
|
||||
if(chunk.endsWith("\r\n") || chunk.endsWith("\n")) {
|
||||
chunkReceived = true;
|
||||
chunkLength = hex(chunk);
|
||||
if(chunkLength == 0) break;
|
||||
chunk.reset();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!chunked) {
|
||||
body.resize(body.size() + length);
|
||||
memory::copy(body.get() + body.size() - length, p, length);
|
||||
|
||||
p += length;
|
||||
length = 0;
|
||||
} else {
|
||||
signed transferLength = min(length, chunkLength);
|
||||
body.resize(body.size() + transferLength);
|
||||
memory::copy(body.get() + body.size() - transferLength, p, transferLength);
|
||||
|
||||
p += transferLength;
|
||||
length -= transferLength;
|
||||
chunkLength -= transferLength;
|
||||
|
||||
if(chunkLength == 0) {
|
||||
chunkReceived = false;
|
||||
chunkFooterReceived = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!message.setBody()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Role::upload(signed fd, const Message& message) -> bool {
|
||||
auto transfer = [&](const uint8_t* data, unsigned size) -> bool {
|
||||
while(size) {
|
||||
signed length = send(fd, data, min(size, settings.chunkSize), MSG_NOSIGNAL);
|
||||
if(length < 0) return false;
|
||||
data += length;
|
||||
size -= length;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if(message.head([&](const uint8_t* data, unsigned size) -> bool { return transfer(data, size); })) {
|
||||
if(message.body([&](const uint8_t* data, unsigned size) -> bool { return transfer(data, size); })) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}}
|
226
http/server.hpp
Normal file
226
http/server.hpp
Normal file
|
@ -0,0 +1,226 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/service.hpp>
|
||||
#include <nall/http/role.hpp>
|
||||
|
||||
namespace nall { namespace HTTP {
|
||||
|
||||
struct Server : Role, service {
|
||||
inline auto open(unsigned port = 8080, const string& serviceName = "", const string& command = "") -> bool;
|
||||
inline auto main(const function<Response (Request&)>& function = {}) -> void;
|
||||
inline auto scan() -> string;
|
||||
inline auto close() -> void;
|
||||
~Server() { close(); }
|
||||
|
||||
private:
|
||||
function<Response (Request&)> callback;
|
||||
std::atomic<signed> connections{0};
|
||||
|
||||
signed fd4 = -1;
|
||||
signed fd6 = -1;
|
||||
struct sockaddr_in addrin4 = {0};
|
||||
struct sockaddr_in6 addrin6 = {0};
|
||||
|
||||
auto ipv4() const -> bool { return fd4 >= 0; }
|
||||
auto ipv6() const -> bool { return fd6 >= 0; }
|
||||
|
||||
auto ipv4_close() -> void { if(fd4 >= 0) ::close(fd4); fd4 = -1; }
|
||||
auto ipv6_close() -> void { if(fd6 >= 0) ::close(fd6); fd6 = -1; }
|
||||
|
||||
auto ipv4_scan() -> bool;
|
||||
auto ipv6_scan() -> bool;
|
||||
};
|
||||
|
||||
auto Server::open(unsigned port, const string& serviceName, const string& command) -> bool {
|
||||
if(serviceName) {
|
||||
if(!service::command(serviceName, command)) return false;
|
||||
}
|
||||
|
||||
fd4 = socket(AF_INET, SOCK_STREAM, 0);
|
||||
fd6 = socket(AF_INET6, SOCK_STREAM, 0);
|
||||
if(!ipv4() && !ipv6()) return false;
|
||||
|
||||
{
|
||||
#if defined(SO_RCVTIMEO)
|
||||
if(settings.timeoutReceive) {
|
||||
struct timeval rcvtimeo;
|
||||
rcvtimeo.tv_sec = settings.timeoutReceive / 1000;
|
||||
rcvtimeo.tv_usec = settings.timeoutReceive % 1000 * 1000;
|
||||
if(ipv4()) setsockopt(fd4, SOL_SOCKET, SO_RCVTIMEO, &rcvtimeo, sizeof(struct timeval));
|
||||
if(ipv6()) setsockopt(fd6, SOL_SOCKET, SO_RCVTIMEO, &rcvtimeo, sizeof(struct timeval));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(SO_SNDTIMEO)
|
||||
if(settings.timeoutSend) {
|
||||
struct timeval sndtimeo;
|
||||
sndtimeo.tv_sec = settings.timeoutSend / 1000;
|
||||
sndtimeo.tv_usec = settings.timeoutSend % 1000 * 1000;
|
||||
if(ipv4()) setsockopt(fd4, SOL_SOCKET, SO_SNDTIMEO, &sndtimeo, sizeof(struct timeval));
|
||||
if(ipv6()) setsockopt(fd6, SOL_SOCKET, SO_SNDTIMEO, &sndtimeo, sizeof(struct timeval));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(SO_NOSIGPIPE) //BSD, OSX
|
||||
signed nosigpipe = 1;
|
||||
if(ipv4()) setsockopt(fd4, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(signed));
|
||||
if(ipv6()) setsockopt(fd6, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(signed));
|
||||
#endif
|
||||
|
||||
#if defined(SO_REUSEADDR) //BSD, Linux, OSX
|
||||
signed reuseaddr = 1;
|
||||
if(ipv4()) setsockopt(fd4, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(signed));
|
||||
if(ipv6()) setsockopt(fd6, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(signed));
|
||||
#endif
|
||||
|
||||
#if defined(SO_REUSEPORT) //BSD, OSX
|
||||
signed reuseport = 1;
|
||||
if(ipv4()) setsockopt(fd4, SOL_SOCKET, SO_REUSEPORT, &reuseport, sizeof(signed));
|
||||
if(ipv6()) setsockopt(fd6, SOL_SOCKET, SO_REUSEPORT, &reuseport, sizeof(signed));
|
||||
#endif
|
||||
}
|
||||
|
||||
addrin4.sin_family = AF_INET;
|
||||
addrin4.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
addrin4.sin_port = htons(port);
|
||||
|
||||
addrin6.sin6_family = AF_INET6;
|
||||
addrin6.sin6_addr = in6addr_any;
|
||||
addrin6.sin6_port = htons(port);
|
||||
|
||||
if(bind(fd4, (struct sockaddr*)&addrin4, sizeof(addrin4)) < 0 || listen(fd4, SOMAXCONN) < 0) ipv4_close();
|
||||
if(bind(fd6, (struct sockaddr*)&addrin6, sizeof(addrin6)) < 0 || listen(fd6, SOMAXCONN) < 0) ipv6_close();
|
||||
return ipv4() || ipv6();
|
||||
}
|
||||
|
||||
auto Server::main(const function<Response (Request&)>& function) -> void {
|
||||
callback = function;
|
||||
}
|
||||
|
||||
auto Server::scan() -> string {
|
||||
if(auto command = service::receive()) return command;
|
||||
if(connections >= settings.connectionLimit) return "busy";
|
||||
if(ipv4() && ipv4_scan()) return "ok";
|
||||
if(ipv6() && ipv6_scan()) return "ok";
|
||||
return "idle";
|
||||
}
|
||||
|
||||
auto Server::ipv4_scan() -> bool {
|
||||
struct pollfd query = {0};
|
||||
query.fd = fd4;
|
||||
query.events = POLLIN;
|
||||
poll(&query, 1, 0);
|
||||
|
||||
if(query.fd == fd4 && query.revents & POLLIN) {
|
||||
++connections;
|
||||
|
||||
thread::create([&](uintptr_t) {
|
||||
thread::detach();
|
||||
|
||||
signed clientfd = -1;
|
||||
struct sockaddr_in settings = {0};
|
||||
socklen_t socklen = sizeof(sockaddr_in);
|
||||
|
||||
clientfd = accept(fd4, (struct sockaddr*)&settings, &socklen);
|
||||
if(clientfd < 0) return;
|
||||
|
||||
uint32_t ip = ntohl(settings.sin_addr.s_addr);
|
||||
|
||||
Request request;
|
||||
request._ipv6 = false;
|
||||
request._ip = {
|
||||
(uint8_t)(ip >> 24), ".",
|
||||
(uint8_t)(ip >> 16), ".",
|
||||
(uint8_t)(ip >> 8), ".",
|
||||
(uint8_t)(ip >> 0)
|
||||
};
|
||||
|
||||
if(download(clientfd, request) && callback) {
|
||||
auto response = callback(request);
|
||||
upload(clientfd, response);
|
||||
} else {
|
||||
upload(clientfd, Response()); //"501 Not Implemented"
|
||||
}
|
||||
|
||||
::close(clientfd);
|
||||
--connections;
|
||||
}, 0, settings.threadStackSize);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Server::ipv6_scan() -> bool {
|
||||
struct pollfd query = {0};
|
||||
query.fd = fd6;
|
||||
query.events = POLLIN;
|
||||
poll(&query, 1, 0);
|
||||
|
||||
if(query.fd == fd6 && query.revents & POLLIN) {
|
||||
++connections;
|
||||
|
||||
thread::create([&](uintptr_t) {
|
||||
thread::detach();
|
||||
|
||||
signed clientfd = -1;
|
||||
struct sockaddr_in6 settings = {0};
|
||||
socklen_t socklen = sizeof(sockaddr_in6);
|
||||
|
||||
clientfd = accept(fd6, (struct sockaddr*)&settings, &socklen);
|
||||
if(clientfd < 0) return;
|
||||
|
||||
uint8_t* ip = settings.sin6_addr.s6_addr;
|
||||
uint16_t ipSegment[8];
|
||||
for(auto n : range(8)) ipSegment[n] = ip[n * 2 + 0] * 256 + ip[n * 2 + 1];
|
||||
|
||||
Request request;
|
||||
request._ipv6 = true;
|
||||
//RFC5952 IPv6 encoding: the first longest 2+ consecutive zero-sequence is compressed to "::"
|
||||
signed zeroOffset = -1;
|
||||
signed zeroLength = 0;
|
||||
signed zeroCounter = 0;
|
||||
for(auto n : range(8)) {
|
||||
uint16_t value = ipSegment[n];
|
||||
if(value == 0) zeroCounter++;
|
||||
if(zeroCounter > zeroLength) {
|
||||
zeroLength = zeroCounter;
|
||||
zeroOffset = 1 + n - zeroLength;
|
||||
}
|
||||
if(value != 0) zeroCounter = 0;
|
||||
}
|
||||
if(zeroLength == 1) zeroOffset = -1;
|
||||
for(unsigned n = 0; n < 8;) {
|
||||
if(n == zeroOffset) {
|
||||
request._ip.append(n == 0 ? "::" : ":");
|
||||
n += zeroLength;
|
||||
} else {
|
||||
uint16_t value = ipSegment[n];
|
||||
request._ip.append(hex(value), n++ != 7 ? ":" : "");
|
||||
}
|
||||
}
|
||||
|
||||
if(download(clientfd, request) && callback) {
|
||||
auto response = callback(request);
|
||||
upload(clientfd, response);
|
||||
} else {
|
||||
upload(clientfd, Response()); //"501 Not Implemented"
|
||||
}
|
||||
|
||||
::close(clientfd);
|
||||
--connections;
|
||||
}, 0, settings.threadStackSize);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Server::close() -> void {
|
||||
ipv4_close();
|
||||
ipv6_close();
|
||||
}
|
||||
|
||||
}}
|
18
image.hpp
Normal file
18
image.hpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <nall/filemap.hpp>
|
||||
#include <nall/interpolation.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/decode/bmp.hpp>
|
||||
#include <nall/decode/png.hpp>
|
||||
#include <nall/image/base.hpp>
|
||||
#include <nall/image/static.hpp>
|
||||
#include <nall/image/core.hpp>
|
||||
#include <nall/image/load.hpp>
|
||||
#include <nall/image/interpolation.hpp>
|
||||
#include <nall/image/fill.hpp>
|
||||
#include <nall/image/scale.hpp>
|
||||
#include <nall/image/blend.hpp>
|
||||
#include <nall/image/utility.hpp>
|
150
image/base.hpp
Normal file
150
image/base.hpp
Normal file
|
@ -0,0 +1,150 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct image {
|
||||
enum class blend : unsigned {
|
||||
add,
|
||||
sourceAlpha, //color = sourceColor * sourceAlpha + targetColor * (1 - sourceAlpha)
|
||||
sourceColor, //color = sourceColor
|
||||
targetAlpha, //color = targetColor * targetAlpha + sourceColor * (1 - targetAlpha)
|
||||
targetColor, //color = targetColor
|
||||
};
|
||||
|
||||
struct channel {
|
||||
channel(uint64_t mask, unsigned depth, unsigned shift) : _mask(mask), _depth(depth), _shift(shift) {
|
||||
}
|
||||
|
||||
auto operator==(const channel& source) const -> bool {
|
||||
return _mask == source._mask && _depth == source._depth && _shift == source._shift;
|
||||
}
|
||||
|
||||
auto operator!=(const channel& source) const -> bool {
|
||||
return !operator==(source);
|
||||
}
|
||||
|
||||
alwaysinline auto mask() const { return _mask; }
|
||||
alwaysinline auto depth() const { return _depth; }
|
||||
alwaysinline auto shift() const { return _shift; }
|
||||
|
||||
private:
|
||||
uint64_t _mask;
|
||||
unsigned _depth;
|
||||
unsigned _shift;
|
||||
};
|
||||
|
||||
//core.hpp
|
||||
inline image(const image& source);
|
||||
inline image(image&& source);
|
||||
inline image(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask);
|
||||
inline image(const string& filename);
|
||||
inline image(const vector<uint8_t>& buffer);
|
||||
inline image(const uint8_t* data, unsigned size);
|
||||
inline image();
|
||||
inline ~image();
|
||||
|
||||
inline auto operator=(const image& source) -> image&;
|
||||
inline auto operator=(image&& source) -> image&;
|
||||
|
||||
inline explicit operator bool() const;
|
||||
inline auto operator==(const image& source) const -> bool;
|
||||
inline auto operator!=(const image& source) const -> bool;
|
||||
|
||||
inline auto read(const uint8_t* data) const -> uint64_t;
|
||||
inline auto write(uint8_t* data, uint64_t value) const -> void;
|
||||
|
||||
inline auto free() -> void;
|
||||
inline auto empty() const -> bool;
|
||||
inline auto load(const string& filename) -> bool;
|
||||
inline auto allocate(unsigned width, unsigned height) -> void;
|
||||
|
||||
//fill.hpp
|
||||
inline auto fill(uint64_t color = 0) -> void;
|
||||
inline auto gradient(uint64_t a, uint64_t b, uint64_t c, uint64_t d) -> void;
|
||||
inline auto gradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY, function<double (double, double)> callback) -> void;
|
||||
inline auto crossGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void;
|
||||
inline auto diamondGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void;
|
||||
inline auto horizontalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void;
|
||||
inline auto radialGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void;
|
||||
inline auto sphericalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void;
|
||||
inline auto squareGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void;
|
||||
inline auto verticalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void;
|
||||
|
||||
//scale.hpp
|
||||
inline auto scale(unsigned width, unsigned height, bool linear = true) -> void;
|
||||
|
||||
//blend.hpp
|
||||
inline auto impose(blend mode, unsigned targetX, unsigned targetY, image source, unsigned x, unsigned y, unsigned width, unsigned height) -> void;
|
||||
|
||||
//utility.hpp
|
||||
inline auto crop(unsigned x, unsigned y, unsigned width, unsigned height) -> bool;
|
||||
inline auto alphaBlend(uint64_t alphaColor) -> void;
|
||||
inline auto alphaMultiply() -> void;
|
||||
inline auto transform(const image& source = {}) -> void;
|
||||
inline auto transform(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask) -> void;
|
||||
|
||||
//static.hpp
|
||||
static inline auto bitDepth(uint64_t color) -> unsigned;
|
||||
static inline auto bitShift(uint64_t color) -> unsigned;
|
||||
static inline auto normalize(uint64_t color, unsigned sourceDepth, unsigned targetDepth) -> uint64_t;
|
||||
|
||||
//access
|
||||
alwaysinline auto data() { return _data; }
|
||||
alwaysinline auto data() const { return _data; }
|
||||
alwaysinline auto width() const { return _width; }
|
||||
alwaysinline auto height() const { return _height; }
|
||||
|
||||
alwaysinline auto endian() const { return _endian; }
|
||||
alwaysinline auto depth() const { return _depth; }
|
||||
alwaysinline auto stride() const { return (_depth + 7) >> 3; }
|
||||
|
||||
alwaysinline auto pitch() const { return _width * stride(); }
|
||||
alwaysinline auto size() const { return _height * pitch(); }
|
||||
|
||||
alwaysinline auto alpha() const { return _alpha; }
|
||||
alwaysinline auto red() const { return _red; }
|
||||
alwaysinline auto green() const { return _green; }
|
||||
alwaysinline auto blue() const { return _blue; }
|
||||
|
||||
private:
|
||||
//core.hpp
|
||||
inline auto allocate(unsigned width, unsigned height, unsigned stride) -> uint8_t*;
|
||||
|
||||
//scale.hpp
|
||||
inline auto scaleLinearWidth(unsigned width) -> void;
|
||||
inline auto scaleLinearHeight(unsigned height) -> void;
|
||||
inline auto scaleLinear(unsigned width, unsigned height) -> void;
|
||||
inline auto scaleNearest(unsigned width, unsigned height) -> void;
|
||||
|
||||
//load.hpp
|
||||
inline auto loadBMP(const string& filename) -> bool;
|
||||
inline auto loadBMP(const uint8_t* data, unsigned size) -> bool;
|
||||
inline auto loadPNG(const string& filename) -> bool;
|
||||
inline auto loadPNG(const uint8_t* data, unsigned size) -> bool;
|
||||
|
||||
//interpolation.hpp
|
||||
alwaysinline auto isplit(uint64_t* component, uint64_t color) -> void;
|
||||
alwaysinline auto imerge(const uint64_t* component) -> uint64_t;
|
||||
alwaysinline auto interpolate1f(uint64_t a, uint64_t b, double x) -> uint64_t;
|
||||
alwaysinline auto interpolate1f(uint64_t a, uint64_t b, uint64_t c, uint64_t d, double x, double y) -> uint64_t;
|
||||
alwaysinline auto interpolate1i(int64_t a, int64_t b, uint32_t x) -> uint64_t;
|
||||
alwaysinline auto interpolate1i(int64_t a, int64_t b, int64_t c, int64_t d, uint32_t x, uint32_t y) -> uint64_t;
|
||||
inline auto interpolate4f(uint64_t a, uint64_t b, double x) -> uint64_t;
|
||||
inline auto interpolate4f(uint64_t a, uint64_t b, uint64_t c, uint64_t d, double x, double y) -> uint64_t;
|
||||
inline auto interpolate4i(uint64_t a, uint64_t b, uint32_t x) -> uint64_t;
|
||||
inline auto interpolate4i(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint32_t x, uint32_t y) -> uint64_t;
|
||||
|
||||
uint8_t* _data = nullptr;
|
||||
unsigned _width = 0;
|
||||
unsigned _height = 0;
|
||||
|
||||
bool _endian = 0; //0 = lsb, 1 = msb
|
||||
unsigned _depth = 32;
|
||||
|
||||
channel _alpha{255u << 24, 8, 24};
|
||||
channel _red {255u << 16, 8, 16};
|
||||
channel _green{255u << 8, 8, 8};
|
||||
channel _blue {255u << 0, 8, 0};
|
||||
};
|
||||
|
||||
}
|
72
image/blend.hpp
Normal file
72
image/blend.hpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
auto image::impose(blend mode, unsigned targetX, unsigned targetY, image source, unsigned sourceX, unsigned sourceY, unsigned sourceWidth, unsigned sourceHeight) -> void {
|
||||
source.transform(_endian, _depth, _alpha.mask(), _red.mask(), _green.mask(), _blue.mask());
|
||||
|
||||
#pragma omp parallel for
|
||||
for(unsigned y = 0; y < sourceHeight; y++) {
|
||||
const uint8_t* sp = source._data + source.pitch() * (sourceY + y) + source.stride() * sourceX;
|
||||
uint8_t* dp = _data + pitch() * (targetY + y) + stride() * targetX;
|
||||
for(unsigned x = 0; x < sourceWidth; x++) {
|
||||
uint64_t sourceColor = source.read(sp);
|
||||
uint64_t targetColor = read(dp);
|
||||
|
||||
int64_t sa = (sourceColor & _alpha.mask()) >> _alpha.shift();
|
||||
int64_t sr = (sourceColor & _red.mask() ) >> _red.shift();
|
||||
int64_t sg = (sourceColor & _green.mask()) >> _green.shift();
|
||||
int64_t sb = (sourceColor & _blue.mask() ) >> _blue.shift();
|
||||
|
||||
int64_t da = (targetColor & _alpha.mask()) >> _alpha.shift();
|
||||
int64_t dr = (targetColor & _red.mask() ) >> _red.shift();
|
||||
int64_t dg = (targetColor & _green.mask()) >> _green.shift();
|
||||
int64_t db = (targetColor & _blue.mask() ) >> _blue.shift();
|
||||
|
||||
uint64_t a, r, g, b;
|
||||
|
||||
switch(mode) {
|
||||
case blend::add:
|
||||
a = max(sa, da);
|
||||
r = min(_red.mask() >> _red.shift(), ((sr * sa) >> _alpha.depth()) + ((dr * da) >> _alpha.depth()));
|
||||
g = min(_green.mask() >> _green.shift(), ((sg * sa) >> _alpha.depth()) + ((dg * da) >> _alpha.depth()));
|
||||
b = min(_blue.mask() >> _blue.shift(), ((sb * sa) >> _alpha.depth()) + ((db * da) >> _alpha.depth()));
|
||||
break;
|
||||
|
||||
case blend::sourceAlpha:
|
||||
a = max(sa, da);
|
||||
r = dr + (((sr - dr) * sa) >> _alpha.depth());
|
||||
g = dg + (((sg - dg) * sa) >> _alpha.depth());
|
||||
b = db + (((sb - db) * sa) >> _alpha.depth());
|
||||
break;
|
||||
|
||||
case blend::sourceColor:
|
||||
a = sa;
|
||||
r = sr;
|
||||
g = sg;
|
||||
b = sb;
|
||||
break;
|
||||
|
||||
case blend::targetAlpha:
|
||||
a = max(sa, da);
|
||||
r = sr + (((dr - sr) * da) >> _alpha.depth());
|
||||
g = sg + (((dg - sg) * da) >> _alpha.depth());
|
||||
b = sb + (((db - sb) * da) >> _alpha.depth());
|
||||
break;
|
||||
|
||||
case blend::targetColor:
|
||||
a = da;
|
||||
r = dr;
|
||||
g = dg;
|
||||
b = db;
|
||||
break;
|
||||
}
|
||||
|
||||
write(dp, (a << _alpha.shift()) | (r << _red.shift()) | (g << _green.shift()) | (b << _blue.shift()));
|
||||
sp += source.stride();
|
||||
dp += stride();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
167
image/core.hpp
Normal file
167
image/core.hpp
Normal file
|
@ -0,0 +1,167 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
image::image(const image& source) {
|
||||
operator=(source);
|
||||
}
|
||||
|
||||
image::image(image&& source) {
|
||||
operator=(forward<image>(source));
|
||||
}
|
||||
|
||||
image::image(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask) {
|
||||
_endian = endian;
|
||||
_depth = depth;
|
||||
|
||||
_alpha = {alphaMask, bitDepth(alphaMask), bitShift(alphaMask)};
|
||||
_red = {redMask, bitDepth(redMask), bitShift(redMask )};
|
||||
_green = {greenMask, bitDepth(greenMask), bitShift(greenMask)};
|
||||
_blue = {blueMask, bitDepth(blueMask), bitShift(blueMask )};
|
||||
}
|
||||
|
||||
image::image(const string& filename) {
|
||||
load(filename);
|
||||
}
|
||||
|
||||
image::image(const vector<uint8_t>& buffer) {
|
||||
auto data = buffer.data();
|
||||
auto size = buffer.size();
|
||||
if(0);
|
||||
else if(data[0] == 'B' && data[1] == 'M') loadBMP(data, size);
|
||||
else if(data[1] == 'P' && data[2] == 'N' && data[3] == 'G') loadPNG(data, size);
|
||||
}
|
||||
|
||||
image::image(const uint8_t* data, unsigned size) {
|
||||
if(0);
|
||||
else if(data[0] == 'B' && data[1] == 'M') loadBMP(data, size);
|
||||
else if(data[1] == 'P' && data[2] == 'N' && data[3] == 'G') loadPNG(data, size);
|
||||
}
|
||||
|
||||
image::image() {
|
||||
}
|
||||
|
||||
image::~image() {
|
||||
free();
|
||||
}
|
||||
|
||||
auto image::operator=(const image& source) -> image& {
|
||||
if(this == &source) return *this;
|
||||
free();
|
||||
|
||||
_width = source._width;
|
||||
_height = source._height;
|
||||
|
||||
_endian = source._endian;
|
||||
_depth = source._depth;
|
||||
|
||||
_alpha = source._alpha;
|
||||
_red = source._red;
|
||||
_green = source._green;
|
||||
_blue = source._blue;
|
||||
|
||||
_data = allocate(_width, _height, stride());
|
||||
memory::copy(_data, source._data, source.size());
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto image::operator=(image&& source) -> image& {
|
||||
if(this == &source) return *this;
|
||||
free();
|
||||
|
||||
_width = source._width;
|
||||
_height = source._height;
|
||||
|
||||
_endian = source._endian;
|
||||
_depth = source._depth;
|
||||
|
||||
_alpha = source._alpha;
|
||||
_red = source._red;
|
||||
_green = source._green;
|
||||
_blue = source._blue;
|
||||
|
||||
_data = source._data;
|
||||
source._data = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
image::operator bool() const {
|
||||
return !empty();
|
||||
}
|
||||
|
||||
auto image::operator==(const image& source) const -> bool {
|
||||
if(_width != source._width) return false;
|
||||
if(_height != source._height) return false;
|
||||
|
||||
if(_endian != source._endian) return false;
|
||||
if(_depth != source._depth) return false;
|
||||
|
||||
if(_alpha != source._alpha) return false;
|
||||
if(_red != source._red) return false;
|
||||
if(_green != source._green) return false;
|
||||
if(_blue != source._blue) return false;
|
||||
|
||||
return memory::compare(_data, source._data, size()) == 0;
|
||||
}
|
||||
|
||||
auto image::operator!=(const image& source) const -> bool {
|
||||
return !operator==(source);
|
||||
}
|
||||
|
||||
auto image::read(const uint8_t* data) const -> uint64_t {
|
||||
uint64_t result = 0;
|
||||
if(_endian == 0) {
|
||||
for(signed n = stride() - 1; n >= 0; n--) result = (result << 8) | data[n];
|
||||
} else {
|
||||
for(signed n = 0; n < stride(); n++) result = (result << 8) | data[n];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto image::write(uint8_t* data, uint64_t value) const -> void {
|
||||
if(_endian == 0) {
|
||||
for(signed n = 0; n < stride(); n++) {
|
||||
data[n] = value;
|
||||
value >>= 8;
|
||||
}
|
||||
} else {
|
||||
for(signed n = stride() - 1; n >= 0; n--) {
|
||||
data[n] = value;
|
||||
value >>= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto image::free() -> void {
|
||||
if(_data) delete[] _data;
|
||||
_data = nullptr;
|
||||
}
|
||||
|
||||
auto image::empty() const -> bool {
|
||||
return !_data || !_width || !_height;
|
||||
}
|
||||
|
||||
auto image::load(const string& filename) -> bool {
|
||||
if(loadBMP(filename) == true) return true;
|
||||
if(loadPNG(filename) == true) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto image::allocate(unsigned width, unsigned height) -> void {
|
||||
if(_data && _width == width && _height == height) return;
|
||||
free();
|
||||
_width = width;
|
||||
_height = height;
|
||||
_data = allocate(_width, _height, stride());
|
||||
}
|
||||
|
||||
auto image::allocate(unsigned width, unsigned height, unsigned stride) -> uint8_t* {
|
||||
//allocate 1x1 larger than requested; so that linear interpolation does not require bounds-checking
|
||||
unsigned size = width * height * stride;
|
||||
unsigned padding = width * stride + stride;
|
||||
auto data = new uint8_t[size + padding];
|
||||
memory::fill(data + size, padding);
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
84
image/fill.hpp
Normal file
84
image/fill.hpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
auto image::fill(uint64_t color) -> void {
|
||||
for(unsigned y = 0; y < _height; y++) {
|
||||
uint8_t* dp = _data + pitch() * y;
|
||||
for(unsigned x = 0; x < _width; x++) {
|
||||
write(dp, color);
|
||||
dp += stride();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto image::gradient(uint64_t a, uint64_t b, uint64_t c, uint64_t d) -> void {
|
||||
for(unsigned y = 0; y < _height; y++) {
|
||||
uint8_t* dp = _data + pitch() * y;
|
||||
double muY = (double)y / (double)_height;
|
||||
for(unsigned x = 0; x < _width; x++) {
|
||||
double muX = (double)x / (double)_width;
|
||||
write(dp, interpolate4f(a, b, c, d, muX, muY));
|
||||
dp += stride();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto image::gradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY, function<double (double, double)> callback) -> void {
|
||||
for(signed y = 0; y < _height; y++) {
|
||||
uint8_t* dp = _data + pitch() * y;
|
||||
double py = max(-radiusY, min(+radiusY, y - centerY)) * 1.0 / radiusY;
|
||||
for(signed x = 0; x < _width; x++) {
|
||||
double px = max(-radiusX, min(+radiusX, x - centerX)) * 1.0 / radiusX;
|
||||
double mu = max(0.0, min(1.0, callback(px, py)));
|
||||
if(mu != mu) mu = 1.0; //NaN
|
||||
write(dp, interpolate4f(a, b, mu));
|
||||
dp += stride();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto image::crossGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void {
|
||||
return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double {
|
||||
x = fabs(x), y = fabs(y);
|
||||
return min(x, y) * min(x, y);
|
||||
});
|
||||
}
|
||||
|
||||
auto image::diamondGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void {
|
||||
return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double {
|
||||
return fabs(x) + fabs(y);
|
||||
});
|
||||
}
|
||||
|
||||
auto image::horizontalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void {
|
||||
return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double {
|
||||
return fabs(x);
|
||||
});
|
||||
}
|
||||
|
||||
auto image::radialGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void {
|
||||
return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double {
|
||||
return sqrt(x * x + y * y);
|
||||
});
|
||||
}
|
||||
|
||||
auto image::sphericalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void {
|
||||
return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double {
|
||||
return x * x + y * y;
|
||||
});
|
||||
}
|
||||
|
||||
auto image::squareGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void {
|
||||
return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double {
|
||||
return max(fabs(x), fabs(y));
|
||||
});
|
||||
}
|
||||
|
||||
auto image::verticalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void {
|
||||
return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double {
|
||||
return fabs(y);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
62
image/interpolation.hpp
Normal file
62
image/interpolation.hpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
auto image::isplit(uint64_t* c, uint64_t color) -> void {
|
||||
c[0] = (color & _alpha.mask()) >> _alpha.shift();
|
||||
c[1] = (color & _red.mask() ) >> _red.shift();
|
||||
c[2] = (color & _green.mask()) >> _green.shift();
|
||||
c[3] = (color & _blue.mask() ) >> _blue.shift();
|
||||
}
|
||||
|
||||
auto image::imerge(const uint64_t* c) -> uint64_t {
|
||||
return c[0] << _alpha.shift() | c[1] << _red.shift() | c[2] << _green.shift() | c[3] << _blue.shift();
|
||||
}
|
||||
|
||||
auto image::interpolate1f(uint64_t a, uint64_t b, double x) -> uint64_t {
|
||||
return a * (1.0 - x) + b * x;
|
||||
}
|
||||
|
||||
auto image::interpolate1f(uint64_t a, uint64_t b, uint64_t c, uint64_t d, double x, double y) -> uint64_t {
|
||||
return a * (1.0 - x) * (1.0 - y) + b * x * (1.0 - y) + c * (1.0 - x) * y + d * x * y;
|
||||
}
|
||||
|
||||
auto image::interpolate1i(int64_t a, int64_t b, uint32_t x) -> uint64_t {
|
||||
return a + (((b - a) * x) >> 32); //a + (b - a) * x
|
||||
}
|
||||
|
||||
auto image::interpolate1i(int64_t a, int64_t b, int64_t c, int64_t d, uint32_t x, uint32_t y) -> uint64_t {
|
||||
a = a + (((b - a) * x) >> 32); //a + (b - a) * x
|
||||
c = c + (((d - c) * x) >> 32); //c + (d - c) * x
|
||||
return a + (((c - a) * y) >> 32); //a + (c - a) * y
|
||||
}
|
||||
|
||||
auto image::interpolate4f(uint64_t a, uint64_t b, double x) -> uint64_t {
|
||||
uint64_t o[4], pa[4], pb[4];
|
||||
isplit(pa, a), isplit(pb, b);
|
||||
for(unsigned n = 0; n < 4; n++) o[n] = interpolate1f(pa[n], pb[n], x);
|
||||
return imerge(o);
|
||||
}
|
||||
|
||||
auto image::interpolate4f(uint64_t a, uint64_t b, uint64_t c, uint64_t d, double x, double y) -> uint64_t {
|
||||
uint64_t o[4], pa[4], pb[4], pc[4], pd[4];
|
||||
isplit(pa, a), isplit(pb, b), isplit(pc, c), isplit(pd, d);
|
||||
for(unsigned n = 0; n < 4; n++) o[n] = interpolate1f(pa[n], pb[n], pc[n], pd[n], x, y);
|
||||
return imerge(o);
|
||||
}
|
||||
|
||||
auto image::interpolate4i(uint64_t a, uint64_t b, uint32_t x) -> uint64_t {
|
||||
uint64_t o[4], pa[4], pb[4];
|
||||
isplit(pa, a), isplit(pb, b);
|
||||
for(unsigned n = 0; n < 4; n++) o[n] = interpolate1i(pa[n], pb[n], x);
|
||||
return imerge(o);
|
||||
}
|
||||
|
||||
auto image::interpolate4i(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint32_t x, uint32_t y) -> uint64_t {
|
||||
uint64_t o[4], pa[4], pb[4], pc[4], pd[4];
|
||||
isplit(pa, a), isplit(pb, b), isplit(pc, c), isplit(pd, d);
|
||||
for(unsigned n = 0; n < 4; n++) o[n] = interpolate1i(pa[n], pb[n], pc[n], pd[n], x, y);
|
||||
return imerge(o);
|
||||
}
|
||||
|
||||
}
|
97
image/load.hpp
Normal file
97
image/load.hpp
Normal file
|
@ -0,0 +1,97 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
auto image::loadBMP(const string& filename) -> bool {
|
||||
if(!file::exists(filename)) return false;
|
||||
auto buffer = file::read(filename);
|
||||
return loadBMP(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
auto image::loadBMP(const uint8_t* bmpData, unsigned bmpSize) -> bool {
|
||||
Decode::BMP source;
|
||||
if(!source.load(bmpData, bmpSize)) return false;
|
||||
|
||||
allocate(source.width(), source.height());
|
||||
const uint32_t* sp = source.data();
|
||||
uint8_t* dp = _data;
|
||||
|
||||
for(unsigned y = 0; y < _height; y++) {
|
||||
for(unsigned x = 0; x < _width; x++) {
|
||||
uint32_t color = *sp++;
|
||||
uint64_t a = normalize((uint8_t)(color >> 24), 8, _alpha.depth());
|
||||
uint64_t r = normalize((uint8_t)(color >> 16), 8, _red.depth());
|
||||
uint64_t g = normalize((uint8_t)(color >> 8), 8, _green.depth());
|
||||
uint64_t b = normalize((uint8_t)(color >> 0), 8, _blue.depth());
|
||||
write(dp, (a << _alpha.shift()) | (r << _red.shift()) | (g << _green.shift()) | (b << _blue.shift()));
|
||||
dp += stride();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto image::loadPNG(const string& filename) -> bool {
|
||||
if(!file::exists(filename)) return false;
|
||||
auto buffer = file::read(filename);
|
||||
return loadPNG(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
auto image::loadPNG(const uint8_t* pngData, unsigned pngSize) -> bool {
|
||||
Decode::PNG source;
|
||||
if(!source.load(pngData, pngSize)) return false;
|
||||
|
||||
allocate(source.info.width, source.info.height);
|
||||
const uint8_t* sp = source.data;
|
||||
uint8_t* dp = _data;
|
||||
|
||||
auto decode = [&]() -> uint64_t {
|
||||
uint64_t p, r, g, b, a;
|
||||
|
||||
switch(source.info.colorType) {
|
||||
case 0: //L
|
||||
r = g = b = source.readbits(sp);
|
||||
a = (1 << source.info.bitDepth) - 1;
|
||||
break;
|
||||
case 2: //R,G,B
|
||||
r = source.readbits(sp);
|
||||
g = source.readbits(sp);
|
||||
b = source.readbits(sp);
|
||||
a = (1 << source.info.bitDepth) - 1;
|
||||
break;
|
||||
case 3: //P
|
||||
p = source.readbits(sp);
|
||||
r = source.info.palette[p][0];
|
||||
g = source.info.palette[p][1];
|
||||
b = source.info.palette[p][2];
|
||||
a = (1 << source.info.bitDepth) - 1;
|
||||
break;
|
||||
case 4: //L,A
|
||||
r = g = b = source.readbits(sp);
|
||||
a = source.readbits(sp);
|
||||
break;
|
||||
case 6: //R,G,B,A
|
||||
r = source.readbits(sp);
|
||||
g = source.readbits(sp);
|
||||
b = source.readbits(sp);
|
||||
a = source.readbits(sp);
|
||||
break;
|
||||
}
|
||||
|
||||
a = normalize(a, source.info.bitDepth, _alpha.depth());
|
||||
r = normalize(r, source.info.bitDepth, _red.depth());
|
||||
g = normalize(g, source.info.bitDepth, _green.depth());
|
||||
b = normalize(b, source.info.bitDepth, _blue.depth());
|
||||
|
||||
return (a << _alpha.shift()) | (r << _red.shift()) | (g << _green.shift()) | (b << _blue.shift());
|
||||
};
|
||||
|
||||
for(unsigned y = 0; y < _height; y++) {
|
||||
for(unsigned x = 0; x < _width; x++) {
|
||||
write(dp, decode());
|
||||
dp += stride();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
181
image/scale.hpp
Normal file
181
image/scale.hpp
Normal file
|
@ -0,0 +1,181 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
auto image::scale(unsigned outputWidth, unsigned outputHeight, bool linear) -> void {
|
||||
if(!_data) return;
|
||||
if(_width == outputWidth && _height == outputHeight) return; //no scaling necessary
|
||||
if(linear == false) return scaleNearest(outputWidth, outputHeight);
|
||||
|
||||
if(_width == outputWidth ) return scaleLinearHeight(outputHeight);
|
||||
if(_height == outputHeight) return scaleLinearWidth(outputWidth);
|
||||
|
||||
//find fastest scaling method, based on number of interpolation operations required
|
||||
//magnification usually benefits from two-pass linear interpolation
|
||||
//minification usually benefits from one-pass bilinear interpolation
|
||||
unsigned d1wh = ((_width * outputWidth ) + (outputWidth * outputHeight)) * 1;
|
||||
unsigned d1hw = ((_height * outputHeight) + (outputWidth * outputHeight)) * 1;
|
||||
unsigned d2wh = (outputWidth * outputHeight) * 3;
|
||||
|
||||
if(d1wh <= d1hw && d1wh <= d2wh) return scaleLinearWidth(outputWidth), scaleLinearHeight(outputHeight);
|
||||
if(d1hw <= d2wh) return scaleLinearHeight(outputHeight), scaleLinearWidth(outputWidth);
|
||||
return scaleLinear(outputWidth, outputHeight);
|
||||
}
|
||||
|
||||
auto image::scaleLinearWidth(unsigned outputWidth) -> void {
|
||||
uint8_t* outputData = allocate(outputWidth, _height, stride());
|
||||
unsigned outputPitch = outputWidth * stride();
|
||||
uint64_t xstride = ((uint64_t)(_width - 1) << 32) / max(1u, outputWidth - 1);
|
||||
|
||||
#pragma omp parallel for
|
||||
for(unsigned y = 0; y < _height; y++) {
|
||||
uint64_t xfraction = 0;
|
||||
|
||||
const uint8_t* sp = _data + pitch() * y;
|
||||
uint8_t* dp = outputData + outputPitch * y;
|
||||
|
||||
uint64_t a = read(sp);
|
||||
uint64_t b = read(sp + stride());
|
||||
sp += stride();
|
||||
|
||||
unsigned x = 0;
|
||||
while(true) {
|
||||
while(xfraction < 0x100000000 && x++ < outputWidth) {
|
||||
write(dp, interpolate4i(a, b, xfraction));
|
||||
dp += stride();
|
||||
xfraction += xstride;
|
||||
}
|
||||
if(x >= outputWidth) break;
|
||||
|
||||
sp += stride();
|
||||
a = b;
|
||||
b = read(sp);
|
||||
xfraction -= 0x100000000;
|
||||
}
|
||||
}
|
||||
|
||||
free();
|
||||
_data = outputData;
|
||||
_width = outputWidth;
|
||||
}
|
||||
|
||||
auto image::scaleLinearHeight(unsigned outputHeight) -> void {
|
||||
uint8_t* outputData = allocate(_width, outputHeight, stride());
|
||||
uint64_t ystride = ((uint64_t)(_height - 1) << 32) / max(1u, outputHeight - 1);
|
||||
|
||||
#pragma omp parallel for
|
||||
for(unsigned x = 0; x < _width; x++) {
|
||||
uint64_t yfraction = 0;
|
||||
|
||||
const uint8_t* sp = _data + stride() * x;
|
||||
uint8_t* dp = outputData + stride() * x;
|
||||
|
||||
uint64_t a = read(sp);
|
||||
uint64_t b = read(sp + pitch());
|
||||
sp += pitch();
|
||||
|
||||
unsigned y = 0;
|
||||
while(true) {
|
||||
while(yfraction < 0x100000000 && y++ < outputHeight) {
|
||||
write(dp, interpolate4i(a, b, yfraction));
|
||||
dp += pitch();
|
||||
yfraction += ystride;
|
||||
}
|
||||
if(y >= outputHeight) break;
|
||||
|
||||
sp += pitch();
|
||||
a = b;
|
||||
b = read(sp);
|
||||
yfraction -= 0x100000000;
|
||||
}
|
||||
}
|
||||
|
||||
free();
|
||||
_data = outputData;
|
||||
_height = outputHeight;
|
||||
}
|
||||
|
||||
auto image::scaleLinear(unsigned outputWidth, unsigned outputHeight) -> void {
|
||||
uint8_t* outputData = allocate(outputWidth, outputHeight, stride());
|
||||
unsigned outputPitch = outputWidth * stride();
|
||||
|
||||
uint64_t xstride = ((uint64_t)(_width - 1) << 32) / max(1u, outputWidth - 1);
|
||||
uint64_t ystride = ((uint64_t)(_height - 1) << 32) / max(1u, outputHeight - 1);
|
||||
|
||||
#pragma omp parallel for
|
||||
for(unsigned y = 0; y < outputHeight; y++) {
|
||||
uint64_t yfraction = ystride * y;
|
||||
uint64_t xfraction = 0;
|
||||
|
||||
const uint8_t* sp = _data + pitch() * (yfraction >> 32);
|
||||
uint8_t* dp = outputData + outputPitch * y;
|
||||
|
||||
uint64_t a = read(sp);
|
||||
uint64_t b = read(sp + stride());
|
||||
uint64_t c = read(sp + pitch());
|
||||
uint64_t d = read(sp + pitch() + stride());
|
||||
sp += stride();
|
||||
|
||||
unsigned x = 0;
|
||||
while(true) {
|
||||
while(xfraction < 0x100000000 && x++ < outputWidth) {
|
||||
write(dp, interpolate4i(a, b, c, d, xfraction, yfraction));
|
||||
dp += stride();
|
||||
xfraction += xstride;
|
||||
}
|
||||
if(x >= outputWidth) break;
|
||||
|
||||
sp += stride();
|
||||
a = b;
|
||||
c = d;
|
||||
b = read(sp);
|
||||
d = read(sp + pitch());
|
||||
xfraction -= 0x100000000;
|
||||
}
|
||||
}
|
||||
|
||||
free();
|
||||
_data = outputData;
|
||||
_width = outputWidth;
|
||||
_height = outputHeight;
|
||||
}
|
||||
|
||||
auto image::scaleNearest(unsigned outputWidth, unsigned outputHeight) -> void {
|
||||
uint8_t* outputData = allocate(outputWidth, outputHeight, stride());
|
||||
unsigned outputPitch = outputWidth * stride();
|
||||
|
||||
uint64_t xstride = ((uint64_t)_width << 32) / outputWidth;
|
||||
uint64_t ystride = ((uint64_t)_height << 32) / outputHeight;
|
||||
|
||||
#pragma omp parallel for
|
||||
for(unsigned y = 0; y < outputHeight; y++) {
|
||||
uint64_t yfraction = ystride * y;
|
||||
uint64_t xfraction = 0;
|
||||
|
||||
const uint8_t* sp = _data + pitch() * (yfraction >> 32);
|
||||
uint8_t* dp = outputData + outputPitch * y;
|
||||
|
||||
uint64_t a = read(sp);
|
||||
|
||||
unsigned x = 0;
|
||||
while(true) {
|
||||
while(xfraction < 0x100000000 && x++ < outputWidth) {
|
||||
write(dp, a);
|
||||
dp += stride();
|
||||
xfraction += xstride;
|
||||
}
|
||||
if(x >= outputWidth) break;
|
||||
|
||||
sp += stride();
|
||||
a = read(sp);
|
||||
xfraction -= 0x100000000;
|
||||
}
|
||||
}
|
||||
|
||||
free();
|
||||
_data = outputData;
|
||||
_width = outputWidth;
|
||||
_height = outputHeight;
|
||||
}
|
||||
|
||||
}
|
28
image/static.hpp
Normal file
28
image/static.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
auto image::bitDepth(uint64_t color) -> unsigned {
|
||||
unsigned depth = 0;
|
||||
if(color) while((color & 1) == 0) color >>= 1;
|
||||
while((color & 1) == 1) { color >>= 1; depth++; }
|
||||
return depth;
|
||||
}
|
||||
|
||||
auto image::bitShift(uint64_t color) -> unsigned {
|
||||
unsigned shift = 0;
|
||||
if(color) while((color & 1) == 0) { color >>= 1; shift++; }
|
||||
return shift;
|
||||
}
|
||||
|
||||
auto image::normalize(uint64_t color, unsigned sourceDepth, unsigned targetDepth) -> uint64_t {
|
||||
if(sourceDepth == 0 || targetDepth == 0) return 0;
|
||||
while(sourceDepth < targetDepth) {
|
||||
color = (color << sourceDepth) | color;
|
||||
sourceDepth += sourceDepth;
|
||||
}
|
||||
if(targetDepth < sourceDepth) color >>= (sourceDepth - targetDepth);
|
||||
return color;
|
||||
}
|
||||
|
||||
}
|
118
image/utility.hpp
Normal file
118
image/utility.hpp
Normal file
|
@ -0,0 +1,118 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
auto image::crop(unsigned outputX, unsigned outputY, unsigned outputWidth, unsigned outputHeight) -> bool {
|
||||
if(outputX + outputWidth > _width) return false;
|
||||
if(outputY + outputHeight > _height) return false;
|
||||
|
||||
uint8_t* outputData = allocate(outputWidth, outputHeight, stride());
|
||||
unsigned outputPitch = outputWidth * stride();
|
||||
|
||||
#pragma omp parallel for
|
||||
for(unsigned y = 0; y < outputHeight; y++) {
|
||||
const uint8_t* sp = _data + pitch() * (outputY + y) + stride() * outputX;
|
||||
uint8_t* dp = outputData + outputPitch * y;
|
||||
for(unsigned x = 0; x < outputWidth; x++) {
|
||||
write(dp, read(sp));
|
||||
sp += stride();
|
||||
dp += stride();
|
||||
}
|
||||
}
|
||||
|
||||
delete[] _data;
|
||||
_data = outputData;
|
||||
_width = outputWidth;
|
||||
_height = outputHeight;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto image::alphaBlend(uint64_t alphaColor) -> void {
|
||||
uint64_t alphaR = (alphaColor & _red.mask() ) >> _red.shift();
|
||||
uint64_t alphaG = (alphaColor & _green.mask()) >> _green.shift();
|
||||
uint64_t alphaB = (alphaColor & _blue.mask() ) >> _blue.shift();
|
||||
|
||||
#pragma omp parallel for
|
||||
for(unsigned y = 0; y < _height; y++) {
|
||||
uint8_t* dp = _data + pitch() * y;
|
||||
for(unsigned x = 0; x < _width; x++) {
|
||||
uint64_t color = read(dp);
|
||||
|
||||
uint64_t colorA = (color & _alpha.mask()) >> _alpha.shift();
|
||||
uint64_t colorR = (color & _red.mask() ) >> _red.shift();
|
||||
uint64_t colorG = (color & _green.mask()) >> _green.shift();
|
||||
uint64_t colorB = (color & _blue.mask() ) >> _blue.shift();
|
||||
double alphaScale = (double)colorA / (double)((1 << _alpha.depth()) - 1);
|
||||
|
||||
colorA = (1 << _alpha.depth()) - 1;
|
||||
colorR = (colorR * alphaScale) + (alphaR * (1.0 - alphaScale));
|
||||
colorG = (colorG * alphaScale) + (alphaG * (1.0 - alphaScale));
|
||||
colorB = (colorB * alphaScale) + (alphaB * (1.0 - alphaScale));
|
||||
|
||||
write(dp, (colorA << _alpha.shift()) | (colorR << _red.shift()) | (colorG << _green.shift()) | (colorB << _blue.shift()));
|
||||
dp += stride();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto image::alphaMultiply() -> void {
|
||||
unsigned divisor = (1 << _alpha.depth()) - 1;
|
||||
|
||||
#pragma omp parallel for
|
||||
for(unsigned y = 0; y < _height; y++) {
|
||||
uint8_t* dp = _data + pitch() * y;
|
||||
for(unsigned x = 0; x < _width; x++) {
|
||||
uint64_t color = read(dp);
|
||||
|
||||
uint64_t colorA = (color & _alpha.mask()) >> _alpha.shift();
|
||||
uint64_t colorR = (color & _red.mask() ) >> _red.shift();
|
||||
uint64_t colorG = (color & _green.mask()) >> _green.shift();
|
||||
uint64_t colorB = (color & _blue.mask() ) >> _blue.shift();
|
||||
|
||||
colorR = (colorR * colorA) / divisor;
|
||||
colorG = (colorG * colorA) / divisor;
|
||||
colorB = (colorB * colorA) / divisor;
|
||||
|
||||
write(dp, (colorA << _alpha.shift()) | (colorR << _red.shift()) | (colorG << _green.shift()) | (colorB << _blue.shift()));
|
||||
dp += stride();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto image::transform(const image& source) -> void {
|
||||
return transform(source._endian, source._depth, source._alpha.mask(), source._red.mask(), source._green.mask(), source._blue.mask());
|
||||
}
|
||||
|
||||
auto image::transform(bool outputEndian, unsigned outputDepth, uint64_t outputAlphaMask, uint64_t outputRedMask, uint64_t outputGreenMask, uint64_t outputBlueMask) -> void {
|
||||
if(_endian == outputEndian && _depth == outputDepth && _alpha.mask() == outputAlphaMask && _red.mask() == outputRedMask && _green.mask() == outputGreenMask && _blue.mask() == outputBlueMask) return;
|
||||
|
||||
image output(outputEndian, outputDepth, outputAlphaMask, outputRedMask, outputGreenMask, outputBlueMask);
|
||||
output.allocate(_width, _height);
|
||||
|
||||
#pragma omp parallel for
|
||||
for(unsigned y = 0; y < _height; y++) {
|
||||
const uint8_t* sp = _data + pitch() * y;
|
||||
uint8_t* dp = output._data + output.pitch() * y;
|
||||
for(unsigned x = 0; x < _width; x++) {
|
||||
uint64_t color = read(sp);
|
||||
sp += stride();
|
||||
|
||||
uint64_t a = (color & _alpha.mask()) >> _alpha.shift();
|
||||
uint64_t r = (color & _red.mask() ) >> _red.shift();
|
||||
uint64_t g = (color & _green.mask()) >> _green.shift();
|
||||
uint64_t b = (color & _blue.mask() ) >> _blue.shift();
|
||||
|
||||
a = normalize(a, _alpha.depth(), output._alpha.depth());
|
||||
r = normalize(r, _red.depth(), output._red.depth());
|
||||
g = normalize(g, _green.depth(), output._green.depth());
|
||||
b = normalize(b, _blue.depth(), output._blue.depth());
|
||||
|
||||
output.write(dp, (a << output._alpha.shift()) | (r << output._red.shift()) | (g << output._green.shift()) | (b << output._blue.shift()));
|
||||
dp += output.stride();
|
||||
}
|
||||
}
|
||||
|
||||
operator=(move(output));
|
||||
}
|
||||
|
||||
}
|
83
inode.hpp
Normal file
83
inode.hpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
#pragma once
|
||||
|
||||
//generic abstraction layer for common storage operations against both files and directories
|
||||
//these functions are not recursive; use directory::create() and directory::remove() for recursion
|
||||
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/string.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct inode {
|
||||
enum class time : unsigned { access, modify };
|
||||
|
||||
static auto exists(const string& name) -> bool {
|
||||
return access(name, F_OK) == 0;
|
||||
}
|
||||
|
||||
static auto readable(const string& name) -> bool {
|
||||
return access(name, R_OK) == 0;
|
||||
}
|
||||
|
||||
static auto writable(const string& name) -> bool {
|
||||
return access(name, W_OK) == 0;
|
||||
}
|
||||
|
||||
static auto executable(const string& name) -> bool {
|
||||
return access(name, X_OK) == 0;
|
||||
}
|
||||
|
||||
static auto uid(const string& name) -> unsigned {
|
||||
struct stat data{0};
|
||||
stat(name, &data);
|
||||
return data.st_uid;
|
||||
}
|
||||
|
||||
static auto gid(const string& name) -> unsigned {
|
||||
struct stat data{0};
|
||||
stat(name, &data);
|
||||
return data.st_gid;
|
||||
}
|
||||
|
||||
static auto mode(const string& name) -> unsigned {
|
||||
struct stat data{0};
|
||||
stat(name, &data);
|
||||
return data.st_mode;
|
||||
}
|
||||
|
||||
static auto timestamp(const string& name, time mode = time::modify) -> time_t {
|
||||
struct stat data = {0};
|
||||
stat(name, &data);
|
||||
switch(mode) { default:
|
||||
case time::access: return data.st_atime;
|
||||
case time::modify: return data.st_mtime;
|
||||
}
|
||||
}
|
||||
|
||||
//returns true if 'name' already exists
|
||||
static auto create(const string& name, unsigned permissions = 0755) -> bool {
|
||||
if(exists(name)) return true;
|
||||
if(name.endsWith("/")) return mkdir(name, permissions) == 0;
|
||||
int fd = open(name, O_CREAT | O_EXCL, permissions);
|
||||
if(fd < 0) return false;
|
||||
return close(fd), true;
|
||||
}
|
||||
|
||||
//returns false if 'name' and 'targetname' are on different file systems (requires copy)
|
||||
static auto rename(const string& name, const string& targetname) -> bool {
|
||||
return ::rename(name, targetname) == 0;
|
||||
}
|
||||
|
||||
//returns false if 'name' is a directory that is not empty
|
||||
static auto remove(const string& name) -> bool {
|
||||
#if defined(PLATFORM_WINDOWS)
|
||||
if(name.endsWith("/")) return _wrmdir(utf16_t(name)) == 0;
|
||||
return _wunlink(utf16_t(name)) == 0;
|
||||
#else
|
||||
if(name.endsWith("/")) return rmdir(name) == 0;
|
||||
return unlink(name) == 0;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
}
|
56
interpolation.hpp
Normal file
56
interpolation.hpp
Normal file
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct Interpolation {
|
||||
static inline auto Nearest(double mu, double a, double b, double c, double d) -> double {
|
||||
return (mu <= 0.5 ? b : c);
|
||||
}
|
||||
|
||||
static inline auto Sublinear(double mu, double a, double b, double c, double d) -> double {
|
||||
mu = ((mu - 0.5) * 2.0) + 0.5;
|
||||
if(mu < 0) mu = 0;
|
||||
if(mu > 1) mu = 1;
|
||||
return b * (1.0 - mu) + c * mu;
|
||||
}
|
||||
|
||||
static inline auto Linear(double mu, double a, double b, double c, double d) -> double {
|
||||
return b * (1.0 - mu) + c * mu;
|
||||
}
|
||||
|
||||
static inline auto Cosine(double mu, double a, double b, double c, double d) -> double {
|
||||
mu = (1.0 - cos(mu * 3.14159265)) / 2.0;
|
||||
return b * (1.0 - mu) + c * mu;
|
||||
}
|
||||
|
||||
static inline auto Cubic(double mu, double a, double b, double c, double d) -> double {
|
||||
double A = d - c - a + b;
|
||||
double B = a - b - A;
|
||||
double C = c - a;
|
||||
double D = b;
|
||||
return A * (mu * mu * mu) + B * (mu * mu) + C * mu + D;
|
||||
}
|
||||
|
||||
static inline auto Hermite(double mu1, double a, double b, double c, double d) -> double {
|
||||
const double tension = 0.0; //-1 = low, 0 = normal, +1 = high
|
||||
const double bias = 0.0; //-1 = left, 0 = even, +1 = right
|
||||
double mu2, mu3, m0, m1, a0, a1, a2, a3;
|
||||
|
||||
mu2 = mu1 * mu1;
|
||||
mu3 = mu2 * mu1;
|
||||
|
||||
m0 = (b - a) * (1.0 + bias) * (1.0 - tension) / 2.0;
|
||||
m0 += (c - b) * (1.0 - bias) * (1.0 - tension) / 2.0;
|
||||
m1 = (c - b) * (1.0 + bias) * (1.0 - tension) / 2.0;
|
||||
m1 += (d - c) * (1.0 - bias) * (1.0 - tension) / 2.0;
|
||||
|
||||
a0 = +2 * mu3 - 3 * mu2 + 1;
|
||||
a1 = mu3 - 2 * mu2 + mu1;
|
||||
a2 = mu3 - mu2;
|
||||
a3 = -2 * mu3 + 3 * mu2;
|
||||
|
||||
return (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
156
intrinsics.hpp
Normal file
156
intrinsics.hpp
Normal file
|
@ -0,0 +1,156 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
struct Intrinsics {
|
||||
enum class Compiler : unsigned { Clang, GCC, VisualCPP, Unknown };
|
||||
enum class Platform : unsigned { Windows, MacOSX, Linux, BSD, Unknown };
|
||||
enum class API : unsigned { Windows, Posix, Unknown };
|
||||
enum class Display : unsigned { Windows, Quartz, Xorg, Unknown };
|
||||
enum class Processor : unsigned { x86, amd64, ARM, PPC32, PPC64, Unknown };
|
||||
enum class Endian : unsigned { LSB, MSB, Unknown };
|
||||
|
||||
static inline auto compiler() -> Compiler;
|
||||
static inline auto platform() -> Platform;
|
||||
static inline auto api() -> API;
|
||||
static inline auto display() -> Display;
|
||||
static inline auto processor() -> Processor;
|
||||
static inline auto endian() -> Endian;
|
||||
};
|
||||
}
|
||||
|
||||
/* Compiler detection */
|
||||
|
||||
namespace nall {
|
||||
|
||||
#if defined(__clang__)
|
||||
#define COMPILER_CLANG
|
||||
auto Intrinsics::compiler() -> Compiler { return Compiler::Clang; }
|
||||
|
||||
#pragma clang diagnostic ignored "-Wunknown-pragmas"
|
||||
#pragma clang diagnostic ignored "-Wempty-body"
|
||||
#pragma clang diagnostic ignored "-Wparentheses"
|
||||
#pragma clang diagnostic ignored "-Wreturn-type"
|
||||
#pragma clang diagnostic ignored "-Wswitch"
|
||||
#pragma clang diagnostic ignored "-Wswitch-bool"
|
||||
#pragma clang diagnostic ignored "-Wtautological-compare"
|
||||
#pragma clang diagnostic ignored "-Wabsolute-value"
|
||||
|
||||
//temporary
|
||||
#pragma clang diagnostic ignored "-Winconsistent-missing-override"
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
#elif defined(__GNUC__)
|
||||
#define COMPILER_GCC
|
||||
auto Intrinsics::compiler() -> Compiler { return Compiler::GCC; }
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
|
||||
#pragma GCC diagnostic ignored "-Wpragmas"
|
||||
#pragma GCC diagnostic ignored "-Wswitch-bool"
|
||||
#elif defined(_MSC_VER)
|
||||
#define COMPILER_VISUALCPP
|
||||
auto Intrinsics::compiler() -> Compiler { return Compiler::VisualCPP; }
|
||||
|
||||
#pragma warning(disable:4996) //libc "deprecation" warnings
|
||||
#else
|
||||
#warning "unable to detect compiler"
|
||||
#define COMPILER_UNKNOWN
|
||||
auto Intrinsics::compiler() -> Compiler { return Compiler::Unknown; }
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
/* Platform detection */
|
||||
|
||||
namespace nall {
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define PLATFORM_WINDOWS
|
||||
#define API_WINDOWS
|
||||
#define DISPLAY_WINDOWS
|
||||
auto Intrinsics::platform() -> Platform { return Platform::Windows; }
|
||||
auto Intrinsics::api() -> API { return API::Windows; }
|
||||
auto Intrinsics::display() -> Display { return Display::Windows; }
|
||||
#elif defined(__APPLE__)
|
||||
#define PLATFORM_MACOSX
|
||||
#define API_POSIX
|
||||
#define DISPLAY_QUARTZ
|
||||
auto Intrinsics::platform() -> Platform { return Platform::MacOSX; }
|
||||
auto Intrinsics::api() -> API { return API::Posix; }
|
||||
auto Intrinsics::display() -> Display { return Display::Quartz; }
|
||||
#elif defined(linux) || defined(__linux__)
|
||||
#define PLATFORM_LINUX
|
||||
#define API_POSIX
|
||||
#define DISPLAY_XORG
|
||||
auto Intrinsics::platform() -> Platform { return Platform::Linux; }
|
||||
auto Intrinsics::api() -> API { return API::Posix; }
|
||||
auto Intrinsics::display() -> Display { return Display::Xorg; }
|
||||
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
#define PLATFORM_BSD
|
||||
#define API_POSIX
|
||||
#define DISPLAY_XORG
|
||||
auto Intrinsics::platform() -> Platform { return Platform::BSD; }
|
||||
auto Intrinsics::api() -> API { return API::Posix; }
|
||||
auto Intrinsics::display() -> Display { return Display::Xorg; }
|
||||
#else
|
||||
#warning "unable to detect platform"
|
||||
#define PLATFORM_UNKNOWN
|
||||
#define API_UNKNOWN
|
||||
#define DISPLAY_UNKNOWN
|
||||
auto Intrinsics::platform() -> Platform { return Platform::Unknown; }
|
||||
auto Intrinsics::api() -> API { return API::Unknown; }
|
||||
auto Intrinsics::display() -> Display { return Display::Unknown; }
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#if defined(PLATFORM_MACOSX)
|
||||
#include <machine/endian.h>
|
||||
#elif defined(PLATFORM_LINUX)
|
||||
#include <endian.h>
|
||||
#elif defined(PLATFORM_BSD)
|
||||
#include <sys/endian.h>
|
||||
#endif
|
||||
|
||||
/* Processor Detection */
|
||||
|
||||
namespace nall {
|
||||
|
||||
#if defined(__i386__) || defined(_M_IX86)
|
||||
#define PROCESSOR_X86
|
||||
auto Intrinsics::processor() -> Processor { return Processor::x86; }
|
||||
#elif defined(__amd64__) || defined(_M_AMD64)
|
||||
#define PROCESSOR_AMD64
|
||||
auto Intrinsics::processor() -> Processor { return Processor::amd64; }
|
||||
#elif defined(__arm__)
|
||||
#define PROCESSOR_ARM
|
||||
auto Intrinsics::processor() -> Processor { return Processor::ARM; }
|
||||
#elif defined(__ppc64__) || defined(_ARCH_PPC64)
|
||||
#define PROCESSOR_PPC64
|
||||
auto Intrinsics::processor() -> Processor { return Processor::PPC64; }
|
||||
#elif defined(__ppc__) || defined(_ARCH_PPC) || defined(_M_PPC)
|
||||
#define PROCESSOR_PPC32
|
||||
auto Intrinsics::processor() -> Processor { return Processor::PPC32; }
|
||||
#else
|
||||
#warning "unable to detect processor"
|
||||
#define PROCESSOR_UNKNOWN
|
||||
auto Intrinsics::processor() -> Processor { return Processor::Unknown; }
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
/* Endian detection */
|
||||
|
||||
namespace nall {
|
||||
|
||||
#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN) || defined(__LITTLE_ENDIAN__) || defined(__i386__) || defined(__amd64__) || defined(_M_IX86) || defined(_M_AMD64)
|
||||
#define ENDIAN_LSB
|
||||
auto Intrinsics::endian() -> Endian { return Endian::LSB; }
|
||||
#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && __BYTE_ORDER == __BIG_ENDIAN) || defined(__BIG_ENDIAN__) || defined(__powerpc__) || defined(_M_PPC)
|
||||
#define ENDIAN_MSB
|
||||
auto Intrinsics::endian() -> Endian { return Endian::MSB; }
|
||||
#else
|
||||
#warning "unable to detect endian"
|
||||
#define ENDIAN_UNKNOWN
|
||||
auto Intrinsics::endian() -> Endian { return Endian::Unknown; }
|
||||
#endif
|
||||
|
||||
}
|
26
main.hpp
Normal file
26
main.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/string.hpp>
|
||||
|
||||
namespace nall {
|
||||
auto main(lstring arguments) -> void;
|
||||
|
||||
auto main(int argc, char** argv) -> int {
|
||||
#if defined(PLATFORM_WINDOWS)
|
||||
CoInitialize(0);
|
||||
WSAData wsaData{0};
|
||||
WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||
utf8_args(argc, argv);
|
||||
#endif
|
||||
|
||||
lstring arguments;
|
||||
for(auto n : range(argc)) arguments.append(argv[n]);
|
||||
|
||||
return main(move(arguments)), EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
auto main(int argc, char** argv) -> int {
|
||||
return nall::main(argc, argv);
|
||||
}
|
58
map.hpp
Normal file
58
map.hpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/set.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename T, typename U> struct map {
|
||||
struct node_t {
|
||||
T key;
|
||||
U value;
|
||||
node_t() = default;
|
||||
node_t(const T& key) : key(key) {}
|
||||
node_t(const T& key, const U& value) : key(key), value(value) {}
|
||||
auto operator< (const node_t& source) const -> bool { return key < source.key; }
|
||||
auto operator==(const node_t& source) const -> bool { return key == source.key; }
|
||||
};
|
||||
|
||||
auto find(const T& key) const -> maybe<U&> {
|
||||
if(auto node = root.find({key})) return node().value;
|
||||
return nothing;
|
||||
}
|
||||
|
||||
auto insert(const T& key, const U& value) -> void { root.insert({key, value}); }
|
||||
auto remove(const T& key) -> void { root.remove({key}); }
|
||||
auto size() const -> unsigned { return root.size(); }
|
||||
auto reset() -> void { root.reset(); }
|
||||
|
||||
auto begin() -> typename set<node_t>::iterator { return root.begin(); }
|
||||
auto end() -> typename set<node_t>::iterator { return root.end(); }
|
||||
|
||||
auto begin() const -> const typename set<node_t>::iterator { return root.begin(); }
|
||||
auto end() const -> const typename set<node_t>::iterator { return root.end(); }
|
||||
|
||||
protected:
|
||||
set<node_t> root;
|
||||
};
|
||||
|
||||
template<typename T, typename U> struct bimap {
|
||||
auto find(const T& key) const -> maybe<U&> { return tmap.find(key); }
|
||||
auto find(const U& key) const -> maybe<T&> { return umap.find(key); }
|
||||
auto insert(const T& key, const U& value) -> void { tmap.insert(key, value); umap.insert(value, key); }
|
||||
auto remove(const T& key) -> void { if(auto p = tmap.find(key)) { umap.remove(p().value); tmap.remove(key); } }
|
||||
auto remove(const U& key) -> void { if(auto p = umap.find(key)) { tmap.remove(p().value); umap.remove(key); } }
|
||||
auto size() const -> unsigned { return tmap.size(); }
|
||||
auto reset() -> void { tmap.reset(); umap.reset(); }
|
||||
|
||||
auto begin() -> typename set<typename map<T, U>::node_t>::iterator { return tmap.begin(); }
|
||||
auto end() -> typename set<typename map<T, U>::node_t>::iterator { return tmap.end(); }
|
||||
|
||||
auto begin() const -> const typename set<typename map<T, U>::node_t>::iterator { return tmap.begin(); }
|
||||
auto end() const -> const typename set<typename map<T, U>::node_t>::iterator { return tmap.end(); }
|
||||
|
||||
protected:
|
||||
map<T, U> tmap;
|
||||
map<U, T> umap;
|
||||
};
|
||||
|
||||
}
|
30
matrix.hpp
Normal file
30
matrix.hpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
namespace Matrix {
|
||||
|
||||
template<typename T> inline auto Multiply(T* output, const T* xdata, unsigned xrows, unsigned xcols, const T* ydata, unsigned yrows, unsigned ycols) -> void {
|
||||
if(xcols != yrows) return;
|
||||
|
||||
for(unsigned y = 0; y < xrows; y++) {
|
||||
for(unsigned x = 0; x < ycols; x++) {
|
||||
T sum = 0;
|
||||
for(unsigned z = 0; z < xcols; z++) {
|
||||
sum += xdata[y * xcols + z] * ydata[z * ycols + x];
|
||||
}
|
||||
*output++ = sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T> inline auto Multiply(const T* xdata, unsigned xrows, unsigned xcols, const T* ydata, unsigned yrows, unsigned ycols) -> vector<T> {
|
||||
vector<T> output;
|
||||
output.resize(xrows * ycols);
|
||||
Multiply(output.data(), xdata, xrows, xcols, ydata, yrows, ycols);
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
91
maybe.hpp
Normal file
91
maybe.hpp
Normal file
|
@ -0,0 +1,91 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct nothing_t {};
|
||||
static nothing_t nothing;
|
||||
|
||||
template<typename T>
|
||||
struct maybe {
|
||||
inline maybe() {}
|
||||
inline maybe(nothing_t) {}
|
||||
inline maybe(const T& source) { operator=(source); }
|
||||
inline maybe(T&& source) { operator=(move(source)); }
|
||||
inline maybe(const maybe& source) { operator=(source); }
|
||||
inline maybe(maybe&& source) { operator=(move(source)); }
|
||||
inline ~maybe() { reset(); }
|
||||
|
||||
inline auto operator=(nothing_t) -> maybe& { reset(); return *this; }
|
||||
inline auto operator=(const T& source) -> maybe& { reset(); _valid = true; new(&_value.t) T(source); return *this; }
|
||||
inline auto operator=(T&& source) -> maybe& { reset(); _valid = true; new(&_value.t) T(move(source)); return *this; }
|
||||
|
||||
inline auto operator=(const maybe& source) -> maybe& {
|
||||
if(this == &source) return *this;
|
||||
reset();
|
||||
if(_valid = source._valid) new(&_value.t) T(source.get());
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline auto operator=(maybe&& source) -> maybe& {
|
||||
if(this == &source) return *this;
|
||||
reset();
|
||||
if(_valid = source._valid) new(&_value.t) T(move(source.get()));
|
||||
source._valid = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline explicit operator bool() const { return _valid; }
|
||||
inline auto reset() -> void { if(_valid) { _value.t.~T(); _valid = false; } }
|
||||
inline auto data() -> T* { return _valid ? &_value.t : nullptr; }
|
||||
inline auto get() -> T& { assert(_valid); return _value.t; }
|
||||
|
||||
inline auto data() const -> const T* { return ((maybe*)this)->data(); }
|
||||
inline auto get() const -> const T& { return ((maybe*)this)->get(); }
|
||||
inline auto operator->() -> T* { return data(); }
|
||||
inline auto operator->() const -> const T* { return data(); }
|
||||
inline auto operator*() -> T& { return get(); }
|
||||
inline auto operator*() const -> const T& { return get(); }
|
||||
inline auto operator()() -> T& { return get(); }
|
||||
inline auto operator()() const -> const T& { return get(); }
|
||||
inline auto operator()(const T& invalid) const -> const T& { return _valid ? get() : invalid; }
|
||||
|
||||
private:
|
||||
union U {
|
||||
T t;
|
||||
U() {}
|
||||
~U() {}
|
||||
} _value;
|
||||
bool _valid = false;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct maybe<T&> {
|
||||
inline maybe() : _value(nullptr) {}
|
||||
inline maybe(nothing_t) : _value(nullptr) {}
|
||||
inline maybe(const T& source) : _value((T*)&source) {}
|
||||
inline maybe(const maybe& source) : _value(source._value) {}
|
||||
|
||||
inline auto operator=(nothing_t) -> maybe& { _value = nullptr; return *this; }
|
||||
inline auto operator=(const T& source) -> maybe& { _value = (T*)&source; return *this; }
|
||||
inline auto operator=(const maybe& source) -> maybe& { _value = source._value; return *this; }
|
||||
|
||||
inline explicit operator bool() const { return _value; }
|
||||
inline auto reset() -> void { _value = nullptr; }
|
||||
inline auto data() -> T* { return _value; }
|
||||
inline auto get() -> T& { assert(_value); return *_value; }
|
||||
|
||||
inline auto data() const -> const T* { return ((maybe*)this)->data(); }
|
||||
inline auto get() const -> const T& { return ((maybe*)this)->get(); }
|
||||
inline auto operator->() -> T* { return data(); }
|
||||
inline auto operator->() const -> const T* { return data(); }
|
||||
inline auto operator*() -> T& { return get(); }
|
||||
inline auto operator*() const -> const T& { return get(); }
|
||||
inline auto operator()() -> T& { return get(); }
|
||||
inline auto operator()() const -> const T& { return get(); }
|
||||
inline auto operator()(const T& invalid) const -> const T& { return _value ? get() : invalid; }
|
||||
|
||||
private:
|
||||
T* _value;
|
||||
};
|
||||
|
||||
}
|
3
memory.hpp
Normal file
3
memory.hpp
Normal file
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/memory/memory.hpp>
|
129
memory/memory.hpp
Normal file
129
memory/memory.hpp
Normal file
|
@ -0,0 +1,129 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/algorithm.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
namespace memory {
|
||||
inline auto allocate(unsigned size) -> void*;
|
||||
inline auto allocate(unsigned size, uint8_t data) -> void*;
|
||||
|
||||
inline auto resize(void* target, unsigned size) -> void*;
|
||||
|
||||
inline auto free(void* target) -> void;
|
||||
|
||||
inline auto compare(const void* target, unsigned capacity, const void* source, unsigned size) -> signed;
|
||||
inline auto compare(const void* target, const void* source, unsigned size) -> signed;
|
||||
|
||||
inline auto icompare(const void* target, unsigned capacity, const void* source, unsigned size) -> signed;
|
||||
inline auto icompare(const void* target, const void* source, unsigned size) -> signed;
|
||||
|
||||
inline auto copy(void* target, unsigned capacity, const void* source, unsigned size) -> void*;
|
||||
inline auto copy(void* target, const void* source, unsigned size) -> void*;
|
||||
|
||||
inline auto move(void* target, unsigned capacity, const void* source, unsigned size) -> void*;
|
||||
inline auto move(void* target, const void* source, unsigned size) -> void*;
|
||||
|
||||
inline auto fill(void* target, unsigned capacity, uint8_t data = 0x00) -> void*;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#include <nall/memory/pool.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
//implementation notes:
|
||||
//memcmp, memcpy, memmove have terrible performance on small block sizes (FreeBSD 10.0-amd64)
|
||||
//as this library is used extensively by nall/string, and most strings tend to be small,
|
||||
//this library hand-codes these functions instead. surprisingly, it's a substantial speedup
|
||||
|
||||
auto memory::allocate(unsigned size) -> void* {
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
auto memory::allocate(unsigned size, uint8_t data) -> void* {
|
||||
auto result = malloc(size);
|
||||
if(result) fill(result, size, data);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto memory::resize(void* target, unsigned size) -> void* {
|
||||
return realloc(target, size);
|
||||
}
|
||||
|
||||
auto memory::free(void* target) -> void {
|
||||
::free(target);
|
||||
}
|
||||
|
||||
auto memory::compare(const void* target, unsigned capacity, const void* source, unsigned size) -> signed {
|
||||
auto t = (int8_t*)target;
|
||||
auto s = (int8_t*)source;
|
||||
auto l = min(capacity, size);
|
||||
while(l--) {
|
||||
auto x = *t++;
|
||||
auto y = *s++;
|
||||
if(x != y) return x - y;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto memory::compare(const void* target, const void* source, unsigned size) -> signed {
|
||||
return compare(target, size, source, size);
|
||||
}
|
||||
|
||||
auto memory::icompare(const void* target, unsigned capacity, const void* source, unsigned size) -> signed {
|
||||
auto t = (int8_t*)target;
|
||||
auto s = (int8_t*)source;
|
||||
auto l = min(capacity, size);
|
||||
while(l--) {
|
||||
auto x = *t++;
|
||||
auto y = *s++;
|
||||
if(x - 'A' < 26) x += 32;
|
||||
if(y - 'A' < 26) y += 32;
|
||||
if(x != y) return x - y;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto memory::icompare(const void* target, const void* source, unsigned size) -> signed {
|
||||
return icompare(target, size, source, size);
|
||||
}
|
||||
|
||||
auto memory::copy(void* target, unsigned capacity, const void* source, unsigned size) -> void* {
|
||||
auto t = (uint8_t*)target;
|
||||
auto s = (uint8_t*)source;
|
||||
auto l = min(capacity, size);
|
||||
while(l--) *t++ = *s++;
|
||||
return target;
|
||||
}
|
||||
|
||||
auto memory::copy(void* target, const void* source, unsigned size) -> void* {
|
||||
return copy(target, size, source, size);
|
||||
}
|
||||
|
||||
auto memory::move(void* target, unsigned capacity, const void* source, unsigned size) -> void* {
|
||||
auto t = (uint8_t*)target;
|
||||
auto s = (uint8_t*)source;
|
||||
auto l = min(capacity, size);
|
||||
if(t < s) {
|
||||
while(l--) *t++ = *s++;
|
||||
} else {
|
||||
t += l;
|
||||
s += l;
|
||||
while(l--) *--t = *--s;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
auto memory::move(void* target, const void* source, unsigned size) -> void* {
|
||||
return move(target, size, source, size);
|
||||
}
|
||||
|
||||
auto memory::fill(void* target, unsigned capacity, uint8_t data) -> void* {
|
||||
auto t = (uint8_t*)target;
|
||||
while(capacity--) *t++ = data;
|
||||
return target;
|
||||
}
|
||||
|
||||
}
|
62
memory/pool.hpp
Normal file
62
memory/pool.hpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
namespace memory {
|
||||
|
||||
template<unsigned Capacity, unsigned Size>
|
||||
struct pool_spsc {
|
||||
signed* list = nullptr;
|
||||
uint8_t* data = nullptr;
|
||||
unsigned slot = 0;
|
||||
|
||||
pool_spsc() {
|
||||
list = (signed*)memory::allocate(Capacity * sizeof(signed));
|
||||
data = (uint8_t*)memory::allocate(Capacity * Size);
|
||||
for(unsigned n = 0; n < Capacity; n++) list[n] = n;
|
||||
}
|
||||
|
||||
~pool_spsc() {
|
||||
memory::free(list);
|
||||
memory::free(data);
|
||||
}
|
||||
|
||||
auto allocate(unsigned size) -> void* {
|
||||
if(size == 0) return nullptr;
|
||||
if(size > Size) return memory::allocate(size);
|
||||
signed offset = list[slot];
|
||||
if(offset < 0) return memory::allocate(size);
|
||||
list[slot] = -1;
|
||||
slot = (slot + 1) % Capacity;
|
||||
return (void*)(data + offset * Size);
|
||||
}
|
||||
|
||||
auto allocate(unsigned size, uint8_t data) -> void* {
|
||||
auto result = allocate(size);
|
||||
memset(result, data, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto resize(void* target, unsigned size) -> void* {
|
||||
if(target == nullptr) return allocate(size);
|
||||
signed offset = ((uint8_t*)target - data) / Size;
|
||||
if(offset < 0 || offset >= Capacity) return memory::resize(target, size);
|
||||
if(size <= Size) return target;
|
||||
slot = (slot - 1) % Capacity;
|
||||
list[slot] = offset;
|
||||
return memory::allocate(size);
|
||||
}
|
||||
|
||||
auto free(void* target) -> void {
|
||||
if(target == nullptr) return;
|
||||
signed offset = ((uint8_t*)target - data) / Size;
|
||||
if(offset < 0 || offset >= Capacity) return memory::free(target);
|
||||
slot = (slot - 1) % Capacity;
|
||||
list[slot] = offset;
|
||||
}
|
||||
|
||||
pool_spsc(const pool_spsc&) = delete;
|
||||
pool_spsc& operator=(const pool_spsc&) = delete;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
5
mosaic.hpp
Normal file
5
mosaic.hpp
Normal file
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/mosaic/bitstream.hpp>
|
||||
#include <nall/mosaic/context.hpp>
|
||||
#include <nall/mosaic/parser.hpp>
|
48
mosaic/bitstream.hpp
Normal file
48
mosaic/bitstream.hpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall { namespace mosaic {
|
||||
|
||||
struct bitstream {
|
||||
~bitstream() {
|
||||
close();
|
||||
}
|
||||
|
||||
auto read(uint64_t addr) const -> bool {
|
||||
if(data == nullptr || (addr >> 3) >= size) return 0;
|
||||
uint mask = endian == 0 ? (0x01 << (addr & 7)) : (0x80 >> (addr & 7));
|
||||
return data[addr >> 3] & mask;
|
||||
}
|
||||
|
||||
auto write(uint64_t addr, bool value) -> void {
|
||||
if(data == nullptr || readonly == true || (addr >> 3) >= size) return;
|
||||
uint mask = endian == 0 ? (0x01 << (addr & 7)) : (0x80 >> (addr & 7));
|
||||
if(value == 0) data[addr >> 3] &= ~mask;
|
||||
if(value == 1) data[addr >> 3] |= mask;
|
||||
}
|
||||
|
||||
auto open(const string& filename) -> bool {
|
||||
readonly = false;
|
||||
if(fp.open(filename, filemap::mode::readwrite) == false) {
|
||||
readonly = true;
|
||||
if(fp.open(filename, filemap::mode::read) == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
data = fp.data();
|
||||
size = fp.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto close() -> void {
|
||||
fp.close();
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
filemap fp;
|
||||
uint8_t* data = nullptr;
|
||||
uint size = 0;
|
||||
bool readonly = false;
|
||||
bool endian = 1;
|
||||
};
|
||||
|
||||
}}
|
221
mosaic/context.hpp
Normal file
221
mosaic/context.hpp
Normal file
|
@ -0,0 +1,221 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall { namespace mosaic {
|
||||
|
||||
struct context {
|
||||
context() {
|
||||
reset();
|
||||
}
|
||||
|
||||
auto objectWidth() const -> uint { return blockWidth * tileWidth * mosaicWidth + paddingWidth; }
|
||||
auto objectHeight() const -> uint { return blockHeight * tileHeight * mosaicHeight + paddingHeight; }
|
||||
auto objectSize() const -> uint {
|
||||
uint size = blockStride * tileWidth * tileHeight * mosaicWidth * mosaicHeight
|
||||
+ blockOffset * tileHeight * mosaicWidth * mosaicHeight
|
||||
+ tileStride * mosaicWidth * mosaicHeight
|
||||
+ tileOffset * mosaicHeight;
|
||||
return max(1u, size);
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
offset = 0;
|
||||
width = 0;
|
||||
height = 0;
|
||||
count = 0;
|
||||
|
||||
endian = 1;
|
||||
order = 0;
|
||||
depth = 1;
|
||||
|
||||
blockWidth = 1;
|
||||
blockHeight = 1;
|
||||
blockStride = 0;
|
||||
blockOffset = 0;
|
||||
block.reset();
|
||||
|
||||
tileWidth = 1;
|
||||
tileHeight = 1;
|
||||
tileStride = 0;
|
||||
tileOffset = 0;
|
||||
tile.reset();
|
||||
|
||||
mosaicWidth = 1;
|
||||
mosaicHeight = 1;
|
||||
mosaicStride = 0;
|
||||
mosaicOffset = 0;
|
||||
mosaic.reset();
|
||||
|
||||
paddingWidth = 0;
|
||||
paddingHeight = 0;
|
||||
paddingColor = 0;
|
||||
palette.reset();
|
||||
}
|
||||
|
||||
auto eval(const string& expression) -> uint {
|
||||
if(auto result = Eval::integer(expression)) return result();
|
||||
return 0u;
|
||||
}
|
||||
|
||||
auto eval(vector<uint>& buffer, const string& expression_) -> void {
|
||||
string expression = expression_;
|
||||
bool function = false;
|
||||
for(auto& c : expression) {
|
||||
if(c == '(') function = true;
|
||||
if(c == ')') function = false;
|
||||
if(c == ',' && function == true) c = ';';
|
||||
}
|
||||
|
||||
lstring list = expression.split(",");
|
||||
for(auto& item : list) {
|
||||
item.strip();
|
||||
if(item.match("f(?*) ?*")) {
|
||||
item.ltrim("f(", 1L);
|
||||
lstring part = item.split(") ", 1L);
|
||||
lstring args = part[0].split(";", 3L).strip();
|
||||
|
||||
uint length = eval(args(0, "0"));
|
||||
uint offset = eval(args(1, "0"));
|
||||
uint stride = eval(args(2, "0"));
|
||||
if(args.size() < 2) offset = buffer.size();
|
||||
if(args.size() < 3) stride = 1;
|
||||
|
||||
for(uint n = 0; n < length; n++) {
|
||||
string fn = part[1];
|
||||
fn.replace("n", string{n});
|
||||
fn.replace("o", string{offset});
|
||||
fn.replace("p", string{buffer.size()});
|
||||
buffer.resize(offset + 1);
|
||||
buffer[offset] = eval(fn);
|
||||
offset += stride;
|
||||
}
|
||||
} else if(item.match("base64*")) {
|
||||
uint offset = 0;
|
||||
item.ltrim("base64", 1L);
|
||||
if(item.match("(?*) *")) {
|
||||
item.ltrim("(", 1L);
|
||||
lstring part = item.split(") ", 1L);
|
||||
offset = eval(part[0]);
|
||||
item = part(1, "");
|
||||
}
|
||||
item.strip();
|
||||
for(auto& c : item) {
|
||||
if(c >= 'A' && c <= 'Z') buffer.append(offset + c - 'A' + 0);
|
||||
if(c >= 'a' && c <= 'z') buffer.append(offset + c - 'a' + 26);
|
||||
if(c >= '0' && c <= '9') buffer.append(offset + c - '0' + 52);
|
||||
if(c == '-') buffer.append(offset + 62);
|
||||
if(c == '_') buffer.append(offset + 63);
|
||||
}
|
||||
} else if(item.match("file *")) {
|
||||
item.ltrim("file ", 1L);
|
||||
item.strip();
|
||||
//...
|
||||
} else if(item.empty() == false) {
|
||||
buffer.append(eval(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto parse(const string& data) -> void {
|
||||
reset();
|
||||
|
||||
lstring lines = data.split("\n");
|
||||
for(auto& line : lines) {
|
||||
lstring part = line.split(":", 1L).strip();
|
||||
if(part.size() != 2) continue;
|
||||
|
||||
if(part[0] == "offset") offset = eval(part[1]);
|
||||
if(part[0] == "width") width = eval(part[1]);
|
||||
if(part[0] == "height") height = eval(part[1]);
|
||||
if(part[0] == "count") count = eval(part[1]);
|
||||
|
||||
if(part[0] == "endian") endian = eval(part[1]);
|
||||
if(part[0] == "order") order = eval(part[1]);
|
||||
if(part[0] == "depth") depth = eval(part[1]);
|
||||
|
||||
if(part[0] == "blockWidth") blockWidth = eval(part[1]);
|
||||
if(part[0] == "blockHeight") blockHeight = eval(part[1]);
|
||||
if(part[0] == "blockStride") blockStride = eval(part[1]);
|
||||
if(part[0] == "blockOffset") blockOffset = eval(part[1]);
|
||||
if(part[0] == "block") eval(block, part[1]);
|
||||
|
||||
if(part[0] == "tileWidth") tileWidth = eval(part[1]);
|
||||
if(part[0] == "tileHeight") tileHeight = eval(part[1]);
|
||||
if(part[0] == "tileStride") tileStride = eval(part[1]);
|
||||
if(part[0] == "tileOffset") tileOffset = eval(part[1]);
|
||||
if(part[0] == "tile") eval(tile, part[1]);
|
||||
|
||||
if(part[0] == "mosaicWidth") mosaicWidth = eval(part[1]);
|
||||
if(part[0] == "mosaicHeight") mosaicHeight = eval(part[1]);
|
||||
if(part[0] == "mosaicStride") mosaicStride = eval(part[1]);
|
||||
if(part[0] == "mosaicOffset") mosaicOffset = eval(part[1]);
|
||||
if(part[0] == "mosaic") eval(mosaic, part[1]);
|
||||
|
||||
if(part[0] == "paddingWidth") paddingWidth = eval(part[1]);
|
||||
if(part[0] == "paddingHeight") paddingHeight = eval(part[1]);
|
||||
if(part[0] == "paddingColor") paddingColor = eval(part[1]);
|
||||
if(part[0] == "palette") eval(palette, part[1]);
|
||||
}
|
||||
|
||||
sanitize();
|
||||
}
|
||||
|
||||
auto load(const string& filename) -> bool {
|
||||
if(auto filedata = string::read(filename)) {
|
||||
parse(filedata);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto sanitize() -> void {
|
||||
if(depth < 1) depth = 1;
|
||||
if(depth > 24) depth = 24;
|
||||
|
||||
if(blockWidth < 1) blockWidth = 1;
|
||||
if(blockHeight < 1) blockHeight = 1;
|
||||
|
||||
if(tileWidth < 1) tileWidth = 1;
|
||||
if(tileHeight < 1) tileHeight = 1;
|
||||
|
||||
if(mosaicWidth < 1) mosaicWidth = 1;
|
||||
if(mosaicHeight < 1) mosaicHeight = 1;
|
||||
|
||||
//set alpha to full opacity
|
||||
paddingColor |= 255u << 24;
|
||||
for(auto& color : palette) color |= 255u << 24;
|
||||
}
|
||||
|
||||
uint offset;
|
||||
uint width;
|
||||
uint height;
|
||||
uint count;
|
||||
|
||||
bool endian; //0 = lsb, 1 = msb
|
||||
bool order; //0 = linear, 1 = planar
|
||||
uint depth; //1 - 24bpp
|
||||
|
||||
uint blockWidth;
|
||||
uint blockHeight;
|
||||
uint blockStride;
|
||||
uint blockOffset;
|
||||
vector<uint> block;
|
||||
|
||||
uint tileWidth;
|
||||
uint tileHeight;
|
||||
uint tileStride;
|
||||
uint tileOffset;
|
||||
vector<uint> tile;
|
||||
|
||||
uint mosaicWidth;
|
||||
uint mosaicHeight;
|
||||
uint mosaicStride;
|
||||
uint mosaicOffset;
|
||||
vector<uint> mosaic;
|
||||
|
||||
uint paddingWidth;
|
||||
uint paddingHeight;
|
||||
uint paddingColor;
|
||||
vector<uint> palette;
|
||||
};
|
||||
|
||||
}}
|
119
mosaic/parser.hpp
Normal file
119
mosaic/parser.hpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall { namespace mosaic {
|
||||
|
||||
struct parser {
|
||||
//export from bitstream to canvas
|
||||
auto load(bitstream& stream, uint64_t offset, context& ctx, uint width, uint height) -> void {
|
||||
canvas.allocate(width, height);
|
||||
canvas.fill(ctx.paddingColor);
|
||||
parse(1, stream, offset, ctx, width, height);
|
||||
}
|
||||
|
||||
//import from canvas to bitstream
|
||||
auto save(bitstream& stream, uint64_t offset, context& ctx) -> bool {
|
||||
if(stream.readonly) return false;
|
||||
parse(0, stream, offset, ctx, canvas.width(), canvas.height());
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
auto read(uint x, uint y) const -> uint32_t {
|
||||
uint addr = y * canvas.width() + x;
|
||||
if(addr >= canvas.width() * canvas.height()) return 0u;
|
||||
auto buffer = (uint32_t*)canvas.data();
|
||||
return buffer[addr];
|
||||
}
|
||||
|
||||
auto write(uint x, uint y, uint32_t data) -> void {
|
||||
uint addr = y * canvas.width() + x;
|
||||
if(addr >= canvas.width() * canvas.height()) return;
|
||||
auto buffer = (uint32_t*)canvas.data();
|
||||
buffer[addr] = data;
|
||||
}
|
||||
|
||||
auto parse(bool load, bitstream& stream, uint64_t offset, context& ctx, uint width, uint height) -> void {
|
||||
stream.endian = ctx.endian;
|
||||
uint canvasWidth = width / (ctx.mosaicWidth * ctx.tileWidth * ctx.blockWidth + ctx.paddingWidth);
|
||||
uint canvasHeight = height / (ctx.mosaicHeight * ctx.tileHeight * ctx.blockHeight + ctx.paddingHeight);
|
||||
uint bitsPerBlock = ctx.depth * ctx.blockWidth * ctx.blockHeight;
|
||||
|
||||
uint objectOffset = 0;
|
||||
for(uint objectY = 0; objectY < canvasHeight; objectY++) {
|
||||
for(uint objectX = 0; objectX < canvasWidth; objectX++) {
|
||||
if(objectOffset >= ctx.count && ctx.count > 0) break;
|
||||
uint objectIX = objectX * ctx.objectWidth();
|
||||
uint objectIY = objectY * ctx.objectHeight();
|
||||
objectOffset++;
|
||||
|
||||
uint mosaicOffset = 0;
|
||||
for(uint mosaicY = 0; mosaicY < ctx.mosaicHeight; mosaicY++) {
|
||||
for(uint mosaicX = 0; mosaicX < ctx.mosaicWidth; mosaicX++) {
|
||||
uint mosaicData = ctx.mosaic(mosaicOffset, mosaicOffset);
|
||||
uint mosaicIX = (mosaicData % ctx.mosaicWidth) * (ctx.tileWidth * ctx.blockWidth);
|
||||
uint mosaicIY = (mosaicData / ctx.mosaicWidth) * (ctx.tileHeight * ctx.blockHeight);
|
||||
mosaicOffset++;
|
||||
|
||||
uint tileOffset = 0;
|
||||
for(uint tileY = 0; tileY < ctx.tileHeight; tileY++) {
|
||||
for(uint tileX = 0; tileX < ctx.tileWidth; tileX++) {
|
||||
uint tileData = ctx.tile(tileOffset, tileOffset);
|
||||
uint tileIX = (tileData % ctx.tileWidth) * ctx.blockWidth;
|
||||
uint tileIY = (tileData / ctx.tileWidth) * ctx.blockHeight;
|
||||
tileOffset++;
|
||||
|
||||
uint blockOffset = 0;
|
||||
for(uint blockY = 0; blockY < ctx.blockHeight; blockY++) {
|
||||
for(uint blockX = 0; blockX < ctx.blockWidth; blockX++) {
|
||||
if(load) {
|
||||
uint palette = 0;
|
||||
for(uint n = 0; n < ctx.depth; n++) {
|
||||
uint index = blockOffset++;
|
||||
if(ctx.order == 1) index = (index % ctx.depth) * ctx.blockWidth * ctx.blockHeight + (index / ctx.depth);
|
||||
palette |= stream.read(offset + ctx.block(index, index)) << n;
|
||||
}
|
||||
|
||||
write(
|
||||
objectIX + mosaicIX + tileIX + blockX,
|
||||
objectIY + mosaicIY + tileIY + blockY,
|
||||
ctx.palette(palette, palette)
|
||||
);
|
||||
} else /* save */ {
|
||||
uint32_t palette = read(
|
||||
objectIX + mosaicIX + tileIX + blockX,
|
||||
objectIY + mosaicIY + tileIY + blockY
|
||||
);
|
||||
|
||||
for(uint n = 0; n < ctx.depth; n++) {
|
||||
uint index = blockOffset++;
|
||||
if(ctx.order == 1) index = (index % ctx.depth) * ctx.blockWidth * ctx.blockHeight + (index / ctx.depth);
|
||||
stream.write(offset + ctx.block(index, index), palette & 1);
|
||||
palette >>= 1;
|
||||
}
|
||||
}
|
||||
} //blockX
|
||||
} //blockY
|
||||
|
||||
offset += ctx.blockStride;
|
||||
} //tileX
|
||||
|
||||
offset += ctx.blockOffset;
|
||||
} //tileY
|
||||
|
||||
offset += ctx.tileStride;
|
||||
} //mosaicX
|
||||
|
||||
offset += ctx.tileOffset;
|
||||
} //mosaicY
|
||||
|
||||
offset += ctx.mosaicStride;
|
||||
} //objectX
|
||||
|
||||
offset += ctx.mosaicOffset;
|
||||
} //objectY
|
||||
}
|
||||
|
||||
image canvas;
|
||||
};
|
||||
|
||||
}}
|
76
nall.hpp
Normal file
76
nall.hpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
|
||||
/* nall
|
||||
* author: byuu
|
||||
* license: ISC
|
||||
*
|
||||
* nall is a header library that provides both fundamental and useful classes
|
||||
* its goals are portability, consistency, minimalism and reusability
|
||||
*/
|
||||
|
||||
//include the most common nall headers with one statement
|
||||
//does not include the most obscure components with high cost and low usage
|
||||
|
||||
#include <nall/platform.hpp>
|
||||
|
||||
#include <nall/algorithm.hpp>
|
||||
#include <nall/any.hpp>
|
||||
#include <nall/atoi.hpp>
|
||||
#include <nall/bit.hpp>
|
||||
#include <nall/bitvector.hpp>
|
||||
#include <nall/config.hpp>
|
||||
#include <nall/directory.hpp>
|
||||
#include <nall/dl.hpp>
|
||||
#include <nall/endian.hpp>
|
||||
#include <nall/file.hpp>
|
||||
#include <nall/filemap.hpp>
|
||||
#include <nall/function.hpp>
|
||||
#include <nall/hashset.hpp>
|
||||
#include <nall/hid.hpp>
|
||||
#include <nall/image.hpp>
|
||||
#include <nall/inode.hpp>
|
||||
#include <nall/interpolation.hpp>
|
||||
#include <nall/intrinsics.hpp>
|
||||
#include <nall/map.hpp>
|
||||
#include <nall/matrix.hpp>
|
||||
#include <nall/maybe.hpp>
|
||||
#include <nall/memory.hpp>
|
||||
#include <nall/primitives.hpp>
|
||||
#include <nall/property.hpp>
|
||||
#include <nall/random.hpp>
|
||||
#include <nall/range.hpp>
|
||||
#include <nall/run.hpp>
|
||||
#include <nall/serializer.hpp>
|
||||
#include <nall/set.hpp>
|
||||
#include <nall/shared-pointer.hpp>
|
||||
#include <nall/sort.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/stream.hpp>
|
||||
#include <nall/string.hpp>
|
||||
#include <nall/thread.hpp>
|
||||
#include <nall/traits.hpp>
|
||||
#include <nall/unique-pointer.hpp>
|
||||
#include <nall/utility.hpp>
|
||||
#include <nall/varint.hpp>
|
||||
#include <nall/vector.hpp>
|
||||
#include <nall/decode/base64.hpp>
|
||||
#include <nall/decode/bmp.hpp>
|
||||
#include <nall/decode/gzip.hpp>
|
||||
#include <nall/decode/inflate.hpp>
|
||||
#include <nall/decode/png.hpp>
|
||||
#include <nall/decode/url.hpp>
|
||||
#include <nall/decode/zip.hpp>
|
||||
#include <nall/encode/base64.hpp>
|
||||
#include <nall/encode/url.hpp>
|
||||
#include <nall/hash/crc16.hpp>
|
||||
#include <nall/hash/crc32.hpp>
|
||||
#include <nall/hash/sha256.hpp>
|
||||
|
||||
#if defined(PLATFORM_WINDOWS)
|
||||
#include <nall/windows/registry.hpp>
|
||||
#include <nall/windows/utf8.hpp>
|
||||
#endif
|
||||
|
||||
#if defined(API_POSIX)
|
||||
#include <nall/serial.hpp>
|
||||
#endif
|
133
platform.hpp
Normal file
133
platform.hpp
Normal file
|
@ -0,0 +1,133 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/intrinsics.hpp>
|
||||
|
||||
namespace Math {
|
||||
static const long double e = 2.71828182845904523536;
|
||||
static const long double Pi = 3.14159265358979323846;
|
||||
}
|
||||
|
||||
#if defined(PLATFORM_WINDOWS)
|
||||
//minimum version needed for _wstat64, AI_ADDRCONFIG, etc
|
||||
#undef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#undef __MSVCRT_VERSION__
|
||||
#define __MSVCRT_VERSION__ _WIN32_WINNT
|
||||
#include <nall/windows/utf8.hpp>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#if defined(PLATFORM_WINDOWS)
|
||||
#include <io.h>
|
||||
#include <direct.h>
|
||||
#include <shlobj.h>
|
||||
#include <wchar.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/wait.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <poll.h>
|
||||
#endif
|
||||
|
||||
#if defined(COMPILER_VISUALCPP)
|
||||
#define va_copy(dest, src) ((dest) = (src))
|
||||
#endif
|
||||
|
||||
#if defined(PLATFORM_WINDOWS)
|
||||
//fight Microsoft's ardent efforts at vendor lock-in
|
||||
|
||||
#undef interface
|
||||
#define dllexport __declspec(dllexport)
|
||||
#define MSG_NOSIGNAL 0
|
||||
|
||||
extern "C" {
|
||||
using pollfd = WSAPOLLFD;
|
||||
}
|
||||
|
||||
inline auto access(const char* path, int amode) -> int { return _waccess(nall::utf16_t(path), amode); }
|
||||
inline auto getcwd(char* buf, size_t size) -> char* { wchar_t wpath[PATH_MAX] = L""; if(!_wgetcwd(wpath, size)) return nullptr; strcpy(buf, nall::utf8_t(wpath)); return buf; }
|
||||
inline auto mkdir(const char* path, int mode) -> int { return _wmkdir(nall::utf16_t(path)); }
|
||||
inline auto poll(struct pollfd fds[], unsigned long nfds, int timeout) -> int { return WSAPoll(fds, nfds, timeout); }
|
||||
inline auto putenv(const char* value) -> int { return _wputenv(nall::utf16_t(value)); }
|
||||
inline auto realpath(const char* file_name, char* resolved_name) -> char* { wchar_t wfile_name[PATH_MAX] = L""; if(!_wfullpath(wfile_name, nall::utf16_t(file_name), PATH_MAX)) return nullptr; strcpy(resolved_name, nall::utf8_t(wfile_name)); return resolved_name; }
|
||||
inline auto rename(const char* oldname, const char* newname) -> int { return _wrename(nall::utf16_t(oldname), nall::utf16_t(newname)); }
|
||||
inline auto usleep(unsigned milliseconds) -> void { Sleep(milliseconds / 1000); }
|
||||
|
||||
namespace nall {
|
||||
//network functions take void*, not char*. this allows them to be used without casting
|
||||
|
||||
inline auto recv(int socket, void* buffer, size_t length, int flags) -> ssize_t {
|
||||
return ::recv(socket, (char*)buffer, length, flags);
|
||||
}
|
||||
|
||||
inline auto send(int socket, const void* buffer, size_t length, int flags) -> ssize_t {
|
||||
return ::send(socket, (const char*)buffer, length, flags);
|
||||
}
|
||||
|
||||
inline auto setsockopt(int socket, int level, int option_name, const void* option_value, socklen_t option_len) -> int {
|
||||
return ::setsockopt(socket, level, option_name, (const char*)option_value, option_len);
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define dllexport
|
||||
#endif
|
||||
|
||||
#if defined(PLATFORM_MACOSX)
|
||||
#define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
|
||||
#if defined(COMPILER_CLANG) || defined(COMPILER_GCC)
|
||||
#define neverinline __attribute__((noinline))
|
||||
#define alwaysinline inline __attribute__((always_inline))
|
||||
#if !defined(PLATFORM_MACOSX)
|
||||
//todo: we want this prefix; but it causes compilation errors
|
||||
#define deprecated __attribute__((deprecated))
|
||||
#endif
|
||||
#elif defined(COMPILER_VISUALCPP)
|
||||
#define neverinline __declspec(noinline)
|
||||
#define alwaysinline inline __forceinline
|
||||
#define deprecated __declspec(deprecated)
|
||||
#else
|
||||
#define neverinline
|
||||
#define alwaysinline inline
|
||||
#define deprecated
|
||||
#endif
|
||||
|
||||
#if defined(COMPILER_CLANG) || defined(COMPILER_GCC)
|
||||
#define unreachable __builtin_unreachable()
|
||||
#else
|
||||
#define unreachable throw
|
||||
#endif
|
||||
|
||||
#if defined(COMPILER_GCC) && __GNUC__ == 4 && __GNUC_MINOR__ <= 7
|
||||
//GCC 4.7.x has a bug (#54849) when specifying override with a trailing return type:
|
||||
//auto function() -> return_type override; //this is the syntax that the C++11 standard requires
|
||||
//auto function() override -> return_type; //this is the syntax that GCC 4.7.x requires
|
||||
//in order to compile code correctly with both compilers, we disable the override keyword for GCC
|
||||
#define override
|
||||
#endif
|
114
posix/service.hpp
Normal file
114
posix/service.hpp
Normal file
|
@ -0,0 +1,114 @@
|
|||
#pragma once
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct service {
|
||||
inline explicit operator bool() const;
|
||||
inline auto command(const string& name, const string& command) -> bool;
|
||||
inline auto receive() -> string;
|
||||
inline auto name() const -> string;
|
||||
inline auto stop() const -> bool;
|
||||
|
||||
private:
|
||||
shared_memory shared;
|
||||
string _name;
|
||||
bool _stop = false;
|
||||
};
|
||||
|
||||
service::operator bool() const {
|
||||
return (bool)shared;
|
||||
}
|
||||
|
||||
//returns true on new service process creation (false is not necessarily an error)
|
||||
auto service::command(const string& name, const string& command) -> bool {
|
||||
if(!name) return false;
|
||||
if(!command) return print("[{0}] usage: {service} command\n"
|
||||
"commands:\n"
|
||||
" status : query whether service is running\n"
|
||||
" start : start service if it is not running\n"
|
||||
" stop : stop service if it is running\n"
|
||||
" remove : remove semaphore lock if service crashed\n"
|
||||
" {value} : send custom command to service\n"
|
||||
"", format{name}), false;
|
||||
|
||||
if(shared.open(name, 4096)) {
|
||||
if(command == "start") {
|
||||
print("[{0}] already started\n", format{name});
|
||||
} else if(command == "status") {
|
||||
print("[{0}] running\n", format{name});
|
||||
}
|
||||
if(auto data = shared.acquire()) {
|
||||
if(command == "stop") print("[{0}] stopped\n", format{name});
|
||||
memory::copy(data, command.data(), min(command.size(), 4096));
|
||||
shared.release();
|
||||
}
|
||||
if(command == "remove") {
|
||||
shared.remove();
|
||||
print("[{0}] removed\n", format{name});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(command == "start") {
|
||||
if(shared.create(name, 4096)) {
|
||||
print("[{0}] started\n", format{name});
|
||||
auto pid = fork();
|
||||
if(pid == 0) {
|
||||
signal(SIGHUP, SIG_IGN);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
_name = name;
|
||||
return true;
|
||||
}
|
||||
shared.close();
|
||||
} else {
|
||||
print("[{0}] start failed ({1})\n", format{name, strerror(errno)});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(command == "status") {
|
||||
print("[{0}] stopped\n", format{name});
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto service::receive() -> string {
|
||||
string command;
|
||||
if(shared) {
|
||||
if(auto data = shared.acquire()) {
|
||||
if(*data) {
|
||||
command.resize(4095);
|
||||
memory::copy(command.get(), data, 4095);
|
||||
memory::fill(data, 4096);
|
||||
}
|
||||
shared.release();
|
||||
if(command == "remove") {
|
||||
_stop = true;
|
||||
return "";
|
||||
} else if(command == "start") {
|
||||
return "";
|
||||
} else if(command == "status") {
|
||||
return "";
|
||||
} else if(command == "stop") {
|
||||
_stop = true;
|
||||
shared.remove();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
auto service::name() const -> string {
|
||||
return _name;
|
||||
}
|
||||
|
||||
auto service::stop() const -> bool {
|
||||
return _stop;
|
||||
}
|
||||
|
||||
}
|
151
posix/shared-memory.hpp
Normal file
151
posix/shared-memory.hpp
Normal file
|
@ -0,0 +1,151 @@
|
|||
#pragma once
|
||||
|
||||
#include <semaphore.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct shared_memory {
|
||||
shared_memory() = default;
|
||||
shared_memory(const shared_memory&) = delete;
|
||||
auto operator=(const shared_memory&) -> shared_memory& = delete;
|
||||
|
||||
~shared_memory() {
|
||||
reset();
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return _mode != mode::inactive;
|
||||
}
|
||||
|
||||
auto empty() const -> bool {
|
||||
return _mode == mode::inactive;
|
||||
}
|
||||
|
||||
auto size() const -> unsigned {
|
||||
return _size;
|
||||
}
|
||||
|
||||
auto acquired() const -> bool {
|
||||
return _acquired;
|
||||
}
|
||||
|
||||
auto acquire() -> uint8_t* {
|
||||
if(!acquired()) {
|
||||
sem_wait(_semaphore);
|
||||
_acquired = true;
|
||||
}
|
||||
return _data;
|
||||
}
|
||||
|
||||
auto release() -> void {
|
||||
if(acquired()) {
|
||||
sem_post(_semaphore);
|
||||
_acquired = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
release();
|
||||
if(_mode == mode::server) return remove();
|
||||
if(_mode == mode::client) return close();
|
||||
}
|
||||
|
||||
auto create(const string& name, unsigned size) -> bool {
|
||||
reset();
|
||||
|
||||
_name = {"/nall-", string{name}.transform("/:", "--")};
|
||||
_size = size;
|
||||
|
||||
//O_CREAT | O_EXCL seems to throw ENOENT even when semaphore does not exist ...
|
||||
_semaphore = sem_open(_name, O_CREAT, 0644, 1);
|
||||
if(_semaphore == SEM_FAILED) return remove(), false;
|
||||
|
||||
_descriptor = shm_open(_name, O_CREAT | O_TRUNC | O_RDWR, 0644);
|
||||
if(_descriptor < 0) return remove(), false;
|
||||
|
||||
if(ftruncate(_descriptor, _size) != 0) return remove(), false;
|
||||
|
||||
_data = (uint8_t*)mmap(nullptr, _size, PROT_READ | PROT_WRITE, MAP_SHARED, _descriptor, 0);
|
||||
if(_data == MAP_FAILED) return remove(), false;
|
||||
|
||||
memory::fill(_data, _size);
|
||||
|
||||
_mode = mode::server;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto remove() -> void {
|
||||
if(_data) {
|
||||
munmap(_data, _size);
|
||||
_data = nullptr;
|
||||
}
|
||||
|
||||
if(_descriptor) {
|
||||
::close(_descriptor);
|
||||
shm_unlink(_name);
|
||||
_descriptor = -1;
|
||||
}
|
||||
|
||||
if(_semaphore) {
|
||||
sem_close(_semaphore);
|
||||
sem_unlink(_name);
|
||||
_semaphore = nullptr;
|
||||
}
|
||||
|
||||
_mode = mode::inactive;
|
||||
_name = "";
|
||||
_size = 0;
|
||||
}
|
||||
|
||||
auto open(const string& name, unsigned size) -> bool {
|
||||
reset();
|
||||
|
||||
_name = {"/nall-", string{name}.transform("/:", "--")};
|
||||
_size = size;
|
||||
|
||||
_semaphore = sem_open(_name, 0, 0644);
|
||||
if(_semaphore == SEM_FAILED) return close(), false;
|
||||
|
||||
_descriptor = shm_open(_name, O_RDWR, 0644);
|
||||
if(_descriptor < 0) return close(), false;
|
||||
|
||||
_data = (uint8_t*)mmap(nullptr, _size, PROT_READ | PROT_WRITE, MAP_SHARED, _descriptor, 0);
|
||||
if(_data == MAP_FAILED) return close(), false;
|
||||
|
||||
_mode = mode::client;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto close() -> void {
|
||||
if(_data) {
|
||||
munmap(_data, _size);
|
||||
_data = nullptr;
|
||||
}
|
||||
|
||||
if(_descriptor) {
|
||||
::close(_descriptor);
|
||||
_descriptor = -1;
|
||||
}
|
||||
|
||||
if(_semaphore) {
|
||||
sem_close(_semaphore);
|
||||
_semaphore = nullptr;
|
||||
}
|
||||
|
||||
_mode = mode::inactive;
|
||||
_name = "";
|
||||
_size = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
enum class mode : unsigned { server, client, inactive } _mode = mode::inactive;
|
||||
string _name;
|
||||
sem_t* _semaphore = nullptr;
|
||||
signed _descriptor = -1;
|
||||
uint8_t* _data = nullptr;
|
||||
unsigned _size = 0;
|
||||
bool _acquired = false;
|
||||
};
|
||||
|
||||
}
|
350
primitives.hpp
Normal file
350
primitives.hpp
Normal file
|
@ -0,0 +1,350 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/serializer.hpp>
|
||||
#include <nall/traits.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct Boolean {
|
||||
inline Boolean() : data(false) {}
|
||||
template<typename T> inline Boolean(const T& value) : data(value) {}
|
||||
|
||||
inline operator bool() const { return data; }
|
||||
template<typename T> inline auto& operator=(const T& value) { data = value; return *this; }
|
||||
|
||||
inline auto serialize(serializer& s) { s(data); }
|
||||
|
||||
private:
|
||||
bool data;
|
||||
};
|
||||
|
||||
template<uint Bits> struct Natural {
|
||||
using type =
|
||||
type_if<expression<Bits <= 8>, uint8_t,
|
||||
type_if<expression<Bits <= 16>, uint16_t,
|
||||
type_if<expression<Bits <= 32>, uint32_t,
|
||||
type_if<expression<Bits <= 64>, uint64_t,
|
||||
void>>>>;
|
||||
|
||||
enum : type { Mask = ~0ull >> (64 - Bits) };
|
||||
|
||||
inline Natural() : data(0) {}
|
||||
template<typename T> inline Natural(const T& value) { assign(value); }
|
||||
|
||||
inline operator type() const { return data; }
|
||||
template<typename T> inline auto& operator=(const T& value) { assign(value); return *this; }
|
||||
|
||||
inline auto operator++(int) { type value = data; assign(data + 1); return value; }
|
||||
inline auto operator--(int) { type value = data; assign(data - 1); return value; }
|
||||
|
||||
inline auto& operator++() { assign(data + 1); return *this; }
|
||||
inline auto& operator--() { assign(data - 1); return *this; }
|
||||
|
||||
inline auto& operator &=(const type value) { assign(data & value); return *this; }
|
||||
inline auto& operator |=(const type value) { assign(data | value); return *this; }
|
||||
inline auto& operator ^=(const type value) { assign(data ^ value); return *this; }
|
||||
inline auto& operator<<=(const type value) { assign(data << value); return *this; }
|
||||
inline auto& operator>>=(const type value) { assign(data >> value); return *this; }
|
||||
inline auto& operator +=(const type value) { assign(data + value); return *this; }
|
||||
inline auto& operator -=(const type value) { assign(data - value); return *this; }
|
||||
inline auto& operator *=(const type value) { assign(data * value); return *this; }
|
||||
inline auto& operator /=(const type value) { assign(data / value); return *this; }
|
||||
inline auto& operator %=(const type value) { assign(data % value); return *this; }
|
||||
|
||||
inline auto serialize(serializer& s) { s(data); }
|
||||
|
||||
struct Reference {
|
||||
inline Reference(Natural& source, uint lo, uint hi) : source(source), Lo(lo), Hi(hi) {}
|
||||
|
||||
inline operator type() const {
|
||||
const type RangeBits = Hi - Lo + 1;
|
||||
const type RangeMask = (((1ull << RangeBits) - 1) << Lo) & Mask;
|
||||
return (source & RangeMask) >> Lo;
|
||||
}
|
||||
|
||||
inline auto& operator=(const type value) {
|
||||
const type RangeBits = Hi - Lo + 1;
|
||||
const type RangeMask = (((1ull << RangeBits) - 1) << Lo) & Mask;
|
||||
source = (source & ~RangeMask) | ((value << Lo) & RangeMask);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
Natural& source;
|
||||
const type Lo;
|
||||
const type Hi;
|
||||
};
|
||||
|
||||
inline auto bits(uint lo, uint hi) -> Reference { return {*this, lo < hi ? lo : hi, hi > lo ? hi : lo}; }
|
||||
inline auto bit(uint index) -> Reference { return {*this, index, index}; }
|
||||
inline auto byte(uint index) -> Reference { return {*this, index * 8 + 0, index * 8 + 7}; }
|
||||
|
||||
inline auto clamp(uint bits) -> uintmax {
|
||||
const uintmax b = 1ull << (bits - 1);
|
||||
const uintmax m = b * 2 - 1;
|
||||
return data < m ? data : m;
|
||||
}
|
||||
|
||||
inline auto clip(uint bits) -> uintmax {
|
||||
const uintmax b = 1ull << (bits - 1);
|
||||
const uintmax m = b * 2 - 1;
|
||||
return data & m;
|
||||
}
|
||||
|
||||
private:
|
||||
auto assign(type value) -> void {
|
||||
data = value & Mask;
|
||||
}
|
||||
|
||||
type data;
|
||||
};
|
||||
|
||||
template<uint Bits> struct Integer {
|
||||
using type =
|
||||
type_if<expression<Bits <= 8>, int8_t,
|
||||
type_if<expression<Bits <= 16>, int16_t,
|
||||
type_if<expression<Bits <= 32>, int32_t,
|
||||
type_if<expression<Bits <= 64>, int64_t,
|
||||
void>>>>;
|
||||
using utype = typename Natural<Bits>::type;
|
||||
|
||||
enum : utype { Mask = ~0ull >> (64 - Bits), Sign = 1ull << (Bits - 1) };
|
||||
|
||||
inline Integer() : data(0) {}
|
||||
template<typename T> inline Integer(const T& value) { assign(value); }
|
||||
|
||||
inline operator type() const { return data; }
|
||||
template<typename T> inline auto& operator=(const T& value) { assign(value); return *this; }
|
||||
|
||||
inline auto operator++(int) { type value = data; assign(data + 1); return value; }
|
||||
inline auto operator--(int) { type value = data; assign(data - 1); return value; }
|
||||
|
||||
inline auto& operator++() { assign(data + 1); return *this; }
|
||||
inline auto& operator--() { assign(data - 1); return *this; }
|
||||
|
||||
inline auto& operator &=(const type value) { assign(data & value); return *this; }
|
||||
inline auto& operator |=(const type value) { assign(data | value); return *this; }
|
||||
inline auto& operator ^=(const type value) { assign(data ^ value); return *this; }
|
||||
inline auto& operator<<=(const type value) { assign(data << value); return *this; }
|
||||
inline auto& operator>>=(const type value) { assign(data >> value); return *this; }
|
||||
inline auto& operator +=(const type value) { assign(data + value); return *this; }
|
||||
inline auto& operator -=(const type value) { assign(data - value); return *this; }
|
||||
inline auto& operator *=(const type value) { assign(data * value); return *this; }
|
||||
inline auto& operator /=(const type value) { assign(data / value); return *this; }
|
||||
inline auto& operator %=(const type value) { assign(data % value); return *this; }
|
||||
|
||||
inline auto serialize(serializer& s) { s(data); }
|
||||
|
||||
struct Reference {
|
||||
inline Reference(Integer& source, uint lo, uint hi) : source(source), Lo(lo), Hi(hi) {}
|
||||
|
||||
inline operator utype() const {
|
||||
const type RangeBits = Hi - Lo + 1;
|
||||
const type RangeMask = (((1ull << RangeBits) - 1) << Lo) & Mask;
|
||||
return ((utype)source & RangeMask) >> Lo;
|
||||
}
|
||||
|
||||
inline auto& operator=(const utype value) {
|
||||
const type RangeBits = Hi - Lo + 1;
|
||||
const type RangeMask = (((1ull << RangeBits) - 1) << Lo) & Mask;
|
||||
source = ((utype)source & ~RangeMask) | ((value << Lo) & RangeMask);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
Integer& source;
|
||||
const uint Lo;
|
||||
const uint Hi;
|
||||
};
|
||||
|
||||
inline auto bits(uint lo, uint hi) -> Reference { return {*this, lo, hi}; }
|
||||
inline auto bit(uint index) -> Reference { return {*this, index, index}; }
|
||||
inline auto byte(uint index) -> Reference { return {*this, index * 8 + 0, index * 8 + 7}; }
|
||||
|
||||
inline auto clamp(uint bits) -> intmax {
|
||||
const intmax b = 1ull << (bits - 1);
|
||||
const intmax m = b - 1;
|
||||
return data > m ? m : data < -b ? -b : data;
|
||||
}
|
||||
|
||||
inline auto clip(uint bits) -> intmax {
|
||||
const uintmax b = 1ull << (bits - 1);
|
||||
const uintmax m = b * 2 - 1;
|
||||
return ((data & m) ^ b) - b;
|
||||
}
|
||||
|
||||
private:
|
||||
auto assign(type value) -> void {
|
||||
data = ((value & Mask) ^ Sign) - Sign;
|
||||
}
|
||||
|
||||
type data;
|
||||
};
|
||||
|
||||
template<uint Bits> struct Real {
|
||||
using type =
|
||||
type_if<expression<Bits == 32>, float32_t,
|
||||
type_if<expression<Bits == 64>, float64_t,
|
||||
type_if<expression<Bits == 80>, float80_t,
|
||||
void>>>;
|
||||
|
||||
inline Real() : data(0.0) {}
|
||||
template<typename T> inline Real(const T& value) : data((type)value) {}
|
||||
|
||||
inline operator type() const { return data; }
|
||||
template<typename T> inline auto& operator=(const T& value) { data = (type)value; return *this; }
|
||||
|
||||
inline auto operator++(int) { type value = data; ++data; return value; }
|
||||
inline auto operator--(int) { type value = data; --data; return value; }
|
||||
|
||||
inline auto& operator++() { data++; return *this; }
|
||||
inline auto& operator--() { data--; return *this; }
|
||||
|
||||
inline auto& operator+=(const type value) { data = data + value; return *this; }
|
||||
inline auto& operator-=(const type value) { data = data - value; return *this; }
|
||||
inline auto& operator*=(const type value) { data = data * value; return *this; }
|
||||
inline auto& operator/=(const type value) { data = data / value; return *this; }
|
||||
inline auto& operator%=(const type value) { data = data % value; return *this; }
|
||||
|
||||
inline auto serialize(serializer& s) { s(data); }
|
||||
|
||||
type data;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using boolean = nall::Boolean;
|
||||
|
||||
using int1 = nall::Integer< 1>;
|
||||
using int2 = nall::Integer< 2>;
|
||||
using int3 = nall::Integer< 3>;
|
||||
using int4 = nall::Integer< 4>;
|
||||
using int5 = nall::Integer< 5>;
|
||||
using int6 = nall::Integer< 6>;
|
||||
using int7 = nall::Integer< 7>;
|
||||
using int8 = nall::Integer< 8>;
|
||||
using int9 = nall::Integer< 9>;
|
||||
using int10 = nall::Integer<10>;
|
||||
using int11 = nall::Integer<11>;
|
||||
using int12 = nall::Integer<12>;
|
||||
using int13 = nall::Integer<13>;
|
||||
using int14 = nall::Integer<14>;
|
||||
using int15 = nall::Integer<15>;
|
||||
using int16 = nall::Integer<16>;
|
||||
using int17 = nall::Integer<17>;
|
||||
using int18 = nall::Integer<18>;
|
||||
using int19 = nall::Integer<19>;
|
||||
using int20 = nall::Integer<20>;
|
||||
using int21 = nall::Integer<21>;
|
||||
using int22 = nall::Integer<22>;
|
||||
using int23 = nall::Integer<23>;
|
||||
using int24 = nall::Integer<24>;
|
||||
using int25 = nall::Integer<25>;
|
||||
using int26 = nall::Integer<26>;
|
||||
using int27 = nall::Integer<27>;
|
||||
using int28 = nall::Integer<28>;
|
||||
using int29 = nall::Integer<29>;
|
||||
using int30 = nall::Integer<30>;
|
||||
using int31 = nall::Integer<31>;
|
||||
using int32 = nall::Integer<32>;
|
||||
using int33 = nall::Integer<33>;
|
||||
using int34 = nall::Integer<34>;
|
||||
using int35 = nall::Integer<35>;
|
||||
using int36 = nall::Integer<36>;
|
||||
using int37 = nall::Integer<37>;
|
||||
using int38 = nall::Integer<38>;
|
||||
using int39 = nall::Integer<39>;
|
||||
using int40 = nall::Integer<40>;
|
||||
using int41 = nall::Integer<41>;
|
||||
using int42 = nall::Integer<42>;
|
||||
using int43 = nall::Integer<43>;
|
||||
using int44 = nall::Integer<44>;
|
||||
using int45 = nall::Integer<45>;
|
||||
using int46 = nall::Integer<46>;
|
||||
using int47 = nall::Integer<47>;
|
||||
using int48 = nall::Integer<48>;
|
||||
using int49 = nall::Integer<49>;
|
||||
using int50 = nall::Integer<50>;
|
||||
using int51 = nall::Integer<51>;
|
||||
using int52 = nall::Integer<52>;
|
||||
using int53 = nall::Integer<53>;
|
||||
using int54 = nall::Integer<54>;
|
||||
using int55 = nall::Integer<55>;
|
||||
using int56 = nall::Integer<56>;
|
||||
using int57 = nall::Integer<57>;
|
||||
using int58 = nall::Integer<58>;
|
||||
using int59 = nall::Integer<59>;
|
||||
using int60 = nall::Integer<60>;
|
||||
using int61 = nall::Integer<61>;
|
||||
using int62 = nall::Integer<62>;
|
||||
using int63 = nall::Integer<63>;
|
||||
using int64 = nall::Integer<64>;
|
||||
|
||||
using uint1 = nall::Natural< 1>;
|
||||
using uint2 = nall::Natural< 2>;
|
||||
using uint3 = nall::Natural< 3>;
|
||||
using uint4 = nall::Natural< 4>;
|
||||
using uint5 = nall::Natural< 5>;
|
||||
using uint6 = nall::Natural< 6>;
|
||||
using uint7 = nall::Natural< 7>;
|
||||
using uint8 = nall::Natural< 8>;
|
||||
using uint9 = nall::Natural< 9>;
|
||||
using uint10 = nall::Natural<10>;
|
||||
using uint11 = nall::Natural<11>;
|
||||
using uint12 = nall::Natural<12>;
|
||||
using uint13 = nall::Natural<13>;
|
||||
using uint14 = nall::Natural<14>;
|
||||
using uint15 = nall::Natural<15>;
|
||||
using uint16 = nall::Natural<16>;
|
||||
using uint17 = nall::Natural<17>;
|
||||
using uint18 = nall::Natural<18>;
|
||||
using uint19 = nall::Natural<19>;
|
||||
using uint20 = nall::Natural<20>;
|
||||
using uint21 = nall::Natural<21>;
|
||||
using uint22 = nall::Natural<22>;
|
||||
using uint23 = nall::Natural<23>;
|
||||
using uint24 = nall::Natural<24>;
|
||||
using uint25 = nall::Natural<25>;
|
||||
using uint26 = nall::Natural<26>;
|
||||
using uint27 = nall::Natural<27>;
|
||||
using uint28 = nall::Natural<28>;
|
||||
using uint29 = nall::Natural<29>;
|
||||
using uint30 = nall::Natural<30>;
|
||||
using uint31 = nall::Natural<31>;
|
||||
using uint32 = nall::Natural<32>;
|
||||
using uint33 = nall::Natural<33>;
|
||||
using uint34 = nall::Natural<34>;
|
||||
using uint35 = nall::Natural<35>;
|
||||
using uint36 = nall::Natural<36>;
|
||||
using uint37 = nall::Natural<37>;
|
||||
using uint38 = nall::Natural<38>;
|
||||
using uint39 = nall::Natural<39>;
|
||||
using uint40 = nall::Natural<40>;
|
||||
using uint41 = nall::Natural<41>;
|
||||
using uint42 = nall::Natural<42>;
|
||||
using uint43 = nall::Natural<43>;
|
||||
using uint44 = nall::Natural<44>;
|
||||
using uint45 = nall::Natural<45>;
|
||||
using uint46 = nall::Natural<46>;
|
||||
using uint47 = nall::Natural<47>;
|
||||
using uint48 = nall::Natural<48>;
|
||||
using uint49 = nall::Natural<49>;
|
||||
using uint50 = nall::Natural<50>;
|
||||
using uint51 = nall::Natural<51>;
|
||||
using uint52 = nall::Natural<52>;
|
||||
using uint53 = nall::Natural<53>;
|
||||
using uint54 = nall::Natural<54>;
|
||||
using uint55 = nall::Natural<55>;
|
||||
using uint56 = nall::Natural<56>;
|
||||
using uint57 = nall::Natural<57>;
|
||||
using uint58 = nall::Natural<58>;
|
||||
using uint59 = nall::Natural<59>;
|
||||
using uint60 = nall::Natural<60>;
|
||||
using uint61 = nall::Natural<61>;
|
||||
using uint62 = nall::Natural<62>;
|
||||
using uint63 = nall::Natural<63>;
|
||||
using uint64 = nall::Natural<64>;
|
||||
|
||||
using float32 = nall::Real<32>;
|
||||
using float64 = nall::Real<64>;
|
||||
using float80 = nall::Real<80>;
|
106
priority-queue.hpp
Normal file
106
priority-queue.hpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <nall/function.hpp>
|
||||
#include <nall/serializer.hpp>
|
||||
#include <nall/utility.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename type_t> auto priority_queue_nocallback(type_t) -> void {}
|
||||
|
||||
//priority queue implementation using binary min-heap array;
|
||||
//does not require normalize() function.
|
||||
//O(1) find (tick)
|
||||
//O(log n) append (enqueue)
|
||||
//O(log n) remove (dequeue)
|
||||
template<typename type_t> struct priority_queue {
|
||||
priority_queue(unsigned size, function<void (type_t)> callback = &priority_queue_nocallback<type_t>) : callback(callback) {
|
||||
heap = new heap_t[size];
|
||||
heapcapacity = size;
|
||||
reset();
|
||||
}
|
||||
|
||||
~priority_queue() {
|
||||
delete[] heap;
|
||||
}
|
||||
|
||||
priority_queue(const priority_queue&) = delete;
|
||||
auto operator=(const priority_queue&) -> priority_queue& = delete;
|
||||
|
||||
inline auto tick(unsigned ticks) -> void {
|
||||
basecounter += ticks;
|
||||
while(heapsize && gte(basecounter, heap[0].counter)) callback(dequeue());
|
||||
}
|
||||
|
||||
//counter is relative to current time (eg enqueue(64, ...) fires in 64 ticks);
|
||||
//counter cannot exceed std::numeric_limits<unsigned>::max() >> 1.
|
||||
auto enqueue(unsigned counter, type_t event) -> void {
|
||||
unsigned child = heapsize++;
|
||||
counter += basecounter;
|
||||
|
||||
while(child) {
|
||||
unsigned parent = (child - 1) >> 1;
|
||||
if(gte(counter, heap[parent].counter)) break;
|
||||
|
||||
heap[child].counter = heap[parent].counter;
|
||||
heap[child].event = heap[parent].event;
|
||||
child = parent;
|
||||
}
|
||||
|
||||
heap[child].counter = counter;
|
||||
heap[child].event = event;
|
||||
}
|
||||
|
||||
auto dequeue() -> type_t {
|
||||
type_t event(heap[0].event);
|
||||
unsigned parent = 0;
|
||||
unsigned counter = heap[--heapsize].counter;
|
||||
|
||||
while(true) {
|
||||
unsigned child = (parent << 1) + 1;
|
||||
if(child >= heapsize) break;
|
||||
if(child + 1 < heapsize && gte(heap[child].counter, heap[child + 1].counter)) child++;
|
||||
if(gte(heap[child].counter, counter)) break;
|
||||
|
||||
heap[parent].counter = heap[child].counter;
|
||||
heap[parent].event = heap[child].event;
|
||||
parent = child;
|
||||
}
|
||||
|
||||
heap[parent].counter = counter;
|
||||
heap[parent].event = heap[heapsize].event;
|
||||
return event;
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
basecounter = 0;
|
||||
heapsize = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(basecounter);
|
||||
s.integer(heapsize);
|
||||
for(unsigned n = 0; n < heapcapacity; n++) {
|
||||
s.integer(heap[n].counter);
|
||||
s.integer(heap[n].event);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
function<void (type_t)> callback;
|
||||
unsigned basecounter;
|
||||
unsigned heapsize;
|
||||
unsigned heapcapacity;
|
||||
struct heap_t {
|
||||
unsigned counter;
|
||||
type_t event;
|
||||
} *heap;
|
||||
|
||||
//return true if x is greater than or equal to y
|
||||
inline auto gte(unsigned x, unsigned y) -> bool {
|
||||
return x - y < (std::numeric_limits<unsigned>::max() >> 1);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
41
property.hpp
Normal file
41
property.hpp
Normal file
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename C> struct property {
|
||||
template<typename T> struct readonly {
|
||||
auto operator->() const -> const T* { return &value; }
|
||||
auto operator()() const -> const T& { return value; }
|
||||
operator const T&() const { return value; }
|
||||
private:
|
||||
auto operator->() -> T* { return &value; }
|
||||
operator T&() { return value; }
|
||||
auto operator=(const T& value_) -> const T& { return value = value_; }
|
||||
T value;
|
||||
friend C;
|
||||
};
|
||||
|
||||
template<typename T> struct writeonly {
|
||||
auto operator=(const T& value_) -> void { value = value_; }
|
||||
private:
|
||||
auto operator->() const -> const T* { return &value; }
|
||||
auto operator()() const -> const T& { return value; }
|
||||
operator const T&() const { return value; }
|
||||
auto operator->() -> T* { return &value; }
|
||||
operator T&() { return value; }
|
||||
T value;
|
||||
friend C;
|
||||
};
|
||||
|
||||
template<typename T> struct readwrite {
|
||||
auto operator->() const -> const T* { return &value; }
|
||||
auto operator()() const -> const T& { return value; }
|
||||
operator const T&() const { return value; }
|
||||
auto operator->() -> T* { return &value; }
|
||||
operator T&() { return value; }
|
||||
auto operator=(const T& value_) -> const T& { return value = value_; }
|
||||
T value;
|
||||
};
|
||||
};
|
||||
|
||||
}
|
40
random.hpp
Normal file
40
random.hpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/serializer.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct RandomNumberGenerator {
|
||||
virtual auto seed(uint64_t) -> void = 0;
|
||||
virtual auto operator()() -> uint64_t = 0;
|
||||
virtual auto serialize(serializer&) -> void = 0;
|
||||
};
|
||||
|
||||
//Galois LFSR using CRC64 polynomials
|
||||
struct LinearFeedbackShiftRegisterGenerator : RandomNumberGenerator {
|
||||
auto seed(uint64_t seed) -> void {
|
||||
lfsr = seed;
|
||||
for(unsigned n = 0; n < 8; n++) operator()();
|
||||
}
|
||||
|
||||
auto operator()() -> uint64_t {
|
||||
return lfsr = (lfsr >> 1) ^ (-(lfsr & 1) & crc64jones);
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(lfsr);
|
||||
}
|
||||
|
||||
private:
|
||||
static const uint64_t crc64ecma = 0x42f0e1eba9ea3693;
|
||||
static const uint64_t crc64jones = 0xad93d23594c935a9;
|
||||
uint64_t lfsr = crc64ecma;
|
||||
};
|
||||
|
||||
inline auto random() -> uint64_t {
|
||||
static LinearFeedbackShiftRegisterGenerator lfsr;
|
||||
return lfsr();
|
||||
}
|
||||
|
||||
}
|
50
range.hpp
Normal file
50
range.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct range_t {
|
||||
struct iterator {
|
||||
iterator(int position, int step = 0) : position(position), step(step) {}
|
||||
auto operator*() const -> int { return position; }
|
||||
auto operator!=(const iterator& source) const -> bool { return step > 0 ? position < source.position : position > source.position; }
|
||||
auto operator++() -> iterator& { position += step; return *this; }
|
||||
|
||||
private:
|
||||
int position;
|
||||
const int step;
|
||||
};
|
||||
|
||||
auto begin() const -> const iterator { return iterator(origin, stride); }
|
||||
auto end() const -> const iterator { return iterator(target); }
|
||||
|
||||
int origin;
|
||||
int target;
|
||||
int stride;
|
||||
};
|
||||
|
||||
inline auto range(int size) {
|
||||
return range_t{0, size, 1};
|
||||
}
|
||||
|
||||
inline auto range(int offset, int size) {
|
||||
return range_t{offset, size, 1};
|
||||
}
|
||||
|
||||
inline auto range(int offset, int size, int step) {
|
||||
return range_t{offset, size, step};
|
||||
}
|
||||
|
||||
//reverse-range
|
||||
inline auto rrange(int size) {
|
||||
return range_t{size - 1, -1, -1};
|
||||
}
|
||||
|
||||
template<typename T> inline auto range(const vector<T>& container) {
|
||||
return range_t{0, (int)container.size(), 1};
|
||||
}
|
||||
|
||||
template<typename T> inline auto rrange(const vector<T>& container) {
|
||||
return range_t{(int)container.size() - 1, -1, -1};
|
||||
}
|
||||
|
||||
}
|
157
run.hpp
Normal file
157
run.hpp
Normal file
|
@ -0,0 +1,157 @@
|
|||
#pragma once
|
||||
|
||||
//auto execute(const string& name, const string& args...) -> string;
|
||||
//[[synchronous]]
|
||||
//executes program, waits for completion, and returns data written to stdout
|
||||
|
||||
//auto invoke(const string& name, const string& args...) -> void;
|
||||
//[[asynchronous]]
|
||||
//if a program is specified, it is executed with the arguments provided
|
||||
//if a file is specified, the file is opened using the program associated with said file type
|
||||
//if a folder is specified, the folder is opened using the associated file explorer
|
||||
//if a URL is specified, the default web browser is opened and pointed at the URL requested
|
||||
|
||||
#include <nall/intrinsics.hpp>
|
||||
#include <nall/string.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
#if defined(PLATFORM_MACOSX) || defined(PLATFORM_LINUX) || defined(PLATFORM_BSD)
|
||||
|
||||
template<typename... P> inline auto execute(const string& name, P&&... p) -> string {
|
||||
int fd[2];
|
||||
if(pipe(fd) == -1) return "";
|
||||
|
||||
pid_t pid = fork();
|
||||
if(pid == 0) {
|
||||
const char* argv[1 + sizeof...(p) + 1];
|
||||
const char** argp = argv;
|
||||
lstring argl(forward<P>(p)...);
|
||||
*argp++ = (const char*)name;
|
||||
for(auto& arg : argl) *argp++ = (const char*)arg;
|
||||
*argp++ = nullptr;
|
||||
|
||||
dup2(fd[1], STDOUT_FILENO);
|
||||
dup2(fd[1], STDERR_FILENO);
|
||||
close(fd[0]);
|
||||
close(fd[1]);
|
||||
execvp(name, (char* const*)argv);
|
||||
exit(0);
|
||||
} else {
|
||||
close(fd[1]);
|
||||
|
||||
string result;
|
||||
while(true) {
|
||||
char buffer[256];
|
||||
auto size = read(fd[0], buffer, sizeof(buffer));
|
||||
if(size <= 0) break;
|
||||
|
||||
auto offset = result.size();
|
||||
result.resize(offset + size);
|
||||
memory::copy(result.get() + offset, buffer, size);
|
||||
}
|
||||
|
||||
close(fd[0]);
|
||||
wait(nullptr);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... P> inline auto invoke(const string& name, P&&... p) -> void {
|
||||
pid_t pid = fork();
|
||||
if(pid == 0) {
|
||||
const char* argv[1 + sizeof...(p) + 1];
|
||||
const char** argp = argv;
|
||||
lstring argl(forward<P>(p)...);
|
||||
*argp++ = (const char*)name;
|
||||
for(auto& arg : argl) *argp++ = (const char*)arg;
|
||||
*argp++ = nullptr;
|
||||
|
||||
if(execvp(name, (char* const*)argv) < 0) {
|
||||
execlp("xdg-open", "xdg-open", (const char*)name, nullptr);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(PLATFORM_WINDOWS)
|
||||
|
||||
template<typename... P> inline auto execute(const string& name, P&&... p) -> string {
|
||||
lstring argl(name, forward<P>(p)...);
|
||||
for(auto& arg : argl) if(arg.find(" ")) arg = {"\"", arg, "\""};
|
||||
string arguments = argl.merge(" ");
|
||||
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
|
||||
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
sa.bInheritHandle = true;
|
||||
sa.lpSecurityDescriptor = nullptr;
|
||||
|
||||
HANDLE stdoutRead;
|
||||
HANDLE stdoutWrite;
|
||||
if(!CreatePipe(&stdoutRead, &stdoutWrite, &sa, 0)) return "";
|
||||
if(!SetHandleInformation(stdoutRead, HANDLE_FLAG_INHERIT, 0)) return "";
|
||||
|
||||
HANDLE stdinRead;
|
||||
HANDLE stdinWrite;
|
||||
if(!CreatePipe(&stdinRead, &stdinWrite, &sa, 0)) return "";
|
||||
if(!SetHandleInformation(stdinWrite, HANDLE_FLAG_INHERIT, 0)) return "";
|
||||
|
||||
STARTUPINFO si;
|
||||
ZeroMemory(&si, sizeof(STARTUPINFO));
|
||||
si.cb = sizeof(STARTUPINFO);
|
||||
si.hStdError = stdoutWrite;
|
||||
si.hStdOutput = stdoutWrite;
|
||||
si.hStdInput = stdinRead;
|
||||
si.dwFlags = STARTF_USESTDHANDLES;
|
||||
|
||||
PROCESS_INFORMATION pi;
|
||||
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
|
||||
|
||||
if(!CreateProcess(
|
||||
nullptr, utf16_t(arguments),
|
||||
nullptr, nullptr, true, CREATE_NO_WINDOW,
|
||||
nullptr, nullptr, &si, &pi
|
||||
)) return "";
|
||||
|
||||
if(WaitForSingleObject(pi.hProcess, INFINITE)) return "";
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
|
||||
string result;
|
||||
while(true) {
|
||||
char buffer[256];
|
||||
DWORD read, available, remaining;
|
||||
if(!PeekNamedPipe(stdoutRead, nullptr, sizeof(buffer), &read, &available, &remaining)) break;
|
||||
if(read == 0) break;
|
||||
|
||||
if(!ReadFile(stdoutRead, buffer, sizeof(buffer), &read, nullptr)) break;
|
||||
if(read == 0) break;
|
||||
|
||||
auto offset = result.size();
|
||||
result.resize(offset + read);
|
||||
memory::copy(result.get() + offset, buffer, read);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename... P> inline auto invoke(const string& name, P&&... p) -> void {
|
||||
lstring argl(forward<P>(p)...);
|
||||
for(auto& arg : argl) if(arg.find(" ")) arg = {"\"", arg, "\""};
|
||||
string arguments = argl.merge(" ");
|
||||
ShellExecute(nullptr, nullptr, utf16_t(name), utf16_t(arguments), nullptr, SW_SHOWNORMAL);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
template<typename... P> inline auto execute(const string& name, P&&... p) -> string {
|
||||
return "";
|
||||
}
|
||||
|
||||
template<typename... P> inline auto invoke(const string& name, P&&... p) -> void {
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
115
serial.hpp
Normal file
115
serial.hpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/intrinsics.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/string.hpp>
|
||||
|
||||
#if !defined(API_POSIX)
|
||||
#error "nall/serial: unsupported system"
|
||||
#endif
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct serial {
|
||||
serial() {
|
||||
port = -1;
|
||||
port_open = false;
|
||||
}
|
||||
|
||||
~serial() {
|
||||
close();
|
||||
}
|
||||
|
||||
auto readable() -> bool {
|
||||
if(port_open == false) return false;
|
||||
fd_set fdset;
|
||||
FD_ZERO(&fdset);
|
||||
FD_SET(port, &fdset);
|
||||
timeval timeout;
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 0;
|
||||
int result = select(FD_SETSIZE, &fdset, nullptr, nullptr, &timeout);
|
||||
if(result < 1) return false;
|
||||
return FD_ISSET(port, &fdset);
|
||||
}
|
||||
|
||||
//-1 on error, otherwise return bytes read
|
||||
auto read(uint8_t* data, uint length) -> int {
|
||||
if(port_open == false) return -1;
|
||||
return ::read(port, (void*)data, length);
|
||||
}
|
||||
|
||||
auto writable() -> bool {
|
||||
if(port_open == false) return false;
|
||||
fd_set fdset;
|
||||
FD_ZERO(&fdset);
|
||||
FD_SET(port, &fdset);
|
||||
timeval timeout;
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 0;
|
||||
int result = select(FD_SETSIZE, nullptr, &fdset, nullptr, &timeout);
|
||||
if(result < 1) return false;
|
||||
return FD_ISSET(port, &fdset);
|
||||
}
|
||||
|
||||
//-1 on error, otherwise return bytes written
|
||||
auto write(const uint8_t* data, uint length) -> int {
|
||||
if(port_open == false) return -1;
|
||||
return ::write(port, (void*)data, length);
|
||||
}
|
||||
|
||||
auto open(const string& portname, uint rate, bool flowcontrol) -> bool {
|
||||
close();
|
||||
|
||||
port = ::open(portname, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
|
||||
if(port == -1) return false;
|
||||
|
||||
if(ioctl(port, TIOCEXCL) == -1) { close(); return false; }
|
||||
if(fcntl(port, F_SETFL, 0) == -1) { close(); return false; }
|
||||
if(tcgetattr(port, &original_attr) == -1) { close(); return false; }
|
||||
|
||||
termios attr = original_attr;
|
||||
cfmakeraw(&attr);
|
||||
cfsetspeed(&attr, rate);
|
||||
|
||||
attr.c_lflag &=~ (ECHO | ECHONL | ISIG | ICANON | IEXTEN);
|
||||
attr.c_iflag &=~ (BRKINT | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY);
|
||||
attr.c_iflag |= (IGNBRK | IGNPAR);
|
||||
attr.c_oflag &=~ (OPOST);
|
||||
attr.c_cflag &=~ (CSIZE | CSTOPB | PARENB | CLOCAL);
|
||||
attr.c_cflag |= (CS8 | CREAD);
|
||||
if(flowcontrol == false) {
|
||||
attr.c_cflag &= ~CRTSCTS;
|
||||
} else {
|
||||
attr.c_cflag |= CRTSCTS;
|
||||
}
|
||||
attr.c_cc[VTIME] = attr.c_cc[VMIN] = 0;
|
||||
|
||||
if(tcsetattr(port, TCSANOW, &attr) == -1) { close(); return false; }
|
||||
return port_open = true;
|
||||
}
|
||||
|
||||
auto close() -> void {
|
||||
if(port != -1) {
|
||||
tcdrain(port);
|
||||
if(port_open == true) {
|
||||
tcsetattr(port, TCSANOW, &original_attr);
|
||||
port_open = false;
|
||||
}
|
||||
::close(port);
|
||||
port = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int port;
|
||||
bool port_open;
|
||||
termios original_attr;
|
||||
};
|
||||
|
||||
}
|
146
serializer.hpp
Normal file
146
serializer.hpp
Normal file
|
@ -0,0 +1,146 @@
|
|||
#pragma once
|
||||
|
||||
//serializer: a class designed to save and restore the state of classes.
|
||||
//
|
||||
//benefits:
|
||||
//- data() will be portable in size (it is not necessary to specify type sizes.)
|
||||
//- data() will be portable in endianness (always stored internally as little-endian.)
|
||||
//- one serialize function can both save and restore class states.
|
||||
//
|
||||
//caveats:
|
||||
//- only plain-old-data can be stored. complex classes must provide serialize(serializer&);
|
||||
//- floating-point usage is not portable across different implementations
|
||||
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/traits.hpp>
|
||||
#include <nall/utility.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct serializer;
|
||||
|
||||
template<typename T>
|
||||
struct has_serialize {
|
||||
template<typename C> static auto test(decltype(std::declval<C>().serialize(std::declval<serializer&>()))*) -> char;
|
||||
template<typename C> static auto test(...) -> long;
|
||||
static const bool value = sizeof(test<T>(0)) == sizeof(char);
|
||||
};
|
||||
|
||||
struct serializer {
|
||||
enum Mode : uint { Load, Save, Size };
|
||||
|
||||
auto mode() const -> Mode {
|
||||
return _mode;
|
||||
}
|
||||
|
||||
auto data() const -> const uint8_t* {
|
||||
return _data;
|
||||
}
|
||||
|
||||
auto size() const -> uint {
|
||||
return _size;
|
||||
}
|
||||
|
||||
auto capacity() const -> uint {
|
||||
return _capacity;
|
||||
}
|
||||
|
||||
template<typename T> auto floatingpoint(T& value) -> serializer& {
|
||||
enum { size = sizeof(T) };
|
||||
//this is rather dangerous, and not cross-platform safe;
|
||||
//but there is no standardized way to export FP-values
|
||||
auto p = (uint8_t*)&value;
|
||||
if(_mode == Save) {
|
||||
for(uint n = 0; n < size; n++) _data[_size++] = p[n];
|
||||
} else if(_mode == Load) {
|
||||
for(uint n = 0; n < size; n++) p[n] = _data[_size++];
|
||||
} else {
|
||||
_size += size;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T> auto integer(T& value) -> serializer& {
|
||||
enum { size = std::is_same<bool, T>::value ? 1 : sizeof(T) };
|
||||
if(_mode == Save) {
|
||||
for(uint n = 0; n < size; n++) _data[_size++] = (uintmax)value >> (n << 3);
|
||||
} else if(_mode == Load) {
|
||||
value = 0;
|
||||
for(uint n = 0; n < size; n++) value |= (uintmax)_data[_size++] << (n << 3);
|
||||
} else if(_mode == Size) {
|
||||
_size += size;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, int N> auto array(T (&array)[N]) -> serializer& {
|
||||
for(uint n = 0; n < N; n++) operator()(array[n]);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T> auto array(T array, uint size) -> serializer& {
|
||||
for(uint n = 0; n < size; n++) operator()(array[n]);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T> auto operator()(T& value, typename std::enable_if<has_serialize<T>::value>::type* = 0) -> serializer& { value.serialize(*this); return *this; }
|
||||
template<typename T> auto operator()(T& value, typename std::enable_if<std::is_integral<T>::value>::type* = 0) -> serializer& { return integer(value); }
|
||||
template<typename T> auto operator()(T& value, typename std::enable_if<std::is_floating_point<T>::value>::type* = 0) -> serializer& { return floatingpoint(value); }
|
||||
template<typename T> auto operator()(T& value, typename std::enable_if<std::is_array<T>::value>::type* = 0) -> serializer& { return array(value); }
|
||||
template<typename T> auto operator()(T& value, uint size, typename std::enable_if<std::is_pointer<T>::value>::type* = 0) -> serializer& { return array(value, size); }
|
||||
|
||||
auto operator=(const serializer& s) -> serializer& {
|
||||
if(_data) delete[] _data;
|
||||
|
||||
_mode = s._mode;
|
||||
_data = new uint8_t[s._capacity];
|
||||
_size = s._size;
|
||||
_capacity = s._capacity;
|
||||
|
||||
memcpy(_data, s._data, s._capacity);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator=(serializer&& s) -> serializer& {
|
||||
if(_data) delete[] _data;
|
||||
|
||||
_mode = s._mode;
|
||||
_data = s._data;
|
||||
_size = s._size;
|
||||
_capacity = s._capacity;
|
||||
|
||||
s._data = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
serializer() = default;
|
||||
serializer(const serializer& s) { operator=(s); }
|
||||
serializer(serializer&& s) { operator=(move(s)); }
|
||||
|
||||
serializer(uint capacity) {
|
||||
_mode = Save;
|
||||
_data = new uint8_t[capacity]();
|
||||
_size = 0;
|
||||
_capacity = capacity;
|
||||
}
|
||||
|
||||
serializer(const uint8_t* data, uint capacity) {
|
||||
_mode = Load;
|
||||
_data = new uint8_t[capacity];
|
||||
_size = 0;
|
||||
_capacity = capacity;
|
||||
memcpy(_data, data, capacity);
|
||||
}
|
||||
|
||||
~serializer() {
|
||||
if(_data) delete[] _data;
|
||||
}
|
||||
|
||||
private:
|
||||
Mode _mode = Size;
|
||||
uint8_t* _data = nullptr;
|
||||
uint _size = 0;
|
||||
uint _capacity = 0;
|
||||
};
|
||||
|
||||
};
|
13
service.hpp
Normal file
13
service.hpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
//service model template built on top of shared-memory
|
||||
|
||||
#include <nall/shared-memory.hpp>
|
||||
|
||||
#if defined(API_POSIX)
|
||||
#include <nall/posix/service.hpp>
|
||||
#endif
|
||||
|
||||
#if defined(API_WINDOWS)
|
||||
#include <nall/windows/service.hpp>
|
||||
#endif
|
264
set.hpp
Normal file
264
set.hpp
Normal file
|
@ -0,0 +1,264 @@
|
|||
#pragma once
|
||||
|
||||
//set
|
||||
//implementation: red-black tree
|
||||
//
|
||||
//search: O(log n) average; O(log n) worst
|
||||
//insert: O(log n) average; O(log n) worst
|
||||
//remove: O(log n) average; O(log n) worst
|
||||
//
|
||||
//requirements:
|
||||
// bool T::operator==(const T&) const;
|
||||
// bool T::operator< (const T&) const;
|
||||
|
||||
#include <nall/utility.hpp>
|
||||
#include <nall/vector.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename T> struct set {
|
||||
struct node_t {
|
||||
T value;
|
||||
bool red = 1;
|
||||
node_t* link[2] = {nullptr, nullptr};
|
||||
node_t() = default;
|
||||
node_t(const T& value) : value(value) {}
|
||||
};
|
||||
|
||||
node_t* root = nullptr;
|
||||
unsigned nodes = 0;
|
||||
|
||||
set() = default;
|
||||
set(const set& source) { operator=(source); }
|
||||
set(set&& source) { operator=(move(source)); }
|
||||
set(std::initializer_list<T> list) { for(auto& value : list) insert(value); }
|
||||
~set() { reset(); }
|
||||
|
||||
auto operator=(const set& source) -> set& {
|
||||
reset();
|
||||
copy(root, source.root);
|
||||
nodes = source.nodes;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator=(set&& source) -> set& {
|
||||
root = source.root;
|
||||
nodes = source.nodes;
|
||||
source.root = nullptr;
|
||||
source.nodes = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto size() const -> unsigned { return nodes; }
|
||||
auto empty() const -> bool { return nodes == 0; }
|
||||
|
||||
auto reset() -> void {
|
||||
reset(root);
|
||||
nodes = 0;
|
||||
}
|
||||
|
||||
auto find(const T& value) -> maybe<T&> {
|
||||
if(node_t* node = find(root, value)) return node->value;
|
||||
return nothing;
|
||||
}
|
||||
|
||||
auto find(const T& value) const -> maybe<const T&> {
|
||||
if(node_t* node = find(root, value)) return node->value;
|
||||
return nothing;
|
||||
}
|
||||
|
||||
auto insert(const T& value) -> maybe<T&> {
|
||||
unsigned count = size();
|
||||
node_t* v = insert(root, value);
|
||||
root->red = 0;
|
||||
if(size() == count) return nothing;
|
||||
return v->value;
|
||||
}
|
||||
|
||||
template<typename... P> auto insert(const T& value, P&&... p) -> bool {
|
||||
bool result = insert(value);
|
||||
insert(forward<P>(p)...) | result;
|
||||
return result;
|
||||
}
|
||||
|
||||
auto remove(const T& value) -> bool {
|
||||
unsigned count = size();
|
||||
bool done = 0;
|
||||
remove(root, &value, done);
|
||||
if(root) root->red = 0;
|
||||
return size() < count;
|
||||
}
|
||||
|
||||
template<typename... P> auto remove(const T& value, P&&... p) -> bool {
|
||||
bool result = remove(value);
|
||||
return remove(forward<P>(p)...) | result;
|
||||
}
|
||||
|
||||
struct base_iterator {
|
||||
auto operator!=(const base_iterator& source) const -> bool { return position != source.position; }
|
||||
|
||||
auto operator++() -> base_iterator& {
|
||||
if(++position >= source.size()) { position = source.size(); return *this; }
|
||||
|
||||
if(stack.last()->link[1]) {
|
||||
stack.append(stack.last()->link[1]);
|
||||
while(stack.last()->link[0]) stack.append(stack.last()->link[0]);
|
||||
} else {
|
||||
node_t* child;
|
||||
do child = stack.take();
|
||||
while(child == stack.last()->link[1]);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
base_iterator(const set& source, unsigned position) : source(source), position(position) {
|
||||
node_t* node = source.root;
|
||||
while(node) {
|
||||
stack.append(node);
|
||||
node = node->link[0];
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
const set& source;
|
||||
unsigned position;
|
||||
vector<node_t*> stack;
|
||||
};
|
||||
|
||||
struct iterator : base_iterator {
|
||||
iterator(const set& source, unsigned position) : base_iterator(source, position) {}
|
||||
auto operator*() const -> T& { return base_iterator::stack.last()->value; }
|
||||
};
|
||||
|
||||
auto begin() -> iterator { return iterator(*this, 0); }
|
||||
auto end() -> iterator { return iterator(*this, size()); }
|
||||
|
||||
struct const_iterator : base_iterator {
|
||||
const_iterator(const set& source, unsigned position) : base_iterator(source, position) {}
|
||||
auto operator*() const -> const T& { return base_iterator::stack.last()->value; }
|
||||
};
|
||||
|
||||
auto begin() const -> const const_iterator { return const_iterator(*this, 0); }
|
||||
auto end() const -> const const_iterator { return const_iterator(*this, size()); }
|
||||
|
||||
private:
|
||||
auto reset(node_t*& node) -> void {
|
||||
if(!node) return;
|
||||
if(node->link[0]) reset(node->link[0]);
|
||||
if(node->link[1]) reset(node->link[1]);
|
||||
delete node;
|
||||
node = nullptr;
|
||||
}
|
||||
|
||||
auto copy(node_t*& target, const node_t* source) -> void {
|
||||
if(!source) return;
|
||||
target = new node_t(source->value);
|
||||
target->red = source->red;
|
||||
copy(target->link[0], source->link[0]);
|
||||
copy(target->link[1], source->link[1]);
|
||||
}
|
||||
|
||||
auto find(node_t* node, const T& value) const -> node_t* {
|
||||
if(node == nullptr) return nullptr;
|
||||
if(node->value == value) return node;
|
||||
return find(node->link[node->value < value], value);
|
||||
}
|
||||
|
||||
auto red(node_t* node) const -> bool { return node && node->red; }
|
||||
auto black(node_t* node) const -> bool { return !red(node); }
|
||||
|
||||
auto rotate(node_t*& a, bool dir) -> void {
|
||||
node_t*& b = a->link[!dir];
|
||||
node_t*& c = b->link[dir];
|
||||
a->red = 1, b->red = 0;
|
||||
std::swap(a, b);
|
||||
std::swap(b, c);
|
||||
}
|
||||
|
||||
auto rotateTwice(node_t*& node, bool dir) -> void {
|
||||
rotate(node->link[!dir], !dir);
|
||||
rotate(node, dir);
|
||||
}
|
||||
|
||||
auto insert(node_t*& node, const T& value) -> node_t* {
|
||||
if(!node) { nodes++; node = new node_t(value); return node; }
|
||||
if(node->value == value) { node->value = value; return node; } //prevent duplicate entries
|
||||
|
||||
bool dir = node->value < value;
|
||||
node_t* v = insert(node->link[dir], value);
|
||||
if(black(node->link[dir])) return v;
|
||||
|
||||
if(red(node->link[!dir])) {
|
||||
node->red = 1;
|
||||
node->link[0]->red = 0;
|
||||
node->link[1]->red = 0;
|
||||
} else if(red(node->link[dir]->link[dir])) {
|
||||
rotate(node, !dir);
|
||||
} else if(red(node->link[dir]->link[!dir])) {
|
||||
rotateTwice(node, !dir);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
auto balance(node_t*& node, bool dir, bool& done) -> void {
|
||||
node_t* p = node;
|
||||
node_t* s = node->link[!dir];
|
||||
if(!s) return;
|
||||
|
||||
if(red(s)) {
|
||||
rotate(node, dir);
|
||||
s = p->link[!dir];
|
||||
}
|
||||
|
||||
if(black(s->link[0]) && black(s->link[1])) {
|
||||
if(red(p)) done = 1;
|
||||
p->red = 0, s->red = 1;
|
||||
} else {
|
||||
bool save = p->red;
|
||||
bool head = node == p;
|
||||
|
||||
if(red(s->link[!dir])) rotate(p, dir);
|
||||
else rotateTwice(p, dir);
|
||||
|
||||
p->red = save;
|
||||
p->link[0]->red = 0;
|
||||
p->link[1]->red = 0;
|
||||
|
||||
if(head) node = p;
|
||||
else node->link[dir] = p;
|
||||
|
||||
done = 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto remove(node_t*& node, const T* value, bool& done) -> void {
|
||||
if(!node) { done = 1; return; }
|
||||
|
||||
if(node->value == *value) {
|
||||
if(!node->link[0] || !node->link[1]) {
|
||||
node_t* save = node->link[!node->link[0]];
|
||||
|
||||
if(red(node)) done = 1;
|
||||
else if(red(save)) save->red = 0, done = 1;
|
||||
|
||||
nodes--;
|
||||
delete node;
|
||||
node = save;
|
||||
return;
|
||||
} else {
|
||||
node_t* heir = node->link[0];
|
||||
while(heir->link[1]) heir = heir->link[1];
|
||||
node->value = heir->value;
|
||||
value = &heir->value;
|
||||
}
|
||||
}
|
||||
|
||||
bool dir = node->value < *value;
|
||||
remove(node->link[dir], value, done);
|
||||
if(!done) balance(node, dir, done);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
12
shared-memory.hpp
Normal file
12
shared-memory.hpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/memory.hpp>
|
||||
#include <nall/string.hpp>
|
||||
|
||||
#if defined(API_POSIX)
|
||||
#include <nall/posix/shared-memory.hpp>
|
||||
#endif
|
||||
|
||||
#if defined(API_WINDOWS)
|
||||
#include <nall/windows/shared-memory.hpp>
|
||||
#endif
|
273
shared-pointer.hpp
Normal file
273
shared-pointer.hpp
Normal file
|
@ -0,0 +1,273 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/function.hpp>
|
||||
#include <nall/maybe.hpp>
|
||||
#include <nall/traits.hpp>
|
||||
#include <nall/vector.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename T> struct shared_pointer;
|
||||
|
||||
struct shared_pointer_manager {
|
||||
void* pointer = nullptr;
|
||||
function<auto (void*) -> void> deleter;
|
||||
uint strong = 0;
|
||||
uint weak = 0;
|
||||
|
||||
shared_pointer_manager(void* pointer) : pointer(pointer) {
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> struct shared_pointer;
|
||||
template<typename T> struct shared_pointer_weak;
|
||||
|
||||
template<typename T>
|
||||
struct shared_pointer {
|
||||
using type = T;
|
||||
shared_pointer_manager* manager = nullptr;
|
||||
|
||||
template<typename U>
|
||||
struct is_compatible {
|
||||
static constexpr bool value = is_base_of<T, U>::value || is_base_of<U, T>::value;
|
||||
};
|
||||
|
||||
shared_pointer() {
|
||||
}
|
||||
|
||||
shared_pointer(T* source) {
|
||||
operator=(source);
|
||||
}
|
||||
|
||||
shared_pointer(T* source, const function<void (T*)>& deleter) {
|
||||
operator=(source);
|
||||
manager->deleter = [=](void* p) { deleter((T*)p); };
|
||||
}
|
||||
|
||||
shared_pointer(const shared_pointer& source) {
|
||||
operator=(source);
|
||||
}
|
||||
|
||||
shared_pointer(shared_pointer&& source) {
|
||||
operator=(move(source));
|
||||
}
|
||||
|
||||
template<typename U, typename = enable_if<is_compatible<U>>>
|
||||
shared_pointer(const shared_pointer<U>& source) {
|
||||
operator=<U>(source);
|
||||
}
|
||||
|
||||
template<typename U, typename = enable_if<is_compatible<U>>>
|
||||
shared_pointer(shared_pointer<U>&& source) {
|
||||
operator=<U>(move(source));
|
||||
}
|
||||
|
||||
template<typename U, typename = enable_if<is_compatible<U>>>
|
||||
shared_pointer(const shared_pointer_weak<U>& source) {
|
||||
operator=<U>(source);
|
||||
}
|
||||
|
||||
template<typename U, typename = enable_if<is_compatible<U>>>
|
||||
shared_pointer(const shared_pointer<U>& source, T* pointer) {
|
||||
if((bool)source && (T*)source.manager->pointer == pointer) {
|
||||
manager = source.manager;
|
||||
manager->strong++;
|
||||
}
|
||||
}
|
||||
|
||||
~shared_pointer() {
|
||||
reset();
|
||||
}
|
||||
|
||||
auto operator=(T* source) -> shared_pointer& {
|
||||
reset();
|
||||
if(source) {
|
||||
manager = new shared_pointer_manager((void*)source);
|
||||
manager->strong++;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator=(const shared_pointer& source) -> shared_pointer& {
|
||||
if(this != &source) {
|
||||
reset();
|
||||
if((bool)source) {
|
||||
manager = source.manager;
|
||||
manager->strong++;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator=(shared_pointer&& source) -> shared_pointer& {
|
||||
if(this != &source) {
|
||||
reset();
|
||||
manager = source.manager;
|
||||
source.manager = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename U, typename = enable_if<is_compatible<U>>>
|
||||
auto operator=(const shared_pointer<U>& source) -> shared_pointer& {
|
||||
if((uintptr_t)this != (uintptr_t)&source) {
|
||||
reset();
|
||||
if((bool)source) {
|
||||
manager = source.manager;
|
||||
manager->strong++;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename U, typename = enable_if<is_compatible<U>>>
|
||||
auto operator=(shared_pointer&& source) -> shared_pointer& {
|
||||
if((uintptr_t)this != (uintptr_t)&source) {
|
||||
reset();
|
||||
manager = source.manager;
|
||||
source.manager = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename U, typename = enable_if<is_compatible<U>>>
|
||||
auto operator=(const shared_pointer_weak<U>& source) -> shared_pointer& {
|
||||
reset();
|
||||
if((bool)source) {
|
||||
manager = source.manager;
|
||||
manager->strong++;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto data() -> T* {
|
||||
if(manager) return (T*)manager->pointer;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto data() const -> const T* {
|
||||
if(manager) return (T*)manager->pointer;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto operator->() -> T* { return data(); }
|
||||
auto operator->() const -> const T* { return data(); }
|
||||
|
||||
auto operator*() -> T& { return *data(); }
|
||||
auto operator*() const -> const T& { return *data(); }
|
||||
|
||||
auto operator()() -> T& { return *data(); }
|
||||
auto operator()() const -> const T& { return *data(); }
|
||||
|
||||
template<typename U>
|
||||
auto operator==(const shared_pointer<U>& source) const -> bool {
|
||||
return manager == source.manager;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
auto operator!=(const shared_pointer<U>& source) const -> bool {
|
||||
return manager != source.manager;
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return !empty();
|
||||
}
|
||||
|
||||
auto empty() const -> bool {
|
||||
return !manager || !manager->strong;
|
||||
}
|
||||
|
||||
auto unique() const -> bool {
|
||||
return manager && manager->strong == 1;
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
if(manager && manager->strong) {
|
||||
//pointer may contain weak references; if strong==0 it may destroy manager
|
||||
//as such, we must destroy strong before decrementing it to zero
|
||||
if(manager->strong == 1) {
|
||||
if(manager->deleter) {
|
||||
manager->deleter(manager->pointer);
|
||||
} else {
|
||||
delete (T*)manager->pointer;
|
||||
}
|
||||
manager->pointer = nullptr;
|
||||
}
|
||||
if(--manager->strong == 0) {
|
||||
if(manager->weak == 0) {
|
||||
delete manager;
|
||||
}
|
||||
}
|
||||
}
|
||||
manager = nullptr;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
auto cast() -> shared_pointer<U> {
|
||||
if(auto pointer = dynamic_cast<U*>(data())) {
|
||||
return {*this, pointer};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct shared_pointer_weak {
|
||||
using type = T;
|
||||
shared_pointer_manager* manager = nullptr;
|
||||
|
||||
shared_pointer_weak() {
|
||||
}
|
||||
|
||||
shared_pointer_weak(const shared_pointer<T>& source) {
|
||||
operator=(source);
|
||||
}
|
||||
|
||||
auto operator=(const shared_pointer<T>& source) -> shared_pointer_weak& {
|
||||
reset();
|
||||
if(manager = source.manager) manager->weak++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~shared_pointer_weak() {
|
||||
reset();
|
||||
}
|
||||
|
||||
auto operator==(const shared_pointer_weak& source) const -> bool {
|
||||
return manager == source.manager;
|
||||
}
|
||||
|
||||
auto operator!=(const shared_pointer_weak& source) const -> bool {
|
||||
return manager != source.manager;
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return !empty();
|
||||
}
|
||||
|
||||
auto empty() const -> bool {
|
||||
return !manager || !manager->strong;
|
||||
}
|
||||
|
||||
auto acquire() const -> shared_pointer<T> {
|
||||
return shared_pointer<T>(*this);
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
if(manager && --manager->weak == 0) {
|
||||
if(manager->strong == 0) {
|
||||
delete manager;
|
||||
}
|
||||
}
|
||||
manager = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct shared_pointer_new : shared_pointer<T> {
|
||||
template<typename... P>
|
||||
shared_pointer_new(P&&... p) : shared_pointer<T>(new T(forward<P>(p)...)) {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
315
smtp.hpp
Normal file
315
smtp.hpp
Normal file
|
@ -0,0 +1,315 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/base64.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/string.hpp>
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#else
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct SMTP {
|
||||
enum class Format : unsigned { Plain, HTML };
|
||||
|
||||
inline auto server(string server, uint16_t port = 25) -> void;
|
||||
inline auto from(string mail, string name = "") -> void;
|
||||
inline auto to(string mail, string name = "") -> void;
|
||||
inline auto cc(string mail, string name = "") -> void;
|
||||
inline auto bcc(string mail, string name = "") -> void;
|
||||
inline auto attachment(const uint8_t* data, unsigned size, string name) -> void;
|
||||
inline auto attachment(string filename, string name = "") -> bool;
|
||||
inline auto subject(string subject) -> void;
|
||||
inline auto body(string body, Format format = Format::Plain) -> void;
|
||||
|
||||
inline auto send() -> bool;
|
||||
inline auto message() -> string;
|
||||
inline auto response() -> string;
|
||||
|
||||
#if defined(API_WINDOWS)
|
||||
inline auto close(int) -> int;
|
||||
inline SMTP();
|
||||
#endif
|
||||
|
||||
private:
|
||||
struct Information {
|
||||
string server;
|
||||
uint16_t port;
|
||||
struct Contact {
|
||||
string mail;
|
||||
string name;
|
||||
};
|
||||
Contact from;
|
||||
vector<Contact> to;
|
||||
vector<Contact> cc;
|
||||
vector<Contact> bcc;
|
||||
struct Attachment {
|
||||
vector<uint8_t> buffer;
|
||||
string name;
|
||||
};
|
||||
string subject;
|
||||
string body;
|
||||
Format format = Format::Plain;
|
||||
vector<Attachment> attachments;
|
||||
|
||||
string message;
|
||||
string response;
|
||||
} info;
|
||||
|
||||
inline auto send(int sock, const string& text) -> bool;
|
||||
inline auto recv(int sock) -> string;
|
||||
inline auto boundary() -> string;
|
||||
inline auto filename(const string& filename) -> string;
|
||||
inline auto contact(const Information::Contact& contact) -> string;
|
||||
inline auto contacts(const vector<Information::Contact>& contacts) -> string;
|
||||
inline auto split(const string& text) -> string;
|
||||
};
|
||||
|
||||
auto SMTP::server(string server, uint16_t port) -> void {
|
||||
info.server = server;
|
||||
info.port = port;
|
||||
}
|
||||
|
||||
auto SMTP::from(string mail, string name) -> void {
|
||||
info.from = {mail, name};
|
||||
}
|
||||
|
||||
auto SMTP::to(string mail, string name) -> void {
|
||||
info.to.append({mail, name});
|
||||
}
|
||||
|
||||
auto SMTP::cc(string mail, string name) -> void {
|
||||
info.cc.append({mail, name});
|
||||
}
|
||||
|
||||
auto SMTP::bcc(string mail, string name) -> void {
|
||||
info.bcc.append({mail, name});
|
||||
}
|
||||
|
||||
auto SMTP::attachment(const uint8_t* data, unsigned size, string name) -> void {
|
||||
vector<uint8_t> buffer;
|
||||
buffer.resize(size);
|
||||
memcpy(buffer.data(), data, size);
|
||||
info.attachments.append({std::move(buffer), name});
|
||||
}
|
||||
|
||||
auto SMTP::attachment(string filename, string name) -> bool {
|
||||
if(!file::exists(filename)) return false;
|
||||
if(name == "") name = notdir(filename);
|
||||
auto buffer = file::read(filename);
|
||||
info.attachments.append({std::move(buffer), name});
|
||||
return true;
|
||||
}
|
||||
|
||||
auto SMTP::subject(string subject) -> void {
|
||||
info.subject = subject;
|
||||
}
|
||||
|
||||
auto SMTP::body(string body, Format format) -> void {
|
||||
info.body = body;
|
||||
info.format = format;
|
||||
}
|
||||
|
||||
auto SMTP::send() -> bool {
|
||||
info.message.append("From: =?UTF-8?B?", Base64::encode(contact(info.from)), "?=\r\n");
|
||||
info.message.append("To: =?UTF-8?B?", Base64::encode(contacts(info.to)), "?=\r\n");
|
||||
info.message.append("Cc: =?UTF-8?B?", Base64::encode(contacts(info.cc)), "?=\r\n");
|
||||
info.message.append("Subject: =?UTF-8?B?", Base64::encode(info.subject), "?=\r\n");
|
||||
|
||||
string uniqueID = boundary();
|
||||
|
||||
info.message.append("MIME-Version: 1.0\r\n");
|
||||
info.message.append("Content-Type: multipart/mixed; boundary=", uniqueID, "\r\n");
|
||||
info.message.append("\r\n");
|
||||
|
||||
string format = (info.format == Format::Plain ? "text/plain" : "text/html");
|
||||
|
||||
info.message.append("--", uniqueID, "\r\n");
|
||||
info.message.append("Content-Type: ", format, "; charset=UTF-8\r\n");
|
||||
info.message.append("Content-Transfer-Encoding: base64\r\n");
|
||||
info.message.append("\r\n");
|
||||
info.message.append(split(Base64::encode(info.body)), "\r\n");
|
||||
info.message.append("\r\n");
|
||||
|
||||
for(auto& attachment : info.attachments) {
|
||||
info.message.append("--", uniqueID, "\r\n");
|
||||
info.message.append("Content-Type: application/octet-stream\r\n");
|
||||
info.message.append("Content-Transfer-Encoding: base64\r\n");
|
||||
info.message.append("Content-Disposition: attachment; size=", attachment.buffer.size(), "; filename*=UTF-8''", filename(attachment.name), "\r\n");
|
||||
info.message.append("\r\n");
|
||||
info.message.append(split(Base64::encode(attachment.buffer)), "\r\n");
|
||||
info.message.append("\r\n");
|
||||
}
|
||||
|
||||
info.message.append("--", uniqueID, "--\r\n");
|
||||
|
||||
addrinfo hints;
|
||||
memset(&hints, 0, sizeof(addrinfo));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
addrinfo* serverinfo;
|
||||
int status = getaddrinfo(info.server, string(info.port), &hints, &serverinfo);
|
||||
if(status != 0) return false;
|
||||
|
||||
int sock = socket(serverinfo->ai_family, serverinfo->ai_socktype, serverinfo->ai_protocol);
|
||||
if(sock == -1) return false;
|
||||
|
||||
int result = connect(sock, serverinfo->ai_addr, serverinfo->ai_addrlen);
|
||||
if(result == -1) return false;
|
||||
|
||||
string response;
|
||||
info.response.append(response = recv(sock));
|
||||
if(!response.beginswith("220 ")) { close(sock); return false; }
|
||||
|
||||
send(sock, {"HELO ", info.server, "\r\n"});
|
||||
info.response.append(response = recv(sock));
|
||||
if(!response.beginswith("250 ")) { close(sock); return false; }
|
||||
|
||||
send(sock, {"MAIL FROM: <", info.from.mail, ">\r\n"});
|
||||
info.response.append(response = recv(sock));
|
||||
if(!response.beginswith("250 ")) { close(sock); return false; }
|
||||
|
||||
for(auto& contact : info.to) {
|
||||
send(sock, {"RCPT TO: <", contact.mail, ">\r\n"});
|
||||
info.response.append(response = recv(sock));
|
||||
if(!response.beginswith("250 ")) { close(sock); return false; }
|
||||
}
|
||||
|
||||
for(auto& contact : info.cc) {
|
||||
send(sock, {"RCPT TO: <", contact.mail, ">\r\n"});
|
||||
info.response.append(response = recv(sock));
|
||||
if(!response.beginswith("250 ")) { close(sock); return false; }
|
||||
}
|
||||
|
||||
for(auto& contact : info.bcc) {
|
||||
send(sock, {"RCPT TO: <", contact.mail, ">\r\n"});
|
||||
info.response.append(response = recv(sock));
|
||||
if(!response.beginswith("250 ")) { close(sock); return false; }
|
||||
}
|
||||
|
||||
send(sock, {"DATA\r\n"});
|
||||
info.response.append(response = recv(sock));
|
||||
if(!response.beginswith("354 ")) { close(sock); return false; }
|
||||
|
||||
send(sock, {info.message, "\r\n", ".\r\n"});
|
||||
info.response.append(response = recv(sock));
|
||||
if(!response.beginswith("250 ")) { close(sock); return false; }
|
||||
|
||||
send(sock, {"QUIT\r\n"});
|
||||
info.response.append(response = recv(sock));
|
||||
//if(!response.beginswith("221 ")) { close(sock); return false; }
|
||||
|
||||
close(sock);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto SMTP::message() -> string {
|
||||
return info.message;
|
||||
}
|
||||
|
||||
auto SMTP::response() -> string {
|
||||
return info.response;
|
||||
}
|
||||
|
||||
auto SMTP::send(int sock, const string& text) -> bool {
|
||||
const char* data = text.data();
|
||||
unsigned size = text.size();
|
||||
while(size) {
|
||||
int length = ::send(sock, (const char*)data, size, 0);
|
||||
if(length == -1) return false;
|
||||
data += length;
|
||||
size -= length;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto SMTP::recv(int sock) -> string {
|
||||
vector<uint8_t> buffer;
|
||||
while(true) {
|
||||
char c;
|
||||
if(::recv(sock, &c, sizeof(char), 0) < 1) break;
|
||||
buffer.append(c);
|
||||
if(c == '\n') break;
|
||||
}
|
||||
buffer.append(0);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
auto SMTP::boundary() -> string {
|
||||
random_lfsr random;
|
||||
random.seed(time(0));
|
||||
string boundary;
|
||||
for(unsigned n = 0; n < 16; n++) boundary.append(hex<2>(random()));
|
||||
return boundary;
|
||||
}
|
||||
|
||||
auto SMTP::filename(const string& filename) -> string {
|
||||
string result;
|
||||
for(auto& n : filename) {
|
||||
if(n <= 32 || n >= 127) result.append("%", hex<2>(n));
|
||||
else result.append(n);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto SMTP::contact(const Information::Contact& contact) -> string {
|
||||
if(!contact.name) return contact.mail;
|
||||
return {"\"", contact.name, "\" <", contact.mail, ">"};
|
||||
}
|
||||
|
||||
auto SMTP::contacts(const vector<Information::Contact>& contacts) -> string {
|
||||
string result;
|
||||
for(auto& contact : contacts) {
|
||||
result.append(this->contact(contact), "; ");
|
||||
}
|
||||
result.rtrim("; ", 1L);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto SMTP::split(const string& text) -> string {
|
||||
string result;
|
||||
|
||||
unsigned offset = 0;
|
||||
while(offset < text.size()) {
|
||||
unsigned length = min(76, text.size() - offset);
|
||||
if(length < 76) {
|
||||
result.append(text.slice(offset));
|
||||
} else {
|
||||
result.append(text.slice(offset, 76), "\r\n");
|
||||
}
|
||||
offset += length;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#if defined(API_WINDOWS)
|
||||
auto SMTP::close(int sock) -> int {
|
||||
return closesocket(sock);
|
||||
}
|
||||
|
||||
SMTP::SMTP() {
|
||||
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if(sock == INVALID_SOCKET && WSAGetLastError() == WSANOTINITIALISED) {
|
||||
WSADATA wsaData;
|
||||
if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
||||
WSACleanup();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
close(sock);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
74
sort.hpp
Normal file
74
sort.hpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <nall/utility.hpp>
|
||||
|
||||
//class: merge sort
|
||||
//average: O(n log n)
|
||||
//worst: O(n log n)
|
||||
//memory: O(n)
|
||||
//stack: O(log n)
|
||||
//stable?: yes
|
||||
|
||||
//note: merge sort was chosen over quick sort, because:
|
||||
//* it is a stable sort
|
||||
//* it lacks O(n^2) worst-case overhead
|
||||
|
||||
#define NALL_SORT_INSERTION
|
||||
//#define NALL_SORT_SELECTION
|
||||
|
||||
namespace nall {
|
||||
|
||||
template<typename T, typename Comparator> auto sort(T list[], unsigned size, const Comparator& lessthan) -> void {
|
||||
if(size <= 1) return; //nothing to sort
|
||||
|
||||
//use insertion sort to quickly sort smaller blocks
|
||||
if(size < 64) {
|
||||
#if defined(NALL_SORT_INSERTION)
|
||||
for(signed i = 1, j; i < size; i++) {
|
||||
T copy = std::move(list[i]);
|
||||
for(j = i - 1; j >= 0; j--) {
|
||||
if(!lessthan(copy, list[j])) break;
|
||||
list[j + 1] = std::move(list[j]);
|
||||
}
|
||||
list[j + 1] = std::move(copy);
|
||||
}
|
||||
#elif defined(NALL_SORT_SELECTION)
|
||||
for(unsigned i = 0; i < size; i++) {
|
||||
unsigned min = i;
|
||||
for(unsigned j = i + 1; j < size; j++) {
|
||||
if(lessthan(list[j], list[min])) min = j;
|
||||
}
|
||||
if(min != i) std::swap(list[i], list[min]);
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
//split list in half and recursively sort both
|
||||
unsigned middle = size / 2;
|
||||
sort(list, middle, lessthan);
|
||||
sort(list + middle, size - middle, lessthan);
|
||||
|
||||
//left and right are sorted here; perform merge sort
|
||||
T* buffer = new T[size];
|
||||
unsigned offset = 0, left = 0, right = middle;
|
||||
while(left < middle && right < size) {
|
||||
if(!lessthan(list[right], list[left])) {
|
||||
buffer[offset++] = std::move(list[left++]);
|
||||
} else {
|
||||
buffer[offset++] = std::move(list[right++]);
|
||||
}
|
||||
}
|
||||
while(left < middle) buffer[offset++] = std::move(list[left++]);
|
||||
while(right < size) buffer[offset++] = std::move(list[right++]);
|
||||
|
||||
for(unsigned i = 0; i < size; i++) list[i] = std::move(buffer[i]);
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
template<typename T> auto sort(T list[], unsigned size) -> void {
|
||||
return sort(list, size, [](const T& l, const T& r) { return l < r; });
|
||||
}
|
||||
|
||||
}
|
58
stdint.hpp
Normal file
58
stdint.hpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
typedef signed char int8_t;
|
||||
typedef signed short int16_t;
|
||||
typedef signed int int32_t;
|
||||
typedef signed long long int64_t;
|
||||
typedef int64_t intmax_t;
|
||||
#if defined(_WIN64)
|
||||
typedef int64_t intptr_t;
|
||||
#else
|
||||
typedef int32_t intptr_t;
|
||||
#endif
|
||||
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned int uint32_t;
|
||||
typedef unsigned long long uint64_t;
|
||||
typedef uint64_t uintmax_t;
|
||||
#if defined(_WIN64)
|
||||
typedef uint64_t uintptr_t;
|
||||
#else
|
||||
typedef uint32_t uintptr_t;
|
||||
#endif
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
#if defined(__SIZEOF_INT128__)
|
||||
using int128_t = signed __int128;
|
||||
using uint128_t = unsigned __int128;
|
||||
#endif
|
||||
|
||||
using float32_t = float;
|
||||
using float64_t = double;
|
||||
using float80_t = long double;
|
||||
|
||||
static_assert(sizeof(int8_t) == 1, "int8_t is not of the correct size" );
|
||||
static_assert(sizeof(int16_t) == 2, "int16_t is not of the correct size");
|
||||
static_assert(sizeof(int32_t) == 4, "int32_t is not of the correct size");
|
||||
static_assert(sizeof(int64_t) == 8, "int64_t is not of the correct size");
|
||||
|
||||
static_assert(sizeof(uint8_t) == 1, "int8_t is not of the correct size" );
|
||||
static_assert(sizeof(uint16_t) == 2, "int16_t is not of the correct size");
|
||||
static_assert(sizeof(uint32_t) == 4, "int32_t is not of the correct size");
|
||||
static_assert(sizeof(uint64_t) == 8, "int64_t is not of the correct size");
|
||||
|
||||
static_assert(sizeof(float) >= 4, "float32_t is not of the correct size");
|
||||
static_assert(sizeof(double) >= 8, "float64_t is not of the correct size");
|
||||
static_assert(sizeof(long double) >= 10, "float80_t is not of the correct size");
|
||||
|
||||
using intmax = intmax_t;
|
||||
using intptr = intptr_t;
|
||||
|
||||
using uintmax = uintmax_t;
|
||||
using uintptr = uintptr_t;
|
||||
|
||||
using uint = unsigned int;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue