From ec425242378bb3acff7d614233d66da533a28baf Mon Sep 17 00:00:00 2001 From: Morten Delenk Date: Sun, 15 May 2016 12:42:10 +0200 Subject: [PATCH] First commit --- GNUmakefile | 162 +++++ algorithm.hpp | 26 + any.hpp | 84 +++ atoi.hpp | 87 +++ beat/archive.hpp | 131 ++++ beat/delta.hpp | 210 ++++++ beat/file.hpp | 23 + beat/linear.hpp | 148 +++++ beat/metadata.hpp | 117 ++++ beat/multi.hpp | 239 +++++++ beat/patch.hpp | 214 +++++++ bit.hpp | 85 +++ bitvector.hpp | 116 ++++ config.hpp | 112 ++++ database/odbc.hpp | 297 +++++++++ database/sqlite3.hpp | 203 ++++++ decode/base64.hpp | 46 ++ decode/bmp.hpp | 76 +++ decode/gzip.hpp | 78 +++ decode/inflate.hpp | 346 ++++++++++ decode/png.hpp | 332 ++++++++++ decode/url.hpp | 33 + decode/zip.hpp | 123 ++++ directory.hpp | 235 +++++++ dl.hpp | 126 ++++ dsp.hpp | 10 + dsp/buffer.hpp | 50 ++ dsp/core.hpp | 164 +++++ dsp/resample/average.hpp | 72 +++ dsp/resample/cosine.hpp | 44 ++ dsp/resample/cubic.hpp | 50 ++ dsp/resample/hermite.hpp | 62 ++ dsp/resample/lib/sinc.hpp | 600 ++++++++++++++++++ dsp/resample/linear.hpp | 43 ++ dsp/resample/nearest.hpp | 43 ++ dsp/resample/sinc.hpp | 62 ++ dsp/settings.hpp | 46 ++ emulation/super-famicom-usart.hpp | 95 +++ encode/base64.hpp | 67 ++ encode/bmp.hpp | 46 ++ encode/url.hpp | 22 + encode/zip.hpp | 92 +++ endian.hpp | 39 ++ file.hpp | 333 ++++++++++ filemap.hpp | 214 +++++++ function.hpp | 70 ++ hash/crc16.hpp | 40 ++ hash/crc32.hpp | 82 +++ hash/sha256.hpp | 108 ++++ hashset.hpp | 133 ++++ hid.hpp | 107 ++++ http/client.hpp | 56 ++ http/message.hpp | 99 +++ http/request.hpp | 184 ++++++ http/response.hpp | 246 +++++++ http/role.hpp | 158 +++++ http/server.hpp | 226 +++++++ image.hpp | 18 + image/base.hpp | 150 +++++ image/blend.hpp | 72 +++ image/core.hpp | 167 +++++ image/fill.hpp | 84 +++ image/interpolation.hpp | 62 ++ image/load.hpp | 97 +++ image/scale.hpp | 181 ++++++ image/static.hpp | 28 + image/utility.hpp | 118 ++++ inode.hpp | 83 +++ interpolation.hpp | 56 ++ intrinsics.hpp | 156 +++++ main.hpp | 26 + map.hpp | 58 ++ matrix.hpp | 30 + maybe.hpp | 91 +++ memory.hpp | 3 + memory/memory.hpp | 129 ++++ memory/pool.hpp | 62 ++ mosaic.hpp | 5 + mosaic/bitstream.hpp | 48 ++ mosaic/context.hpp | 221 +++++++ mosaic/parser.hpp | 119 ++++ nall.hpp | 76 +++ platform.hpp | 133 ++++ posix/service.hpp | 114 ++++ posix/shared-memory.hpp | 151 +++++ primitives.hpp | 350 ++++++++++ priority-queue.hpp | 106 ++++ property.hpp | 41 ++ random.hpp | 40 ++ range.hpp | 50 ++ run.hpp | 157 +++++ serial.hpp | 115 ++++ serializer.hpp | 146 +++++ service.hpp | 13 + set.hpp | 264 ++++++++ shared-memory.hpp | 12 + shared-pointer.hpp | 273 ++++++++ smtp.hpp | 315 +++++++++ sort.hpp | 74 +++ stdint.hpp | 58 ++ stream.hpp | 19 + stream/auto.hpp | 21 + stream/file.hpp | 39 ++ stream/gzip.hpp | 31 + stream/memory.hpp | 48 ++ stream/mmap.hpp | 41 ++ stream/stream.hpp | 98 +++ stream/vector.hpp | 37 ++ stream/zip.hpp | 35 + string.hpp | 55 ++ string/allocator/adaptive.hpp | 121 ++++ string/allocator/copy-on-write.hpp | 90 +++ .../allocator/small-string-optimization.hpp | 93 +++ string/allocator/vector.hpp | 82 +++ string/atoi.hpp | 17 + string/base.hpp | 293 +++++++++ string/cast.hpp | 225 +++++++ string/compare.hpp | 58 ++ string/convert.hpp | 53 ++ string/core.hpp | 56 ++ string/datetime.hpp | 30 + string/eval/evaluator.hpp | 146 +++++ string/eval/literal.hpp | 99 +++ string/eval/node.hpp | 37 ++ string/eval/parser.hpp | 164 +++++ string/find.hpp | 26 + string/format.hpp | 177 ++++++ string/hash.hpp | 33 + string/list.hpp | 73 +++ string/markup/bml.hpp | 189 ++++++ string/markup/find.hpp | 131 ++++ string/markup/node.hpp | 141 ++++ string/markup/xml.hpp | 217 +++++++ string/match.hpp | 90 +++ string/path.hpp | 72 +++ string/platform.hpp | 142 +++++ string/replace.hpp | 94 +++ string/split.hpp | 41 ++ string/transform/cml.hpp | 103 +++ string/transform/dml.hpp | 267 ++++++++ string/trim.hpp | 102 +++ string/utility.hpp | 158 +++++ string/view.hpp | 88 +++ thread.hpp | 137 ++++ traits.hpp | 53 ++ unique-pointer.hpp | 102 +++ utility.hpp | 18 + varint.hpp | 122 ++++ vector.hpp | 282 ++++++++ windows/detour.hpp | 189 ++++++ windows/guard.hpp | 13 + windows/guid.hpp | 27 + windows/launcher.hpp | 91 +++ windows/registry.hpp | 118 ++++ windows/service.hpp | 13 + windows/shared-memory.hpp | 27 + windows/utf8.hpp | 88 +++ xorg/guard.hpp | 31 + xorg/xorg.hpp | 9 + 159 files changed, 17316 insertions(+) create mode 100644 GNUmakefile create mode 100644 algorithm.hpp create mode 100644 any.hpp create mode 100644 atoi.hpp create mode 100644 beat/archive.hpp create mode 100644 beat/delta.hpp create mode 100644 beat/file.hpp create mode 100644 beat/linear.hpp create mode 100644 beat/metadata.hpp create mode 100644 beat/multi.hpp create mode 100644 beat/patch.hpp create mode 100644 bit.hpp create mode 100644 bitvector.hpp create mode 100644 config.hpp create mode 100644 database/odbc.hpp create mode 100644 database/sqlite3.hpp create mode 100644 decode/base64.hpp create mode 100644 decode/bmp.hpp create mode 100644 decode/gzip.hpp create mode 100644 decode/inflate.hpp create mode 100644 decode/png.hpp create mode 100644 decode/url.hpp create mode 100644 decode/zip.hpp create mode 100644 directory.hpp create mode 100644 dl.hpp create mode 100644 dsp.hpp create mode 100644 dsp/buffer.hpp create mode 100644 dsp/core.hpp create mode 100644 dsp/resample/average.hpp create mode 100644 dsp/resample/cosine.hpp create mode 100644 dsp/resample/cubic.hpp create mode 100644 dsp/resample/hermite.hpp create mode 100644 dsp/resample/lib/sinc.hpp create mode 100644 dsp/resample/linear.hpp create mode 100644 dsp/resample/nearest.hpp create mode 100644 dsp/resample/sinc.hpp create mode 100644 dsp/settings.hpp create mode 100644 emulation/super-famicom-usart.hpp create mode 100644 encode/base64.hpp create mode 100644 encode/bmp.hpp create mode 100644 encode/url.hpp create mode 100644 encode/zip.hpp create mode 100644 endian.hpp create mode 100644 file.hpp create mode 100644 filemap.hpp create mode 100644 function.hpp create mode 100644 hash/crc16.hpp create mode 100644 hash/crc32.hpp create mode 100644 hash/sha256.hpp create mode 100644 hashset.hpp create mode 100644 hid.hpp create mode 100644 http/client.hpp create mode 100644 http/message.hpp create mode 100644 http/request.hpp create mode 100644 http/response.hpp create mode 100644 http/role.hpp create mode 100644 http/server.hpp create mode 100644 image.hpp create mode 100644 image/base.hpp create mode 100644 image/blend.hpp create mode 100644 image/core.hpp create mode 100644 image/fill.hpp create mode 100644 image/interpolation.hpp create mode 100644 image/load.hpp create mode 100644 image/scale.hpp create mode 100644 image/static.hpp create mode 100644 image/utility.hpp create mode 100644 inode.hpp create mode 100644 interpolation.hpp create mode 100644 intrinsics.hpp create mode 100644 main.hpp create mode 100644 map.hpp create mode 100644 matrix.hpp create mode 100644 maybe.hpp create mode 100644 memory.hpp create mode 100644 memory/memory.hpp create mode 100644 memory/pool.hpp create mode 100644 mosaic.hpp create mode 100644 mosaic/bitstream.hpp create mode 100644 mosaic/context.hpp create mode 100644 mosaic/parser.hpp create mode 100644 nall.hpp create mode 100644 platform.hpp create mode 100644 posix/service.hpp create mode 100644 posix/shared-memory.hpp create mode 100644 primitives.hpp create mode 100644 priority-queue.hpp create mode 100644 property.hpp create mode 100644 random.hpp create mode 100644 range.hpp create mode 100644 run.hpp create mode 100644 serial.hpp create mode 100644 serializer.hpp create mode 100644 service.hpp create mode 100644 set.hpp create mode 100644 shared-memory.hpp create mode 100644 shared-pointer.hpp create mode 100644 smtp.hpp create mode 100644 sort.hpp create mode 100644 stdint.hpp create mode 100644 stream.hpp create mode 100644 stream/auto.hpp create mode 100644 stream/file.hpp create mode 100644 stream/gzip.hpp create mode 100644 stream/memory.hpp create mode 100644 stream/mmap.hpp create mode 100644 stream/stream.hpp create mode 100644 stream/vector.hpp create mode 100644 stream/zip.hpp create mode 100644 string.hpp create mode 100644 string/allocator/adaptive.hpp create mode 100644 string/allocator/copy-on-write.hpp create mode 100644 string/allocator/small-string-optimization.hpp create mode 100644 string/allocator/vector.hpp create mode 100644 string/atoi.hpp create mode 100644 string/base.hpp create mode 100644 string/cast.hpp create mode 100644 string/compare.hpp create mode 100644 string/convert.hpp create mode 100644 string/core.hpp create mode 100644 string/datetime.hpp create mode 100644 string/eval/evaluator.hpp create mode 100644 string/eval/literal.hpp create mode 100644 string/eval/node.hpp create mode 100644 string/eval/parser.hpp create mode 100644 string/find.hpp create mode 100644 string/format.hpp create mode 100644 string/hash.hpp create mode 100644 string/list.hpp create mode 100644 string/markup/bml.hpp create mode 100644 string/markup/find.hpp create mode 100644 string/markup/node.hpp create mode 100644 string/markup/xml.hpp create mode 100644 string/match.hpp create mode 100644 string/path.hpp create mode 100644 string/platform.hpp create mode 100644 string/replace.hpp create mode 100644 string/split.hpp create mode 100644 string/transform/cml.hpp create mode 100644 string/transform/dml.hpp create mode 100644 string/trim.hpp create mode 100644 string/utility.hpp create mode 100644 string/view.hpp create mode 100644 thread.hpp create mode 100644 traits.hpp create mode 100644 unique-pointer.hpp create mode 100644 utility.hpp create mode 100644 varint.hpp create mode 100644 vector.hpp create mode 100644 windows/detour.hpp create mode 100644 windows/guard.hpp create mode 100644 windows/guid.hpp create mode 100644 windows/launcher.hpp create mode 100644 windows/registry.hpp create mode 100644 windows/service.hpp create mode 100644 windows/shared-memory.hpp create mode 100644 windows/utf8.hpp create mode 100644 xorg/guard.hpp create mode 100644 xorg/xorg.hpp diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..ec63d5e --- /dev/null +++ b/GNUmakefile @@ -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,) diff --git a/algorithm.hpp b/algorithm.hpp new file mode 100644 index 0000000..612b255 --- /dev/null +++ b/algorithm.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#undef min +#undef max + +namespace nall { namespace { + +template auto min(const T& t, const U& u) -> T { + return t < u ? t : u; +} + +template auto min(const T& t, const U& u, P&&... p) -> T { + return t < u ? min(t, forward

(p)...) : min(u, forward

(p)...); +} + +template auto max(const T& t, const U& u) -> T { + return t > u ? t : u; +} + +template auto max(const T& t, const U& u, P&&... p) -> T { + return t > u ? max(t, forward

(p)...) : max(u, forward

(p)...); +} + +}} diff --git a/any.hpp b/any.hpp new file mode 100644 index 0000000..ed1971e --- /dev/null +++ b/any.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include +#include + +namespace nall { + +struct any { + any() = default; + any(const any& source) { operator=(source); } + any(any&& source) { operator=(move(source)); } + template 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 auto is() const -> bool { + return type() == typeid(typename remove_reference::type); + } + + template auto get() -> T& { + if(!is()) throw; + return static_cast::type>*>(container)->value; + } + + template auto get() const -> const T& { + if(!is()) throw; + return static_cast::type>*>(container)->value; + } + + template auto get(const T& fallback) const -> const T& { + if(!is()) return fallback; + return static_cast::type>*>(container)->value; + } + + template auto operator=(const T& value) -> any& { + using auto_t = type_if, typename remove_extent::type>::type*, T>; + + if(type() == typeid(auto_t)) { + static_cast*>(container)->value = (auto_t)value; + } else { + if(container) delete container; + container = new holder((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 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; + }; +}; + +} diff --git a/atoi.hpp b/atoi.hpp new file mode 100644 index 0000000..4fc639e --- /dev/null +++ b/atoi.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include + +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); +} + +} diff --git a/beat/archive.hpp b/beat/archive.hpp new file mode 100644 index 0000000..65be338 --- /dev/null +++ b/beat/archive.hpp @@ -0,0 +1,131 @@ +#pragma once + +#include + +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; + +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 { + 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 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}); + } +} + +}} diff --git a/beat/delta.hpp b/beat/delta.hpp new file mode 100644 index 0000000..17c9834 --- /dev/null +++ b/beat/delta.hpp @@ -0,0 +1,210 @@ +#pragma once + +#include +#include +#include +#include + +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; +} + +} diff --git a/beat/file.hpp b/beat/file.hpp new file mode 100644 index 0000000..f3f4f49 --- /dev/null +++ b/beat/file.hpp @@ -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); +} + +}} diff --git a/beat/linear.hpp b/beat/linear.hpp new file mode 100644 index 0000000..94e1953 --- /dev/null +++ b/beat/linear.hpp @@ -0,0 +1,148 @@ +#pragma once + +#include +#include +#include +#include + +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; +} + +} diff --git a/beat/metadata.hpp b/beat/metadata.hpp new file mode 100644 index 0000000..4336cde --- /dev/null +++ b/beat/metadata.hpp @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include +#include + +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; +} + +} diff --git a/beat/multi.hpp b/beat/multi.hpp new file mode 100644 index 0000000..7fb9add --- /dev/null +++ b/beat/multi.hpp @@ -0,0 +1,239 @@ +#pragma once + +#include +#include +#include + +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 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; + } +}; + +} diff --git a/beat/patch.hpp b/beat/patch.hpp new file mode 100644 index 0000000..ab502ea --- /dev/null +++ b/beat/patch.hpp @@ -0,0 +1,214 @@ +#pragma once + +#include +#include +#include +#include + +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; +} + +} diff --git a/bit.hpp b/bit.hpp new file mode 100644 index 0000000..e182104 --- /dev/null +++ b/bit.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include + +namespace nall { + +template 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 inline auto uclip(const uintmax_t x) -> uintmax_t { + enum : uintmax_t { b = 1ull << (bits - 1), m = b * 2 - 1 }; + return (x & m); +} + +template 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 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; + } +} + +} diff --git a/bitvector.hpp b/bitvector.hpp new file mode 100644 index 0000000..f968cc3 --- /dev/null +++ b/bitvector.hpp @@ -0,0 +1,116 @@ +#pragma once + +#include + +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; +}; + +} diff --git a/config.hpp b/config.hpp new file mode 100644 index 0000000..3201cfe --- /dev/null +++ b/config.hpp @@ -0,0 +1,112 @@ +#pragma once + +#include +#include +#include + +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 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 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 { + 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; + } +}; + +} +} diff --git a/database/odbc.hpp b/database/odbc.hpp new file mode 100644 index 0000000..a714c6a --- /dev/null +++ b/database/odbc.hpp @@ -0,0 +1,297 @@ +#pragma once + +#include + +#include +#include +#include + +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(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(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(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 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 { + if(auto value = _values(column)) return value.get>({}); + vector 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)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 { return data(_output++); } + + protected: + virtual auto statement() -> SQLHANDLE { return _statement; } + + SQLHANDLE _statement = nullptr; + unsigned _output = 0; + vector _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& value) { return _bindings.append({column, any{(vector)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& value) { return bind(_input++, value); } + + auto step() -> bool { + if(!_stepped) { + for(auto& binding : _bindings) { + if(binding.value.is()) { + 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()) { + SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &binding.value.get(), 0, nullptr); + } else if(binding.value.is()) { + SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, &binding.value.get(), 0, nullptr); + } else if(binding.value.is()) { + SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_INTEGER, 0, 0, &binding.value.get(), 0, nullptr); + } else if(binding.value.is()) { + SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_UBIGINT, SQL_INTEGER, 0, 0, &binding.value.get(), 0, nullptr); + } else if(binding.value.is()) { + SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_DOUBLE, 0, 0, &binding.value.get(), 0, nullptr); + } else if(binding.value.is()) { + auto& value = binding.value.get(); + 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>()) { + auto& value = binding.value.get>(); + 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 _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 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)...); + return query; + } + +private: + auto success() const -> bool { return _result == SQL_SUCCESS || _result == SQL_SUCCESS_WITH_INFO; } + + auto bind(Query&) -> void {} + template auto bind(Query& query, const T& value, P&&... p) -> void { + query.bind(value); + bind(query, forward

(p)...); + } + + SQLHANDLE _environment = nullptr; + SQLHANDLE _connection = nullptr; + SQLRETURN _result = SQL_SUCCESS; +}; + +}} diff --git a/database/sqlite3.hpp b/database/sqlite3.hpp new file mode 100644 index 0000000..b78cf41 --- /dev/null +++ b/database/sqlite3.hpp @@ -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 + +#include +#include + +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 { + vector 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 { 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& 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& 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 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)...); + 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 auto bind(Query& query, const T& value, P&&... p) -> void { + query.bind(value); + bind(query, forward

(p)...); + } + + bool _debug = false; + sqlite3* _database = nullptr; +}; + +}} diff --git a/decode/base64.hpp b/decode/base64.hpp new file mode 100644 index 0000000..48661dd --- /dev/null +++ b/decode/base64.hpp @@ -0,0 +1,46 @@ +#pragma once + +namespace nall { namespace Decode { + +inline auto Base64(const string& text) -> vector { + 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 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; +} + +}} diff --git a/decode/bmp.hpp b/decode/bmp.hpp new file mode 100644 index 0000000..17b094c --- /dev/null +++ b/decode/bmp.hpp @@ -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; + } +}; + +}} diff --git a/decode/gzip.hpp b/decode/gzip.hpp new file mode 100644 index 0000000..15ad463 --- /dev/null +++ b/decode/gzip.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include + +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); +} + +}} diff --git a/decode/inflate.hpp b/decode/inflate.hpp new file mode 100644 index 0000000..e07fd52 --- /dev/null +++ b/decode/inflate.hpp @@ -0,0 +1,346 @@ +#pragma once + +#include + +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; +} + +} + +}} diff --git a/decode/png.hpp b/decode/png.hpp new file mode 100644 index 0000000..3e884d0 --- /dev/null +++ b/decode/png.hpp @@ -0,0 +1,332 @@ +#pragma once + +#include +#include + +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; +} + +}} diff --git a/decode/url.hpp b/decode/url.hpp new file mode 100644 index 0000000..9bbfdfd --- /dev/null +++ b/decode/url.hpp @@ -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; +} + +}} diff --git a/decode/zip.hpp b/decode/zip.hpp new file mode 100644 index 0000000..e6314aa --- /dev/null +++ b/decode/zip.hpp @@ -0,0 +1,123 @@ +#pragma once + +#include +#include +#include +#include + +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 { + vector 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; +}; + +}} diff --git a/directory.hpp b/directory.hpp new file mode 100644 index 0000000..34ced00 --- /dev/null +++ b/directory.hpp @@ -0,0 +1,235 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#if defined(PLATFORM_WINDOWS) + #include +#else + #include + #include + #include +#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 + +} diff --git a/dl.hpp b/dl.hpp new file mode 100644 index 0000000..46d6a10 --- /dev/null +++ b/dl.hpp @@ -0,0 +1,126 @@ +#pragma once + +//dynamic linking support + +#include +#include +#include +#include + +#if defined(PLATFORM_WINDOWS) + #include + #include +#else + #include +#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 + +} diff --git a/dsp.hpp b/dsp.hpp new file mode 100644 index 0000000..4a3ffc0 --- /dev/null +++ b/dsp.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include +#ifdef __SSE__ + #include +#endif + +#include diff --git a/dsp/buffer.hpp b/dsp/buffer.hpp new file mode 100644 index 0000000..22934d0 --- /dev/null +++ b/dsp/buffer.hpp @@ -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; +}; diff --git a/dsp/core.hpp b/dsp/core.hpp new file mode 100644 index 0000000..f273578 --- /dev/null +++ b/dsp/core.hpp @@ -0,0 +1,164 @@ +#pragma once + +#include +#include +#include + +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(); +} + +} diff --git a/dsp/resample/average.hpp b/dsp/resample/average.hpp new file mode 100644 index 0000000..c98b7fe --- /dev/null +++ b/dsp/resample/average.hpp @@ -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; +} diff --git a/dsp/resample/cosine.hpp b/dsp/resample/cosine.hpp new file mode 100644 index 0000000..68c0a44 --- /dev/null +++ b/dsp/resample/cosine.hpp @@ -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; +} diff --git a/dsp/resample/cubic.hpp b/dsp/resample/cubic.hpp new file mode 100644 index 0000000..1b297d9 --- /dev/null +++ b/dsp/resample/cubic.hpp @@ -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; +} diff --git a/dsp/resample/hermite.hpp b/dsp/resample/hermite.hpp new file mode 100644 index 0000000..a6215e0 --- /dev/null +++ b/dsp/resample/hermite.hpp @@ -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; +} diff --git a/dsp/resample/lib/sinc.hpp b/dsp/resample/lib/sinc.hpp new file mode 100644 index 0000000..135b45e --- /dev/null +++ b/dsp/resample/lib/sinc.hpp @@ -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 coeffs_mem; + + // second half of ringbuffer should be copy of first half. + resample_samp_t *rb; + std::vector 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 coeffs; // Pointers into coeff_mem. + std::vector coeff_mem; + + + std::vector 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 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 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(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; +} diff --git a/dsp/resample/linear.hpp b/dsp/resample/linear.hpp new file mode 100644 index 0000000..b7aa7ec --- /dev/null +++ b/dsp/resample/linear.hpp @@ -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; +} diff --git a/dsp/resample/nearest.hpp b/dsp/resample/nearest.hpp new file mode 100644 index 0000000..50d6da7 --- /dev/null +++ b/dsp/resample/nearest.hpp @@ -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; +} diff --git a/dsp/resample/sinc.hpp b/dsp/resample/sinc.hpp new file mode 100644 index 0000000..e39e9fe --- /dev/null +++ b/dsp/resample/sinc.hpp @@ -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); + } +} diff --git a/dsp/settings.hpp b/dsp/settings.hpp new file mode 100644 index 0000000..3254c14 --- /dev/null +++ b/dsp/settings.hpp @@ -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(); +} diff --git a/emulation/super-famicom-usart.hpp b/emulation/super-famicom-usart.hpp new file mode 100644 index 0000000..c8a1650 --- /dev/null +++ b/emulation/super-famicom-usart.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +using namespace nall; + +#include +#include +#include + +static function bool> usart_quit; +static function void> usart_usleep; +static function bool> usart_readable; +static function uint8> usart_read; +static function bool> usart_writable; +static function void> usart_write; + +extern "C" auto usart_init( + function bool> quit, + function void> usleep, + function bool> readable, + function uint8> read, + function bool> writable, + function 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 +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(); +} diff --git a/encode/base64.hpp b/encode/base64.hpp new file mode 100644 index 0000000..46fa5f5 --- /dev/null +++ b/encode/base64.hpp @@ -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 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& 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); +} + +}} diff --git a/encode/bmp.hpp b/encode/bmp.hpp new file mode 100644 index 0000000..2aa037f --- /dev/null +++ b/encode/bmp.hpp @@ -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; + } +}; + +}} diff --git a/encode/url.hpp b/encode/url.hpp new file mode 100644 index 0000000..7a95921 --- /dev/null +++ b/encode/url.hpp @@ -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; +} + +}} diff --git a/encode/zip.hpp b/encode/zip.hpp new file mode 100644 index 0000000..c6beae2 --- /dev/null +++ b/encode/zip.hpp @@ -0,0 +1,92 @@ +#pragma once + +//creates uncompressed ZIP archives + +#include +#include + +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 directory; +}; + +}} diff --git a/endian.hpp b/endian.hpp new file mode 100644 index 0000000..f08a742 --- /dev/null +++ b/endian.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +#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 diff --git a/file.hpp b/file.hpp new file mode 100644 index 0000000..b4495ac --- /dev/null +++ b/file.hpp @@ -0,0 +1,333 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +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 { + vector 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& 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 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; + } +}; + +} diff --git a/filemap.hpp b/filemap.hpp new file mode 100644 index 0000000..2a2870c --- /dev/null +++ b/filemap.hpp @@ -0,0 +1,214 @@ +#pragma once + +#include +#include +#include + +#include +#include +#if defined(_WIN32) + #include +#else + #include + #include + #include + #include + #include +#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 +}; + +} diff --git a/function.hpp b/function.hpp new file mode 100644 index 0000000..14eebb4 --- /dev/null +++ b/function.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include + +namespace nall { + +template struct function; + +template struct function R> { + //value = true if auto L::operator()(P...) -> R exists + template struct is_compatible { + template static auto exists(T*) -> const typename is_same().operator()(declval

()...))>::type; + template static auto exists(...) -> const false_type; + static constexpr bool value = decltype(exists(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 function(auto (C::*function)(P...) -> R, C* object) { callback = new member(function, object); } + template function(auto (C::*function)(P...) const -> R, C* object) { callback = new member((auto (C::*)(P...) -> R)function, object); } + template>> function(const L& object) { callback = new lambda(object); } + ~function() { if(callback) delete callback; } + + explicit operator bool() const { return callback; } + auto operator()(P... p) const -> R { return (*callback)(forward

(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)...); } + auto copy() const -> container* { return new global(function); } + global(auto (*function)(P...) -> R) : function(function) {} + }; + + template struct member : container { + auto (C::*function)(P...) -> R; + C* object; + auto operator()(P... p) const -> R { return (object->*function)(forward

(p)...); } + auto copy() const -> container* { return new member(function, object); } + member(auto (C::*function)(P...) -> R, C* object) : function(function), object(object) {} + }; + + template struct lambda : container { + mutable L object; + auto operator()(P... p) const -> R { return object(forward

(p)...); } + auto copy() const -> container* { return new lambda(object); } + lambda(const L& object) : object(object) {} + }; +}; + +} diff --git a/hash/crc16.hpp b/hash/crc16.hpp new file mode 100644 index 0000000..63d3c47 --- /dev/null +++ b/hash/crc16.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include + +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; +}; + +}} diff --git a/hash/crc32.hpp b/hash/crc32.hpp new file mode 100644 index 0000000..73ded1a --- /dev/null +++ b/hash/crc32.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include + +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; +}; + +}} diff --git a/hash/sha256.hpp b/hash/sha256.hpp new file mode 100644 index 0000000..0e71f72 --- /dev/null +++ b/hash/sha256.hpp @@ -0,0 +1,108 @@ +#pragma once + +#include + +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 { + SHA256 self(*this); + self.finish(); + vector 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; +}; + +}} diff --git a/hashset.hpp b/hashset.hpp new file mode 100644 index 0000000..7efd057 --- /dev/null +++ b/hashset.hpp @@ -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 +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 { + 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 { + 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 +}; + +} diff --git a/hid.hpp b/hid.hpp new file mode 100644 index 0000000..fbbe06c --- /dev/null +++ b/hid.hpp @@ -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 { + 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 { + for(auto id : range(size())) { + if(operator[](id)._name == name) return id; + } + return nothing; + } + +private: + string _name; + friend class Device; +}; + +struct Device : vector { + 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 { + 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; +}; + +}} diff --git a/http/client.hpp b/http/client.hpp new file mode 100644 index 0000000..716d4e9 --- /dev/null +++ b/http/client.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include + +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; + } +} + +}} diff --git a/http/message.hpp b/http/message.hpp new file mode 100644 index 0000000..0bb7c6b --- /dev/null +++ b/http/message.hpp @@ -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 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 { + vector 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 variables; +}; + +struct Message { + using type = Message; + + virtual auto head(const function& callback) const -> bool = 0; + virtual auto setHead() -> bool = 0; + + virtual auto body(const function& callback) const -> bool = 0; + virtual auto setBody() -> bool = 0; + + Variables header; + +//private: + string _head; + string _body; +}; + +}} diff --git a/http/request.hpp b/http/request.hpp new file mode 100644 index 0000000..a9afe8d --- /dev/null +++ b/http/request.hpp @@ -0,0 +1,184 @@ +#pragma once + +#include +#include +#include + +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& callback) const -> bool override; + inline auto setHead() -> bool override; + + inline auto body(const function& 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& 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& 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; +} + +}} diff --git a/http/response.hpp b/http/response.hpp new file mode 100644 index 0000000..9b00f99 --- /dev/null +++ b/http/response.hpp @@ -0,0 +1,246 @@ +#pragma once + +#include + +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& callback) const -> bool override; + inline auto setHead() -> bool override; + + inline auto body(const function& 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& { return _data; } + inline auto setData(const vector& 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 _data; + string _file; + string _text; +}; + +auto Response::head(const function& 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& 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& 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; +} + +}} diff --git a/http/role.hpp b/http/role.hpp new file mode 100644 index 0000000..29eaea8 --- /dev/null +++ b/http/role.hpp @@ -0,0 +1,158 @@ +#pragma once + +//Role: base class for Client and Server +//provides shared functionality + +#include +#include + +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; +} + +}} diff --git a/http/server.hpp b/http/server.hpp new file mode 100644 index 0000000..a2e0422 --- /dev/null +++ b/http/server.hpp @@ -0,0 +1,226 @@ +#pragma once + +#include +#include + +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& function = {}) -> void; + inline auto scan() -> string; + inline auto close() -> void; + ~Server() { close(); } + +private: + function callback; + std::atomic 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& 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(); +} + +}} diff --git a/image.hpp b/image.hpp new file mode 100644 index 0000000..03e5484 --- /dev/null +++ b/image.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/image/base.hpp b/image/base.hpp new file mode 100644 index 0000000..83742e9 --- /dev/null +++ b/image/base.hpp @@ -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& 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 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}; +}; + +} diff --git a/image/blend.hpp b/image/blend.hpp new file mode 100644 index 0000000..13d2ba3 --- /dev/null +++ b/image/blend.hpp @@ -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(); + } + } +} + +} diff --git a/image/core.hpp b/image/core.hpp new file mode 100644 index 0000000..e018935 --- /dev/null +++ b/image/core.hpp @@ -0,0 +1,167 @@ +#pragma once + +namespace nall { + +image::image(const image& source) { + operator=(source); +} + +image::image(image&& source) { + operator=(forward(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& 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; +} + +} diff --git a/image/fill.hpp b/image/fill.hpp new file mode 100644 index 0000000..8ff1f88 --- /dev/null +++ b/image/fill.hpp @@ -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 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); + }); +} + +} diff --git a/image/interpolation.hpp b/image/interpolation.hpp new file mode 100644 index 0000000..c839f6c --- /dev/null +++ b/image/interpolation.hpp @@ -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); +} + +} diff --git a/image/load.hpp b/image/load.hpp new file mode 100644 index 0000000..6edefe1 --- /dev/null +++ b/image/load.hpp @@ -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; +} + +} diff --git a/image/scale.hpp b/image/scale.hpp new file mode 100644 index 0000000..451acd3 --- /dev/null +++ b/image/scale.hpp @@ -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; +} + +} diff --git a/image/static.hpp b/image/static.hpp new file mode 100644 index 0000000..7dc0d84 --- /dev/null +++ b/image/static.hpp @@ -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; +} + +} diff --git a/image/utility.hpp b/image/utility.hpp new file mode 100644 index 0000000..50278c7 --- /dev/null +++ b/image/utility.hpp @@ -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)); +} + +} diff --git a/inode.hpp b/inode.hpp new file mode 100644 index 0000000..a5f9e90 --- /dev/null +++ b/inode.hpp @@ -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 +#include + +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 + } +}; + +} diff --git a/interpolation.hpp b/interpolation.hpp new file mode 100644 index 0000000..587a9b2 --- /dev/null +++ b/interpolation.hpp @@ -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); + } +}; + +} diff --git a/intrinsics.hpp b/intrinsics.hpp new file mode 100644 index 0000000..8f5ad9c --- /dev/null +++ b/intrinsics.hpp @@ -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 +#elif defined(PLATFORM_LINUX) + #include +#elif defined(PLATFORM_BSD) + #include +#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 + +} diff --git a/main.hpp b/main.hpp new file mode 100644 index 0000000..925e8f4 --- /dev/null +++ b/main.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +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); +} diff --git a/map.hpp b/map.hpp new file mode 100644 index 0000000..58b1e11 --- /dev/null +++ b/map.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +namespace nall { + +template 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 { + 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::iterator { return root.begin(); } + auto end() -> typename set::iterator { return root.end(); } + + auto begin() const -> const typename set::iterator { return root.begin(); } + auto end() const -> const typename set::iterator { return root.end(); } + +protected: + set root; +}; + +template struct bimap { + auto find(const T& key) const -> maybe { return tmap.find(key); } + auto find(const U& key) const -> maybe { 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::node_t>::iterator { return tmap.begin(); } + auto end() -> typename set::node_t>::iterator { return tmap.end(); } + + auto begin() const -> const typename set::node_t>::iterator { return tmap.begin(); } + auto end() const -> const typename set::node_t>::iterator { return tmap.end(); } + +protected: + map tmap; + map umap; +}; + +} diff --git a/matrix.hpp b/matrix.hpp new file mode 100644 index 0000000..fd79686 --- /dev/null +++ b/matrix.hpp @@ -0,0 +1,30 @@ +#pragma once + +namespace nall { + +namespace Matrix { + +template 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 inline auto Multiply(const T* xdata, unsigned xrows, unsigned xcols, const T* ydata, unsigned yrows, unsigned ycols) -> vector { + vector output; + output.resize(xrows * ycols); + Multiply(output.data(), xdata, xrows, xcols, ydata, yrows, ycols); + return output; +} + +} + +} diff --git a/maybe.hpp b/maybe.hpp new file mode 100644 index 0000000..6ebd2ab --- /dev/null +++ b/maybe.hpp @@ -0,0 +1,91 @@ +#pragma once + +namespace nall { + +struct nothing_t {}; +static nothing_t nothing; + +template +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 +struct maybe { + 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; +}; + +} diff --git a/memory.hpp b/memory.hpp new file mode 100644 index 0000000..7331dd2 --- /dev/null +++ b/memory.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include diff --git a/memory/memory.hpp b/memory/memory.hpp new file mode 100644 index 0000000..f73297d --- /dev/null +++ b/memory/memory.hpp @@ -0,0 +1,129 @@ +#pragma once + +#include + +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 + +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; +} + +} diff --git a/memory/pool.hpp b/memory/pool.hpp new file mode 100644 index 0000000..17d11f7 --- /dev/null +++ b/memory/pool.hpp @@ -0,0 +1,62 @@ +#pragma once + +namespace nall { +namespace memory { + +template +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; +}; + +} +} diff --git a/mosaic.hpp b/mosaic.hpp new file mode 100644 index 0000000..ac6e6f2 --- /dev/null +++ b/mosaic.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include +#include +#include diff --git a/mosaic/bitstream.hpp b/mosaic/bitstream.hpp new file mode 100644 index 0000000..d46e971 --- /dev/null +++ b/mosaic/bitstream.hpp @@ -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; +}; + +}} diff --git a/mosaic/context.hpp b/mosaic/context.hpp new file mode 100644 index 0000000..32950db --- /dev/null +++ b/mosaic/context.hpp @@ -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& 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 block; + + uint tileWidth; + uint tileHeight; + uint tileStride; + uint tileOffset; + vector tile; + + uint mosaicWidth; + uint mosaicHeight; + uint mosaicStride; + uint mosaicOffset; + vector mosaic; + + uint paddingWidth; + uint paddingHeight; + uint paddingColor; + vector palette; +}; + +}} diff --git a/mosaic/parser.hpp b/mosaic/parser.hpp new file mode 100644 index 0000000..5d68cd8 --- /dev/null +++ b/mosaic/parser.hpp @@ -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; +}; + +}} diff --git a/nall.hpp b/nall.hpp new file mode 100644 index 0000000..fe5ea45 --- /dev/null +++ b/nall.hpp @@ -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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(PLATFORM_WINDOWS) + #include + #include +#endif + +#if defined(API_POSIX) + #include +#endif diff --git a/platform.hpp b/platform.hpp new file mode 100644 index 0000000..a8e6aed --- /dev/null +++ b/platform.hpp @@ -0,0 +1,133 @@ +#pragma once + +#include + +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 +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if defined(PLATFORM_WINDOWS) + #include + #include + #include + #include + #include + #include +#else + #include + #include + #include + #include + #include + #include + #include + #include + #include +#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 diff --git a/posix/service.hpp b/posix/service.hpp new file mode 100644 index 0000000..a8e1ed4 --- /dev/null +++ b/posix/service.hpp @@ -0,0 +1,114 @@ +#pragma once + +#include + +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; +} + +} diff --git a/posix/shared-memory.hpp b/posix/shared-memory.hpp new file mode 100644 index 0000000..c5e2756 --- /dev/null +++ b/posix/shared-memory.hpp @@ -0,0 +1,151 @@ +#pragma once + +#include +#include + +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; +}; + +} diff --git a/primitives.hpp b/primitives.hpp new file mode 100644 index 0000000..f551aa5 --- /dev/null +++ b/primitives.hpp @@ -0,0 +1,350 @@ +#pragma once + +#include +#include + +namespace nall { + +struct Boolean { + inline Boolean() : data(false) {} + template inline Boolean(const T& value) : data(value) {} + + inline operator bool() const { return data; } + template inline auto& operator=(const T& value) { data = value; return *this; } + + inline auto serialize(serializer& s) { s(data); } + +private: + bool data; +}; + +template struct Natural { + using type = + type_if, uint8_t, + type_if, uint16_t, + type_if, uint32_t, + type_if, uint64_t, + void>>>>; + + enum : type { Mask = ~0ull >> (64 - Bits) }; + + inline Natural() : data(0) {} + template inline Natural(const T& value) { assign(value); } + + inline operator type() const { return data; } + template 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 struct Integer { + using type = + type_if, int8_t, + type_if, int16_t, + type_if, int32_t, + type_if, int64_t, + void>>>>; + using utype = typename Natural::type; + + enum : utype { Mask = ~0ull >> (64 - Bits), Sign = 1ull << (Bits - 1) }; + + inline Integer() : data(0) {} + template inline Integer(const T& value) { assign(value); } + + inline operator type() const { return data; } + template 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 struct Real { + using type = + type_if, float32_t, + type_if, float64_t, + type_if, float80_t, + void>>>; + + inline Real() : data(0.0) {} + template inline Real(const T& value) : data((type)value) {} + + inline operator type() const { return data; } + template 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>; diff --git a/priority-queue.hpp b/priority-queue.hpp new file mode 100644 index 0000000..49b5fa9 --- /dev/null +++ b/priority-queue.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include +#include + +namespace nall { + +template 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 struct priority_queue { + priority_queue(unsigned size, function callback = &priority_queue_nocallback) : 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::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 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::max() >> 1); + } +}; + +} diff --git a/property.hpp b/property.hpp new file mode 100644 index 0000000..e6c076b --- /dev/null +++ b/property.hpp @@ -0,0 +1,41 @@ +#pragma once + +namespace nall { + +template struct property { + template 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 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 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; + }; +}; + +} diff --git a/random.hpp b/random.hpp new file mode 100644 index 0000000..4187eab --- /dev/null +++ b/random.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +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(); +} + +} diff --git a/range.hpp b/range.hpp new file mode 100644 index 0000000..f29a838 --- /dev/null +++ b/range.hpp @@ -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 inline auto range(const vector& container) { + return range_t{0, (int)container.size(), 1}; +} + +template inline auto rrange(const vector& container) { + return range_t{(int)container.size() - 1, -1, -1}; +} + +} diff --git a/run.hpp b/run.hpp new file mode 100644 index 0000000..eb4955e --- /dev/null +++ b/run.hpp @@ -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 +#include + +namespace nall { + +#if defined(PLATFORM_MACOSX) || defined(PLATFORM_LINUX) || defined(PLATFORM_BSD) + +template 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)...); + *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 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)...); + *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 inline auto execute(const string& name, P&&... p) -> string { + lstring argl(name, forward

(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 inline auto invoke(const string& name, P&&... p) -> void { + lstring argl(forward

(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 inline auto execute(const string& name, P&&... p) -> string { + return ""; +} + +template inline auto invoke(const string& name, P&&... p) -> void { +} + +#endif + +} diff --git a/serial.hpp b/serial.hpp new file mode 100644 index 0000000..4d580dc --- /dev/null +++ b/serial.hpp @@ -0,0 +1,115 @@ +#pragma once + +#include +#include +#include + +#if !defined(API_POSIX) + #error "nall/serial: unsupported system" +#endif + +#include +#include +#include +#include + +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; +}; + +} diff --git a/serializer.hpp b/serializer.hpp new file mode 100644 index 0000000..bb7d862 --- /dev/null +++ b/serializer.hpp @@ -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 +#include +#include + +namespace nall { + +struct serializer; + +template +struct has_serialize { + template static auto test(decltype(std::declval().serialize(std::declval()))*) -> char; + template static auto test(...) -> long; + static const bool value = sizeof(test(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 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 auto integer(T& value) -> serializer& { + enum { size = std::is_same::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 auto array(T (&array)[N]) -> serializer& { + for(uint n = 0; n < N; n++) operator()(array[n]); + return *this; + } + + template auto array(T array, uint size) -> serializer& { + for(uint n = 0; n < size; n++) operator()(array[n]); + return *this; + } + + template auto operator()(T& value, typename std::enable_if::value>::type* = 0) -> serializer& { value.serialize(*this); return *this; } + template auto operator()(T& value, typename std::enable_if::value>::type* = 0) -> serializer& { return integer(value); } + template auto operator()(T& value, typename std::enable_if::value>::type* = 0) -> serializer& { return floatingpoint(value); } + template auto operator()(T& value, typename std::enable_if::value>::type* = 0) -> serializer& { return array(value); } + template auto operator()(T& value, uint size, typename std::enable_if::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; +}; + +}; diff --git a/service.hpp b/service.hpp new file mode 100644 index 0000000..3d3c902 --- /dev/null +++ b/service.hpp @@ -0,0 +1,13 @@ +#pragma once + +//service model template built on top of shared-memory + +#include + +#if defined(API_POSIX) + #include +#endif + +#if defined(API_WINDOWS) + #include +#endif diff --git a/set.hpp b/set.hpp new file mode 100644 index 0000000..2c3a405 --- /dev/null +++ b/set.hpp @@ -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 +#include + +namespace nall { + +template 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 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 { + if(node_t* node = find(root, value)) return node->value; + return nothing; + } + + auto find(const T& value) const -> maybe { + if(node_t* node = find(root, value)) return node->value; + return nothing; + } + + auto insert(const T& value) -> maybe { + unsigned count = size(); + node_t* v = insert(root, value); + root->red = 0; + if(size() == count) return nothing; + return v->value; + } + + template auto insert(const T& value, P&&... p) -> bool { + bool result = insert(value); + insert(forward

(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 auto remove(const T& value, P&&... p) -> bool { + bool result = remove(value); + return remove(forward

(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 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); + } +}; + +} diff --git a/shared-memory.hpp b/shared-memory.hpp new file mode 100644 index 0000000..9d40bca --- /dev/null +++ b/shared-memory.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +#if defined(API_POSIX) + #include +#endif + +#if defined(API_WINDOWS) + #include +#endif diff --git a/shared-pointer.hpp b/shared-pointer.hpp new file mode 100644 index 0000000..af44bde --- /dev/null +++ b/shared-pointer.hpp @@ -0,0 +1,273 @@ +#pragma once + +#include +#include +#include +#include + +namespace nall { + +template struct shared_pointer; + +struct shared_pointer_manager { + void* pointer = nullptr; + function void> deleter; + uint strong = 0; + uint weak = 0; + + shared_pointer_manager(void* pointer) : pointer(pointer) { + } +}; + +template struct shared_pointer; +template struct shared_pointer_weak; + +template +struct shared_pointer { + using type = T; + shared_pointer_manager* manager = nullptr; + + template + struct is_compatible { + static constexpr bool value = is_base_of::value || is_base_of::value; + }; + + shared_pointer() { + } + + shared_pointer(T* source) { + operator=(source); + } + + shared_pointer(T* source, const function& 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>> + shared_pointer(const shared_pointer& source) { + operator=(source); + } + + template>> + shared_pointer(shared_pointer&& source) { + operator=(move(source)); + } + + template>> + shared_pointer(const shared_pointer_weak& source) { + operator=(source); + } + + template>> + shared_pointer(const shared_pointer& 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>> + auto operator=(const shared_pointer& source) -> shared_pointer& { + if((uintptr_t)this != (uintptr_t)&source) { + reset(); + if((bool)source) { + manager = source.manager; + manager->strong++; + } + } + return *this; + } + + template>> + auto operator=(shared_pointer&& source) -> shared_pointer& { + if((uintptr_t)this != (uintptr_t)&source) { + reset(); + manager = source.manager; + source.manager = nullptr; + } + return *this; + } + + template>> + auto operator=(const shared_pointer_weak& 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 + auto operator==(const shared_pointer& source) const -> bool { + return manager == source.manager; + } + + template + auto operator!=(const shared_pointer& 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 + auto cast() -> shared_pointer { + if(auto pointer = dynamic_cast(data())) { + return {*this, pointer}; + } + return {}; + } +}; + +template +struct shared_pointer_weak { + using type = T; + shared_pointer_manager* manager = nullptr; + + shared_pointer_weak() { + } + + shared_pointer_weak(const shared_pointer& source) { + operator=(source); + } + + auto operator=(const shared_pointer& 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 { + return shared_pointer(*this); + } + + auto reset() -> void { + if(manager && --manager->weak == 0) { + if(manager->strong == 0) { + delete manager; + } + } + manager = nullptr; + } +}; + +template +struct shared_pointer_new : shared_pointer { + template + shared_pointer_new(P&&... p) : shared_pointer(new T(forward

(p)...)) { + } +}; + +} diff --git a/smtp.hpp b/smtp.hpp new file mode 100644 index 0000000..f1f60de --- /dev/null +++ b/smtp.hpp @@ -0,0 +1,315 @@ +#pragma once + +#include +#include +#include + +#if !defined(_WIN32) + #include + #include + #include + #include +#else + #include + #include + #include +#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 to; + vector cc; + vector bcc; + struct Attachment { + vector buffer; + string name; + }; + string subject; + string body; + Format format = Format::Plain; + vector 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& 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 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 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& 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 + +} diff --git a/sort.hpp b/sort.hpp new file mode 100644 index 0000000..1f74aa9 --- /dev/null +++ b/sort.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include +#include + +//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 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 auto sort(T list[], unsigned size) -> void { + return sort(list, size, [](const T& l, const T& r) { return l < r; }); +} + +} diff --git a/stdint.hpp b/stdint.hpp new file mode 100644 index 0000000..250b72c --- /dev/null +++ b/stdint.hpp @@ -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 +#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; diff --git a/stream.hpp b/stream.hpp new file mode 100644 index 0000000..3ea0c2c --- /dev/null +++ b/stream.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include diff --git a/stream/auto.hpp b/stream/auto.hpp new file mode 100644 index 0000000..0c5fc65 --- /dev/null +++ b/stream/auto.hpp @@ -0,0 +1,21 @@ +#pragma once + +namespace nall { + +#define autostream(...) (*makestream(__VA_ARGS__)) + +inline auto makestream(const string& path) -> std::unique_ptr { + if(path.iendsWith(".gz")) return std::unique_ptr(new gzipstream(filestream{path})); + if(path.iendsWith(".zip")) return std::unique_ptr(new zipstream(filestream{path})); + return std::unique_ptr(new mmapstream(path)); +} + +inline auto makestream(uint8_t* data, uint size) -> std::unique_ptr { + return std::unique_ptr(new memorystream(data, size)); +} + +inline auto makestream(const uint8_t* data, uint size) -> std::unique_ptr { + return std::unique_ptr(new memorystream(data, size)); +} + +} diff --git a/stream/file.hpp b/stream/file.hpp new file mode 100644 index 0000000..1ce9984 --- /dev/null +++ b/stream/file.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +namespace nall { + +struct filestream : stream { + using stream::read; + using stream::write; + + filestream(const string& filename) { + pfile.open(filename, file::mode::readwrite); + pwritable = pfile.open(); + if(!pwritable) pfile.open(filename, file::mode::read); + } + + filestream(const string& filename, file::mode mode) { + pfile.open(filename, mode); + pwritable = mode == file::mode::write || mode == file::mode::readwrite; + } + + auto seekable() const -> bool { return true; } + auto readable() const -> bool { return true; } + auto writable() const -> bool { return pwritable; } + auto randomaccess() const -> bool { return false; } + + auto size() const -> uint { return pfile.size(); } + auto offset() const -> uint { return pfile.offset(); } + auto seek(uint offset) const -> void { pfile.seek(offset); } + + auto read() const -> uint8_t { return pfile.read(); } + auto write(uint8_t data) const -> void { pfile.write(data); } + +private: + mutable file pfile; + bool pwritable; +}; + +} diff --git a/stream/gzip.hpp b/stream/gzip.hpp new file mode 100644 index 0000000..f4a4e0a --- /dev/null +++ b/stream/gzip.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +namespace nall { + +struct gzipstream : memorystream { + using stream::read; + using stream::write; + + gzipstream(const stream& stream) { + uint size = stream.size(); + auto data = new uint8_t[size]; + stream.read(data, size); + + Decode::GZIP archive; + bool result = archive.decompress(data, size); + delete[] data; + if(!result) return; + + psize = archive.size; + pdata = new uint8_t[psize]; + memcpy(pdata, archive.data, psize); + } + + ~gzipstream() { + if(pdata) delete[] pdata; + } +}; + +} diff --git a/stream/memory.hpp b/stream/memory.hpp new file mode 100644 index 0000000..3f873d4 --- /dev/null +++ b/stream/memory.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include + +namespace nall { + +struct memorystream : stream { + using stream::read; + using stream::write; + + memorystream() = default; + + memorystream(uint8_t* data, uint size) { + pdata = data; + psize = size; + pwritable = true; + } + + memorystream(const uint8_t* data, uint size) { + pdata = (uint8_t*)data; + psize = size; + pwritable = false; + } + + auto seekable() const -> bool { return true; } + auto readable() const -> bool { return true; } + auto writable() const -> bool { return pwritable; } + auto randomaccess() const -> bool { return true; } + + auto data() const -> uint8_t* { return pdata; } + auto size() const -> uint { return psize; } + auto offset() const -> uint { return poffset; } + auto seek(uint offset) const -> void { poffset = offset; } + + auto read() const -> uint8_t { return pdata[poffset++]; } + auto write(uint8_t data) const -> void { pdata[poffset++] = data; } + + auto read(uint offset) const -> uint8_t { return pdata[offset]; } + auto write(uint offset, uint8_t data) const -> void { pdata[offset] = data; } + +protected: + mutable uint8_t* pdata = nullptr; + mutable uint psize = 0; + mutable uint poffset = 0; + mutable bool pwritable = false; +}; + +} diff --git a/stream/mmap.hpp b/stream/mmap.hpp new file mode 100644 index 0000000..8c116f1 --- /dev/null +++ b/stream/mmap.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include + +namespace nall { + +struct mmapstream : stream { + using stream::read; + using stream::write; + + mmapstream(const string& filename) { + pmmap.open(filename, filemap::mode::readwrite); + pwritable = pmmap.open(); + if(!pwritable) pmmap.open(filename, filemap::mode::read); + pdata = pmmap.data(); + poffset = 0; + } + + auto seekable() const -> bool { return true; } + auto readable() const -> bool { return true; } + auto writable() const -> bool { return pwritable; } + auto randomaccess() const -> bool { return true; } + + auto size() const -> uint { return pmmap.size(); } + auto offset() const -> uint { return poffset; } + auto seek(uint offset) const -> void { poffset = offset; } + + auto read() const -> uint8_t { return pdata[poffset++]; } + auto write(uint8_t data) const -> void { pdata[poffset++] = data; } + + auto read(uint offset) const -> uint8_t { return pdata[offset]; } + auto write(uint offset, uint8_t data) const -> void { pdata[offset] = data; } + +private: + mutable filemap pmmap; + mutable uint8_t* pdata = nullptr; + mutable uint poffset = 0; + mutable bool pwritable = false; +}; + +} diff --git a/stream/stream.hpp b/stream/stream.hpp new file mode 100644 index 0000000..a9e1a64 --- /dev/null +++ b/stream/stream.hpp @@ -0,0 +1,98 @@ +#pragma once + +namespace nall { + +struct stream { + stream() = default; + virtual ~stream() = default; + + stream(const stream&) = delete; + auto operator=(const stream&) -> stream& = delete; + + virtual auto seekable() const -> bool = 0; + virtual auto readable() const -> bool = 0; + virtual auto writable() const -> bool = 0; + virtual auto randomaccess() const -> bool = 0; + + virtual auto data() const -> uint8_t* { return nullptr; } + virtual auto size() const -> uint = 0; + virtual auto offset() const -> uint = 0; + virtual auto seek(uint offset) const -> void = 0; + + virtual auto read() const -> uint8_t = 0; + virtual auto write(uint8_t data) const -> void = 0; + + virtual auto read(uint) const -> uint8_t { return 0; } + virtual auto write(uint, uint8_t) const -> void {} + + explicit operator bool() const { + return size(); + } + + auto empty() const -> bool { + return size() == 0; + } + + auto end() const -> bool { + return offset() >= size(); + } + + auto readl(uint length = 1) const -> uintmax { + uintmax data = 0, shift = 0; + while(length--) { data |= read() << shift; shift += 8; } + return data; + } + + auto readm(uint length = 1) const -> uintmax { + uintmax data = 0; + while(length--) data = (data << 8) | read(); + return data; + } + + auto read(uint8_t* data, uint length) const -> void { + while(length--) *data++ = read(); + } + + auto text() const -> string { + string buffer; + buffer.resize(size()); + seek(0); + read((uint8_t*)buffer.get(), size()); + return buffer; + } + + auto writel(uintmax data, uint length = 1) const -> void { + while(length--) { + write(data); + data >>= 8; + } + } + + auto writem(uintmax data, uint length = 1) const -> void { + uintmax shift = 8 * length; + while(length--) { + shift -= 8; + write(data >> shift); + } + } + + auto write(const uint8_t* data, uint length) const -> void { + while(length--) write(*data++); + } + + struct byte { + byte(const stream& s, uint offset) : s(s), offset(offset) {} + operator uint8_t() const { return s.read(offset); } + auto operator=(uint8_t data) -> byte& { s.write(offset, data); return *this; } + + private: + const stream& s; + const uint offset; + }; + + auto operator[](uint offset) const -> byte { + return byte(*this, offset); + } +}; + +} diff --git a/stream/vector.hpp b/stream/vector.hpp new file mode 100644 index 0000000..5f6ddac --- /dev/null +++ b/stream/vector.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +namespace nall { + +struct vectorstream : stream { + using stream::read; + using stream::write; + + vectorstream(vector& memory) : memory(memory), pwritable(true) {} + vectorstream(const vector& memory) : memory((vector&)memory), pwritable(false) {} + + auto seekable() const -> bool { return true; } + auto readable() const -> bool { return true; } + auto writable() const -> bool { return pwritable; } + auto randomaccess() const -> bool { return true; } + + auto data() const -> uint8_t* { return memory.data(); } + auto size() const -> uint { return memory.size(); } + auto offset() const -> uint { return poffset; } + auto seek(uint offset) const -> void { poffset = offset; } + + auto read() const -> uint8_t { return memory[poffset++]; } + auto write(uint8_t data) const -> void { memory[poffset++] = data; } + + auto read(uint offset) const -> uint8_t { return memory[offset]; } + auto write(uint offset, uint8_t data) const -> void { memory[offset] = data; } + +protected: + vector& memory; + mutable uint poffset = 0; + mutable bool pwritable = false; +}; + +} diff --git a/stream/zip.hpp b/stream/zip.hpp new file mode 100644 index 0000000..7f928dc --- /dev/null +++ b/stream/zip.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace nall { + +struct zipstream : memorystream { + using stream::read; + using stream::write; + + zipstream(const stream& stream, const string& filter = "*") { + uint size = stream.size(); + auto data = new uint8_t[size]; + stream.read(data, size); + + Decode::ZIP archive; + if(archive.open(data, size) == false) return; + delete[] data; + + for(auto& file : archive.file) { + if(file.name.match(filter)) { + auto buffer = archive.extract(file); + psize = buffer.size(); + pdata = buffer.release(); + return; + } + } + } + + ~zipstream() { + if(pdata) delete[] pdata; + } +}; + +} diff --git a/string.hpp b/string.hpp new file mode 100644 index 0000000..a774fdc --- /dev/null +++ b/string.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/string/allocator/adaptive.hpp b/string/allocator/adaptive.hpp new file mode 100644 index 0000000..224cc15 --- /dev/null +++ b/string/allocator/adaptive.hpp @@ -0,0 +1,121 @@ +#pragma once + +/***** + adaptive allocator + sizeof(string) == SSO + 8 + + aggressively tries to avoid heap allocations + small strings are stored on the stack + large strings are shared via copy-on-write + + SSO alone is very slow on large strings due to copying + SSO alone is very slightly faster than this allocator on small strings + + COW alone is very slow on small strings due to heap allocations + COW alone is very slightly faster than this allocator on large strings + + adaptive is thus very fast for all string sizes +*****/ + +namespace nall { + +string::string() : _data(nullptr), _capacity(SSO - 1), _size(0) { +} + +auto string::get() -> char* { + if(_capacity < SSO) return _text; + if(*_refs > 1) _copy(); + return _data; +} + +auto string::data() const -> const char* { + if(_capacity < SSO) return _text; + return _data; +} + +auto string::reset() -> type& { + if(_capacity >= SSO && !--*_refs) memory::free(_data); + _data = nullptr; + _capacity = SSO - 1; + _size = 0; + return *this; +} + +auto string::reserve(uint capacity) -> type& { + if(capacity <= _capacity) return *this; + capacity = bit::round(capacity + 1) - 1; + if(_capacity < SSO) { + _capacity = capacity; + _allocate(); + } else if(*_refs > 1) { + _capacity = capacity; + _copy(); + } else { + _capacity = capacity; + _resize(); + } + return *this; +} + +auto string::resize(uint size) -> type& { + reserve(size); + get()[_size = size] = 0; + return *this; +} + +auto string::operator=(const string& source) -> type& { + if(&source == this) return *this; + reset(); + if(source._capacity >= SSO) { + _data = source._data; + _refs = source._refs; + _capacity = source._capacity; + _size = source._size; + ++*_refs; + } else { + memory::copy(_text, source._text, SSO); + _capacity = source._capacity; + _size = source._size; + } + return *this; +} + +auto string::operator=(string&& source) -> type& { + if(&source == this) return *this; + reset(); + memory::copy(this, &source, sizeof(string)); + source._data = nullptr; + source._capacity = SSO - 1; + source._size = 0; + return *this; +} + +//SSO -> COW +auto string::_allocate() -> void { + char _temp[SSO]; + memory::copy(_temp, _text, SSO); + _data = (char*)memory::allocate(_capacity + 1 + sizeof(uint)); + memory::copy(_data, _temp, SSO); + _refs = (uint*)(_data + _capacity + 1); //always aligned by 32 via reserve() + *_refs = 1; +} + +//COW -> Unique +auto string::_copy() -> void { + auto _temp = (char*)memory::allocate(_capacity + 1 + sizeof(uint)); + memory::copy(_temp, _data, _size = min(_capacity, _size)); + _temp[_size] = 0; + --*_refs; + _data = _temp; + _refs = (uint*)(_data + _capacity + 1); + *_refs = 1; +} + +//COW -> Resize +auto string::_resize() -> void { + _data = (char*)memory::resize(_data, _capacity + 1 + sizeof(uint)); + _refs = (uint*)(_data + _capacity + 1); + *_refs = 1; +} + +} diff --git a/string/allocator/copy-on-write.hpp b/string/allocator/copy-on-write.hpp new file mode 100644 index 0000000..36b96cd --- /dev/null +++ b/string/allocator/copy-on-write.hpp @@ -0,0 +1,90 @@ +#pragma once + +namespace nall { + +string::string() : _data(nullptr), _refs(nullptr), _capacity(0), _size(0) { +} + +auto string::get() -> char* { + static char _null[] = ""; + if(!_data) return _null; + if(*_refs > 1) _data = _copy(); //make unique for write operations + return _data; +} + +auto string::data() const -> const char* { + static const char _null[] = ""; + if(!_data) return _null; + return _data; +} + +auto string::reset() -> type& { + if(_data && !--*_refs) { + memory::free(_data); + _data = nullptr; //_refs = nullptr; is unnecessary + } + _capacity = 0; + _size = 0; + return *this; +} + +auto string::reserve(uint capacity) -> type& { + if(capacity > _capacity) { + _capacity = bit::round(max(31u, capacity) + 1) - 1; + _data = _data ? _copy() : _allocate(); + } + return *this; +} + +auto string::resize(uint size) -> type& { + reserve(size); + get()[_size = size] = 0; + return *this; +} + +auto string::operator=(const string& source) -> string& { + if(&source == this) return *this; + reset(); + if(source._data) { + _data = source._data; + _refs = source._refs; + _capacity = source._capacity; + _size = source._size; + ++*_refs; + } + return *this; +} + +auto string::operator=(string&& source) -> string& { + if(&source == this) return *this; + reset(); + _data = source._data; + _refs = source._refs; + _capacity = source._capacity; + _size = source._size; + source._data = nullptr; + source._refs = nullptr; + source._capacity = 0; + source._size = 0; + return *this; +} + +auto string::_allocate() -> char* { + auto _temp = (char*)memory::allocate(_capacity + 1 + sizeof(uint)); + *_temp = 0; + _refs = (uint*)(_temp + _capacity + 1); //this will always be aligned by 32 via reserve() + *_refs = 1; + return _temp; +} + +auto string::_copy() -> char* { + auto _temp = (char*)memory::allocate(_capacity + 1 + sizeof(uint)); + memory::copy(_temp, _data, _size = min(_capacity, _size)); + _temp[_size] = 0; + --*_refs; + _refs = (uint*)(_temp + _capacity + 1); + *_refs = 1; + return _temp; +} + +} diff --git a/string/allocator/small-string-optimization.hpp b/string/allocator/small-string-optimization.hpp new file mode 100644 index 0000000..29e825b --- /dev/null +++ b/string/allocator/small-string-optimization.hpp @@ -0,0 +1,93 @@ +#pragma once + +/* +small string optimization (SSO) allocator +sizeof(string) == 8 + string::SSO + +utilizes a union to store small strings directly into text pointer +bypasses the need to allocate heap memory for small strings +requires extra computations, which can be slower for large strings + +pros: +* potential for in-place resize +* no heap allocation when (capacity < SSO) + +cons: +* added overhead to fetch data() +* pass-by-value requires heap allocation when (capacity >= SSO) + +*/ + +namespace nall { + +string::string() { + _data = nullptr; + _capacity = SSO - 1; + _size = 0; +} + +auto string::get() -> char* { + if(_capacity < SSO) return _text; + return _data; +} + +auto string::data() const -> const char* { + if(_capacity < SSO) return _text; + return _data; +} + +auto string::reset() -> type& { + if(_capacity >= SSO) memory::free(_data); + _data = nullptr; + _capacity = SSO - 1; + _size = 0; + return *this; +} + +auto string::reserve(uint capacity) -> type& { + if(capacity <= _capacity) return *this; + capacity = bit::round(capacity + 1) - 1; + if(_capacity < SSO) { + char _temp[SSO]; + memory::copy(_temp, _text, SSO); + _data = (char*)memory::allocate(_capacity = capacity + 1); + memory::copy(_data, _temp, SSO); + } else { + _data = (char*)memory::resize(_data, _capacity = capacity + 1); + } + return *this; +} + +auto string::resize(uint size) -> type& { + reserve(size); + get()[_size = size] = 0; + return *this; +} + +auto string::operator=(const string& source) -> type& { + if(&source == this) return *this; + reset(); + if(source._capacity >= SSO) { + _data = (char*)memory::allocate(source._capacity + 1); + _capacity = source._capacity; + _size = source._size; + memory::copy(_data, source._data, source._size + 1); + } else { + memory::copy(_text, source._text, SSO); + _capacity = SSO - 1; + _size = source._size; + } + return *this; +} + +auto string::operator=(string&& source) -> type& { + if(&source == this) return *this; + reset(); + memory::copy(this, &source, sizeof(string)); + source._data = nullptr; + source._capacity = SSO - 1; + source._size = 0; + return *this; +} + +} diff --git a/string/allocator/vector.hpp b/string/allocator/vector.hpp new file mode 100644 index 0000000..f43f15c --- /dev/null +++ b/string/allocator/vector.hpp @@ -0,0 +1,82 @@ +#pragma once + +/* +vector allocator +sizeof(string) == 16 (amd64) + +utilizes a raw string pointer +always allocates memory onto the heap when string is not empty + +pros: +* potential for in-place resize +* simplicity + +cons: +* always allocates heap memory on (capacity > 0) +* pass-by-value requires heap allocation + +*/ + +namespace nall { + +auto string::get() -> char* { + if(_capacity == 0) reserve(1); + return _data; +} + +auto string::data() const -> const char* { + if(_capacity == 0) return ""; + return _data; +} + +auto string::reset() -> type& { + if(_data) { memory::free(_data); _data = nullptr; } + _capacity = 0; + _size = 0; + return *this; +} + +auto string::reserve(uint capacity) -> type& { + if(capacity > _capacity) { + _capacity = bit::round(capacity + 1) - 1; + _data = (char*)memory::resize(_data, _capacity + 1); + _data[_capacity] = 0; + } + return *this; +} + +auto string::resize(uint size) -> type& { + reserve(size); + get()[_size = size] = 0; + return *this; +} + +auto string::operator=(const string& source) -> type& { + if(&source == this) return *this; + reset(); + _data = (char*)memory::allocate(source._size + 1); + _capacity = source._size; + _size = source._size; + memory::copy(_data, source.data(), source.size() + 1); + return *this; +} + +auto string::operator=(string&& source) -> type& { + if(&source == this) return *this; + reset(); + _data = source._data; + _capacity = source._capacity; + _size = source._size; + source._data = nullptr; + source._capacity = 0; + source._size = 0; + return *this; +} + +string::string() { + _data = nullptr; + _capacity = 0; + _size = 0; +} + +} diff --git a/string/atoi.hpp b/string/atoi.hpp new file mode 100644 index 0000000..7ee5de5 --- /dev/null +++ b/string/atoi.hpp @@ -0,0 +1,17 @@ +#pragma once + +namespace nall { + +auto string::integer() const -> intmax { + return nall::integer(data()); +} + +auto string::natural() const -> uintmax { + return nall::natural(data()); +} + +auto string::real() const -> double { + return nall::real(data()); +} + +} diff --git a/string/base.hpp b/string/base.hpp new file mode 100644 index 0000000..e72c702 --- /dev/null +++ b/string/base.hpp @@ -0,0 +1,293 @@ +#pragma once + +namespace nall { + +struct string_view; +struct string; +struct format; +struct lstring; + +using rstring = const string_view&; +using cstring = const string&; + +#define NALL_STRING_ALLOCATOR_ADAPTIVE +//#define NALL_STRING_ALLOCATOR_COPY_ON_WRITE +//#define NALL_STRING_ALLOCATOR_SMALL_STRING_OPTIMIZATION +//#define NALL_STRING_ALLOCATOR_VECTOR + +//cast.hpp +template struct stringify; + +//format.hpp +template inline auto print(P&&...) -> void; +inline auto integer(intmax value, long precision = 0, char padchar = '0') -> string; +inline auto natural(uintmax value, long precision = 0, char padchar = '0') -> string; +inline auto hex(uintmax value, long precision = 0, char padchar = '0') -> string; +inline auto octal(uintmax value, long precision = 0, char padchar = '0') -> string; +inline auto binary(uintmax value, long precision = 0, char padchar = '0') -> string; +template inline auto pointer(const T* value, long precision = 0) -> string; +inline auto pointer(uintptr value, long precision = 0) -> string; +inline auto real(long double value) -> string; + +//hash.hpp +inline auto crc16(rstring self) -> string; +inline auto crc32(rstring self) -> string; +inline auto sha256(rstring self) -> string; + +//match.hpp +inline auto tokenize(const char* s, const char* p) -> bool; +inline auto tokenize(lstring& list, const char* s, const char* p) -> bool; + +//path.hpp +inline auto pathname(rstring self) -> string; +inline auto filename(rstring self) -> string; + +inline auto dirname(rstring self) -> string; +inline auto basename(rstring self) -> string; +inline auto prefixname(rstring self) -> string; +inline auto suffixname(rstring self) -> string; + +//platform.hpp +inline auto activepath() -> string; +inline auto realpath(rstring name) -> string; +inline auto programpath() -> string; +inline auto rootpath() -> string; +inline auto userpath() -> string; +inline auto configpath() -> string; +inline auto localpath() -> string; +inline auto sharedpath() -> string; +inline auto temppath() -> string; + +//utility.hpp +inline auto slice(rstring self, int offset = 0, int length = -1) -> string; + +inline auto integer(char* result, intmax value) -> char*; +inline auto natural(char* result, uintmax value) -> char*; +inline auto real(char* str, long double value) -> uint; + +struct string { + using type = string; + struct exception_out_of_bounds{}; + +protected: + #if defined(NALL_STRING_ALLOCATOR_ADAPTIVE) + enum : uint { SSO = 24 }; + union { + struct { //copy-on-write + char* _data; + uint* _refs; + }; + struct { //small-string-optimization + char _text[SSO]; + }; + }; + inline auto _allocate() -> void; + inline auto _copy() -> void; + inline auto _resize() -> void; + #endif + + #if defined(NALL_STRING_ALLOCATOR_COPY_ON_WRITE) + char* _data; + mutable uint* _refs; + inline auto _allocate() -> char*; + inline auto _copy() -> char*; + #endif + + #if defined(NALL_STRING_ALLOCATOR_SMALL_STRING_OPTIMIZATION) + enum : uint { SSO = 24 }; + union { + char* _data; + char _text[SSO]; + }; + #endif + + #if defined(NALL_STRING_ALLOCATOR_VECTOR) + char* _data; + #endif + + uint _capacity; + uint _size; + +public: + inline string(); + inline auto get() -> char*; + inline auto data() const -> const char*; + inline auto reset() -> type&; + inline auto reserve(uint) -> type&; + inline auto resize(uint) -> type&; + inline auto operator=(const string&) -> type&; + inline auto operator=(string&&) -> type&; + + template string(T&& s, P&&... p) : string() { append(forward(s), forward

(p)...); } + ~string() { reset(); } + + explicit operator bool() const { return _size; } + operator const uint8_t*() const { return (const uint8_t*)data(); } + operator const char*() const { return (const char*)data(); } + + auto binary() const -> const uint8_t* { return (const uint8_t*)data(); } + auto size() const -> uint { return _size; } + auto capacity() const -> uint { return _capacity; } + + auto operator==(const string& source) const -> bool { return size() == source.size() && memory::compare(data(), source.data(), size()) == 0; } + auto operator!=(const string& source) const -> bool { return size() != source.size() || memory::compare(data(), source.data(), size()) != 0; } + + auto operator==(const char* source) const -> bool { return strcmp(data(), source) == 0; } + auto operator!=(const char* source) const -> bool { return strcmp(data(), source) != 0; } + + auto operator==(rstring source) const -> bool { return compare(source) == 0; } + auto operator!=(rstring source) const -> bool { return compare(source) != 0; } + auto operator< (rstring source) const -> bool { return compare(source) < 0; } + auto operator<=(rstring source) const -> bool { return compare(source) <= 0; } + auto operator> (rstring source) const -> bool { return compare(source) > 0; } + auto operator>=(rstring source) const -> bool { return compare(source) >= 0; } + + string(const string& source) : string() { operator=(source); } + string(string&& source) : string() { operator=(move(source)); } + + auto begin() -> char* { return &get()[0]; } + auto end() -> char* { return &get()[size()]; } + auto begin() const -> const char* { return &data()[0]; } + auto end() const -> const char* { return &data()[size()]; } + + //atoi.hpp + inline auto integer() const -> intmax; + inline auto natural() const -> uintmax; + inline auto real() const -> double; + + //core.hpp + inline auto operator[](int) const -> const char&; + template inline auto assign(P&&...) -> type&; + template inline auto append(const T&, P&&...) -> type&; + template inline auto append(const nall::format&, P&&...) -> type&; + inline auto append() -> type&; + template inline auto _append(const stringify&) -> string&; + inline auto empty() const -> bool; + inline auto length() const -> uint; + + //datetime.hpp + inline static auto date(time_t = 0) -> string; + inline static auto time(time_t = 0) -> string; + inline static auto datetime(time_t = 0) -> string; + + //find.hpp + template inline auto _find(int, rstring) const -> maybe; + + inline auto find(rstring source) const -> maybe; + inline auto ifind(rstring source) const -> maybe; + inline auto qfind(rstring source) const -> maybe; + inline auto iqfind(rstring source) const -> maybe; + + inline auto findFrom(int offset, rstring source) const -> maybe; + inline auto ifindFrom(int offset, rstring source) const -> maybe; + + //format.hpp + inline auto format(const nall::format& params) -> type&; + + //compare.hpp + template inline static auto _compare(const char*, uint, const char*, uint) -> signed; + + inline static auto compare(rstring, rstring) -> signed; + inline static auto icompare(rstring, rstring) -> signed; + + inline auto compare(rstring source) const -> signed; + inline auto icompare(rstring source) const -> signed; + + inline auto equals(rstring source) const -> bool; + inline auto iequals(rstring source) const -> bool; + + inline auto beginsWith(rstring source) const -> bool; + inline auto ibeginsWith(rstring source) const -> bool; + + inline auto endsWith(rstring source) const -> bool; + inline auto iendsWith(rstring source) const -> bool; + + //convert.hpp + inline auto downcase() -> type&; + inline auto upcase() -> type&; + + inline auto qdowncase() -> type&; + inline auto qupcase() -> type&; + + inline auto transform(rstring from, rstring to) -> type&; + + //match.hpp + inline auto match(rstring source) const -> bool; + inline auto imatch(rstring source) const -> bool; + + //replace.hpp + template inline auto _replace(rstring, rstring, long) -> type&; + inline auto replace(rstring from, rstring to, long limit = LONG_MAX) -> type&; + inline auto ireplace(rstring from, rstring to, long limit = LONG_MAX) -> type&; + inline auto qreplace(rstring from, rstring to, long limit = LONG_MAX) -> type&; + inline auto iqreplace(rstring from, rstring to, long limit = LONG_MAX) -> type&; + + //split.hpp + inline auto split(rstring key, long limit = LONG_MAX) const -> lstring; + inline auto isplit(rstring key, long limit = LONG_MAX) const -> lstring; + inline auto qsplit(rstring key, long limit = LONG_MAX) const -> lstring; + inline auto iqsplit(rstring key, long limit = LONG_MAX) const -> lstring; + + //trim.hpp + inline auto trim(rstring lhs, rstring rhs, long limit = LONG_MAX) -> type&; + inline auto ltrim(rstring lhs, long limit = LONG_MAX) -> type&; + inline auto rtrim(rstring rhs, long limit = LONG_MAX) -> type&; + + inline auto itrim(rstring lhs, rstring rhs, long limit = LONG_MAX) -> type&; + inline auto iltrim(rstring lhs, long limit = LONG_MAX) -> type&; + inline auto irtrim(rstring rhs, long limit = LONG_MAX) -> type&; + + inline auto strip() -> type&; + inline auto lstrip() -> type&; + inline auto rstrip() -> type&; + + //utility.hpp + inline static auto read(rstring filename) -> string; + inline static auto repeat(rstring pattern, uint times) -> string; + inline auto fill(char fill = ' ') -> type&; + inline auto hash() const -> uint; + inline auto remove(uint offset, uint length) -> type&; + inline auto reverse() -> type&; + inline auto size(int length, char fill = ' ') -> type&; +}; + +struct lstring : vector { + using type = lstring; + + lstring(const lstring& source) { vector::operator=(source); } + lstring(lstring& source) { vector::operator=(source); } + lstring(lstring&& source) { vector::operator=(move(source)); } + template lstring(P&&... p) { append(forward

(p)...); } + + //list.hpp + inline auto operator==(const lstring&) const -> bool; + inline auto operator!=(const lstring&) const -> bool; + + inline auto operator=(const lstring& source) -> type& { return vector::operator=(source), *this; } + inline auto operator=(lstring& source) -> type& { return vector::operator=(source), *this; } + inline auto operator=(lstring&& source) -> type& { return vector::operator=(move(source)), *this; } + + inline auto isort() -> type&; + + template inline auto append(const string&, P&&...) -> type&; + inline auto append() -> type&; + + inline auto find(rstring source) const -> maybe; + inline auto ifind(rstring source) const -> maybe; + inline auto match(rstring pattern) const -> lstring; + inline auto merge(rstring separator) const -> string; + inline auto strip() -> type&; + + //split.hpp + template inline auto _split(rstring, rstring, long) -> lstring&; +}; + +struct format : vector { + using type = format; + + template format(P&&... p) { reserve(sizeof...(p)); append(forward

(p)...); } + template inline auto append(const T&, P&&... p) -> type&; + inline auto append() -> type&; +}; + +} diff --git a/string/cast.hpp b/string/cast.hpp new file mode 100644 index 0000000..2574eca --- /dev/null +++ b/string/cast.hpp @@ -0,0 +1,225 @@ +#pragma once + +//convert any (supported) type to a const char* without constructing a new nall::string +//this is used inside string{...} to build nall::string values + +namespace nall { + +//booleans + +template<> struct stringify { + stringify(bool value) : _value(value) {} + auto data() const -> const char* { return _value ? "true" : "false"; } + auto size() const -> unsigned { return _value ? 4 : 5; } + bool _value; +}; + +template<> struct stringify { + stringify(bool value) : _value(value) {} + auto data() const -> const char* { return _value ? "true" : "false"; } + auto size() const -> uint { return _value ? 4 : 5; } + bool _value; +}; + +//characters + +template<> struct stringify { + stringify(char source) { _data[0] = source; _data[1] = 0; } + auto data() const -> const char* { return _data; } + auto size() const -> unsigned { return 1; } + char _data[2]; +}; + +//signed integers + +template<> struct stringify { + stringify(signed char source) { integer(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> unsigned { return strlen(_data); } + char _data[2 + sizeof(signed char) * 3]; +}; + +template<> struct stringify { + stringify(signed short source) { integer(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> unsigned { return strlen(_data); } + char _data[2 + sizeof(signed short) * 3]; +}; + +template<> struct stringify { + stringify(signed int source) { integer(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> unsigned { return strlen(_data); } + char _data[2 + sizeof(signed int) * 3]; +}; + +template<> struct stringify { + stringify(signed long source) { integer(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> unsigned { return strlen(_data); } + char _data[2 + sizeof(signed long) * 3]; +}; + +template<> struct stringify { + stringify(signed long long source) { integer(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> unsigned { return strlen(_data); } + char _data[2 + sizeof(signed long long) * 3]; +}; + +template struct stringify> { + stringify(Integer source) { integer(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[2 + sizeof(int64_t) * 3]; +}; + +//unsigned integers + +template<> struct stringify { + stringify(unsigned char source) { natural(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> unsigned { return strlen(_data); } + char _data[1 + sizeof(unsigned char) * 3]; +}; + +template<> struct stringify { + stringify(unsigned short source) { natural(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> unsigned { return strlen(_data); } + char _data[1 + sizeof(unsigned short) * 3]; +}; + +template<> struct stringify { + stringify(unsigned int source) { natural(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> unsigned { return strlen(_data); } + char _data[1 + sizeof(unsigned int) * 3]; +}; + +template<> struct stringify { + stringify(unsigned long source) { natural(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> unsigned { return strlen(_data); } + char _data[1 + sizeof(unsigned long) * 3]; +}; + +template<> struct stringify { + stringify(unsigned long long source) { natural(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> unsigned { return strlen(_data); } + char _data[1 + sizeof(unsigned long long) * 3]; +}; + +template struct stringify> { + stringify(Natural source) { natural(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[1 + sizeof(uint64_t) * 3]; +}; + +//floating-point + +template<> struct stringify { + stringify(float source) { real(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> unsigned { return strlen(_data); } + char _data[256]; +}; + +template<> struct stringify { + stringify(double source) { real(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> unsigned { return strlen(_data); } + char _data[256]; +}; + +template<> struct stringify { + stringify(long double source) { real(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> unsigned { return strlen(_data); } + char _data[256]; +}; + +template struct stringify> { + stringify(Real source) { real(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[256]; +}; + +//arrays + +template<> struct stringify> { + stringify(vector source) { + _text.resize(source.size()); + memory::copy(_text.data(), source.data(), source.size()); + } + auto data() const -> const char* { return _text.data(); } + auto size() const -> unsigned { return _text.size(); } + vector _text; +}; + +template<> struct stringify&> { + stringify(const vector& source) { + _text.resize(source.size()); + memory::copy(_text.data(), source.data(), source.size()); + } + auto data() const -> const char* { return _text.data(); } + auto size() const -> unsigned { return _text.size(); } + vector _text; +}; + +//char arrays + +template<> struct stringify { + stringify(char* source) : _data(source ? source : "") {} + auto data() const -> const char* { return _data; } + auto size() const -> unsigned { return strlen(_data); } + const char* _data; +}; + +template<> struct stringify { + stringify(const char* source) : _data(source ? source : "") {} + auto data() const -> const char* { return _data; } + auto size() const -> unsigned { return strlen(_data); } + const char* _data; +}; + +//strings + +template<> struct stringify { + stringify(const string& source) : _text(source) {} + auto data() const -> const char* { return _text.data(); } + auto size() const -> unsigned { return _text.size(); } + const string& _text; +}; + +template<> struct stringify { + stringify(const string& source) : _text(source) {} + auto data() const -> const char* { return _text.data(); } + auto size() const -> unsigned { return _text.size(); } + const string& _text; +}; + +template<> struct stringify { + stringify(const string_view& source) : _view(source) {} + auto data() const -> const char* { return _view.data(); } + auto size() const -> unsigned { return _view.size(); } + const string_view& _view; +}; + +template<> struct stringify { + stringify(const string_view& source) : _view(source) {} + auto data() const -> const char* { return _view.data(); } + auto size() const -> unsigned { return _view.size(); } + const string_view& _view; +}; + +// + +template auto make_string(T value) -> stringify { + return stringify(forward(value)); +} + +} diff --git a/string/compare.hpp b/string/compare.hpp new file mode 100644 index 0000000..0fb933d --- /dev/null +++ b/string/compare.hpp @@ -0,0 +1,58 @@ +#pragma once + +namespace nall { + +template +auto string::_compare(const char* target, uint capacity, const char* source, uint size) -> signed { + if(Insensitive) return memory::icompare(target, capacity, source, size); + return memory::compare(target, capacity, source, size); +} + +//size() + 1 includes null-terminator; required to properly compare strings of differing lengths +auto string::compare(rstring x, rstring y) -> int { + return memory::compare(x.data(), x.size() + 1, y.data(), y.size() + 1); +} + +auto string::icompare(rstring x, rstring y) -> int { + return memory::icompare(x.data(), x.size() + 1, y.data(), y.size() + 1); +} + +auto string::compare(rstring source) const -> int { + return memory::compare(data(), size() + 1, source.data(), source.size() + 1); +} + +auto string::icompare(rstring source) const -> int { + return memory::icompare(data(), size() + 1, source.data(), source.size() + 1); +} + +auto string::equals(rstring source) const -> bool { + if(size() != source.size()) return false; + return memory::compare(data(), source.data(), source.size()) == 0; +} + +auto string::iequals(rstring source) const -> bool { + if(size() != source.size()) return false; + return memory::icompare(data(), source.data(), source.size()) == 0; +} + +auto string::beginsWith(rstring source) const -> bool { + if(source.size() > size()) return false; + return memory::compare(data(), source.data(), source.size()) == 0; +} + +auto string::ibeginsWith(rstring source) const -> bool { + if(source.size() > size()) return false; + return memory::icompare(data(), source.data(), source.size()) == 0; +} + +auto string::endsWith(rstring source) const -> bool { + if(source.size() > size()) return false; + return memory::compare(data() + size() - source.size(), source.data(), source.size()) == 0; +} + +auto string::iendsWith(rstring source) const -> bool { + if(source.size() > size()) return false; + return memory::icompare(data() + size() - source.size(), source.data(), source.size()) == 0; +} + +} diff --git a/string/convert.hpp b/string/convert.hpp new file mode 100644 index 0000000..2b177f4 --- /dev/null +++ b/string/convert.hpp @@ -0,0 +1,53 @@ +#pragma once + +namespace nall { + +auto string::downcase() -> string& { + char* p = get(); + for(unsigned n = 0; n < size(); n++) { + if(p[n] >= 'A' && p[n] <= 'Z') p[n] += 0x20; + } + return *this; +} + +auto string::qdowncase() -> string& { + char* p = get(); + for(unsigned n = 0, quoted = 0; n < size(); n++) { + if(p[n] == '\"') quoted ^= 1; + if(!quoted && p[n] >= 'A' && p[n] <= 'Z') p[n] += 0x20; + } + return *this; +} + +auto string::upcase() -> string& { + char* p = get(); + for(unsigned n = 0; n < size(); n++) { + if(p[n] >= 'a' && p[n] <= 'z') p[n] -= 0x20; + } + return *this; +} + +auto string::qupcase() -> string& { + char* p = get(); + for(unsigned n = 0, quoted = 0; n < size(); n++) { + if(p[n] == '\"') quoted ^= 1; + if(!quoted && p[n] >= 'a' && p[n] <= 'z') p[n] -= 0x20; + } + return *this; +} + +auto string::transform(rstring from, rstring to) -> string& { + if(from.size() != to.size() || from.size() == 0) return *this; //patterns must be the same length + char* p = get(); + for(unsigned n = 0; n < size(); n++) { + for(unsigned s = 0; s < from.size(); s++) { + if(p[n] == from[s]) { + p[n] = to[s]; + break; + } + } + } + return *this; +} + +} diff --git a/string/core.hpp b/string/core.hpp new file mode 100644 index 0000000..da8c166 --- /dev/null +++ b/string/core.hpp @@ -0,0 +1,56 @@ +#pragma once + +//only allocators may access _data or modify _size and _capacity +//all other functions must use data(), size(), capacity() + +#if defined(NALL_STRING_ALLOCATOR_ADAPTIVE) + #include +#elif defined(NALL_STRING_ALLOCATOR_COPY_ON_WRITE) + #include +#elif defined(NALL_STRING_ALLOCATOR_SMALL_STRING_OPTIMIZATION) + #include +#elif defined(NALL_STRING_ALLOCATOR_VECTOR) + #include +#endif + +namespace nall { + +auto string::operator[](int position) const -> const char& { + if(position > size() + 1) throw exception_out_of_bounds{}; + return data()[position]; +} + +template auto string::assign(P&&... p) -> string& { + resize(0); + return append(forward

(p)...); +} + +template auto string::append(const T& value, P&&... p) -> string& { + _append(make_string(value)); + return append(forward

(p)...); +} + +template auto string::append(const nall::format& value, P&&... p) -> string& { + format(value); + return append(forward

(p)...); +} + +auto string::append() -> string& { + return *this; +} + +template auto string::_append(const stringify& source) -> string& { + resize(size() + source.size()); + memory::copy(get() + size() - source.size(), source.data(), source.size()); + return *this; +} + +auto string::empty() const -> bool { + return size() == 0; +} + +auto string::length() const -> uint { + return strlen(data()); +} + +} diff --git a/string/datetime.hpp b/string/datetime.hpp new file mode 100644 index 0000000..8469699 --- /dev/null +++ b/string/datetime.hpp @@ -0,0 +1,30 @@ +#pragma once + +namespace nall { + +auto string::date(time_t timestamp) -> string { + if(timestamp == 0) timestamp = ::time(nullptr); + tm* info = localtime(×tamp); + return { + nall::natural(1900 + info->tm_year, 4L), "-", + nall::natural(1 + info->tm_mon, 2L), "-", + nall::natural(info->tm_mday, 2L) + }; +} + +auto string::time(time_t timestamp) -> string { + if(timestamp == 0) timestamp = ::time(nullptr); + tm* info = localtime(×tamp); + return { + nall::natural(info->tm_hour, 2L), ":", + nall::natural(info->tm_min, 2L), ":", + nall::natural(info->tm_sec, 2L) + }; +} + +auto string::datetime(time_t timestamp) -> string { + if(timestamp == 0) timestamp = ::time(nullptr); + return {string::date(timestamp), " ", string::time(timestamp)}; +} + +} diff --git a/string/eval/evaluator.hpp b/string/eval/evaluator.hpp new file mode 100644 index 0000000..60fc2d1 --- /dev/null +++ b/string/eval/evaluator.hpp @@ -0,0 +1,146 @@ +#pragma once + +namespace nall { namespace Eval { + +inline auto evaluateExpression(Node* node) -> string { + #define p(n) evaluateExpression(node->link[n]) + switch(node->type) { + case Node::Type::Null: return "Null"; + case Node::Type::Literal: return {"Literal:", node->literal}; + case Node::Type::Function: return {"Function(0:", p(0), ", 1:", p(1), ")"}; + case Node::Type::Subscript: return {"Subscript(0:", p(0), ", 1:", p(1), ")"}; + case Node::Type::Member: return {"Member(0:", p(0), ", 1:", p(1), ")"}; + case Node::Type::SuffixIncrement: return {"SuffixIncrement(0:", p(0), ")"}; + case Node::Type::SuffixDecrement: return {"SuffixDecrement(0:", p(0), ")"}; + case Node::Type::Reference: return {"Reference(0:", p(0), ")"}; + case Node::Type::Dereference: return {"Dereference(0:", p(0), ")"}; + case Node::Type::BitwiseNot: return {"Complement(0:", p(0), ")"}; + case Node::Type::PrefixIncrement: return {"PrefixIncrement(0:", p(0), ")"}; + case Node::Type::PrefixDecrement: return {"PrefixDecrement(0:", p(0), ")"}; + case Node::Type::Add: return {"Add(0:", p(0), ", 1:", p(1), ")"}; + case Node::Type::Multiply: return {"Multiply(0:", p(0), ", 1:", p(1), ")"}; + case Node::Type::Concatenate: return {"Concatenate(0:", p(0), ", ", p(1), ")"}; + case Node::Type::Coalesce: return {"Coalesce(0:", p(0), ", ", p(1), ")"}; + case Node::Type::Condition: return {"Condition(0:", p(0), ", ", p(1), ", ", p(2), ")"}; + case Node::Type::Assign: return {"Assign(0:", p(0), ", ", p(1), ")"}; + case Node::Type::Separator: { + string result = "Separator("; + for(auto& link : node->link) { + result.append(evaluateExpression(link), ", "); + } + return result.rtrim(", ", 1L).append(")"); + } + } + #undef p + + throw "invalid operator"; +} + +inline auto evaluateInteger(Node* node) -> int64_t { + if(node->type == Node::Type::Literal) return nall::integer(node->literal); + + #define p(n) evaluateInteger(node->link[n]) + switch(node->type) { + case Node::Type::SuffixIncrement: return p(0); + case Node::Type::SuffixDecrement: return p(0); + case Node::Type::LogicalNot: return !p(0); + case Node::Type::BitwiseNot: return ~p(0); + case Node::Type::Positive: return +p(0); + case Node::Type::Negative: return -p(0); + case Node::Type::PrefixIncrement: return p(0) + 1; + case Node::Type::PrefixDecrement: return p(0) - 1; + case Node::Type::Multiply: return p(0) * p(1); + case Node::Type::Divide: return p(0) / p(1); + case Node::Type::Modulo: return p(0) % p(1); + case Node::Type::Add: return p(0) + p(1); + case Node::Type::Subtract: return p(0) - p(1); + case Node::Type::ShiftLeft: return p(0) << p(1); + case Node::Type::ShiftRight: return p(0) >> p(1); + case Node::Type::BitwiseAnd: return p(0) & p(1); + case Node::Type::BitwiseOr: return p(0) | p(1); + case Node::Type::BitwiseXor: return p(0) ^ p(1); + case Node::Type::Equal: return p(0) == p(1); + case Node::Type::NotEqual: return p(0) != p(1); + case Node::Type::LessThanEqual: return p(0) <= p(1); + case Node::Type::GreaterThanEqual: return p(0) >= p(1); + case Node::Type::LessThan: return p(0) < p(1); + case Node::Type::GreaterThan: return p(0) > p(1); + case Node::Type::LogicalAnd: return p(0) && p(1); + case Node::Type::LogicalOr: return p(0) || p(1); + case Node::Type::Condition: return p(0) ? p(1) : p(2); + case Node::Type::Assign: return p(1); + case Node::Type::AssignMultiply: return p(0) * p(1); + case Node::Type::AssignDivide: return p(0) / p(1); + case Node::Type::AssignModulo: return p(0) % p(1); + case Node::Type::AssignAdd: return p(0) + p(1); + case Node::Type::AssignSubtract: return p(0) - p(1); + case Node::Type::AssignShiftLeft: return p(0) << p(1); + case Node::Type::AssignShiftRight: return p(0) >> p(1); + case Node::Type::AssignBitwiseAnd: return p(0) & p(1); + case Node::Type::AssignBitwiseOr: return p(0) | p(1); + case Node::Type::AssignBitwiseXor: return p(0) ^ p(1); + } + #undef p + + throw "invalid operator"; +} + +inline auto integer(const string& expression) -> maybe { + try { + auto tree = new Node; + const char* p = expression; + parse(tree, p, 0); + auto result = evaluateInteger(tree); + delete tree; + return result; + } catch(const char*) { + return nothing; + } +} + +inline auto evaluateReal(Node* node) -> long double { + if(node->type == Node::Type::Literal) return nall::real(node->literal); + + #define p(n) evaluateReal(node->link[n]) + switch(node->type) { + case Node::Type::LogicalNot: return !p(0); + case Node::Type::Positive: return +p(0); + case Node::Type::Negative: return -p(0); + case Node::Type::Multiply: return p(0) * p(1); + case Node::Type::Divide: return p(0) / p(1); + case Node::Type::Add: return p(0) + p(1); + case Node::Type::Subtract: return p(0) - p(1); + case Node::Type::Equal: return p(0) == p(1); + case Node::Type::NotEqual: return p(0) != p(1); + case Node::Type::LessThanEqual: return p(0) <= p(1); + case Node::Type::GreaterThanEqual: return p(0) >= p(1); + case Node::Type::LessThan: return p(0) < p(1); + case Node::Type::GreaterThan: return p(0) > p(1); + case Node::Type::LogicalAnd: return p(0) && p(1); + case Node::Type::LogicalOr: return p(0) || p(1); + case Node::Type::Condition: return p(0) ? p(1) : p(2); + case Node::Type::Assign: return p(1); + case Node::Type::AssignMultiply: return p(0) * p(1); + case Node::Type::AssignDivide: return p(0) / p(1); + case Node::Type::AssignAdd: return p(0) + p(1); + case Node::Type::AssignSubtract: return p(0) - p(1); + } + #undef p + + throw "invalid operator"; +} + +inline auto real(const string& expression) -> maybe { + try { + auto tree = new Node; + const char* p = expression; + parse(tree, p, 0); + auto result = evaluateReal(tree); + delete tree; + return result; + } catch(const char*) { + return nothing; + } +} + +}} diff --git a/string/eval/literal.hpp b/string/eval/literal.hpp new file mode 100644 index 0000000..f56078a --- /dev/null +++ b/string/eval/literal.hpp @@ -0,0 +1,99 @@ +#pragma once + +namespace nall { namespace Eval { + +inline auto isLiteral(const char*& s) -> bool { + char n = s[0]; + return (n >= 'A' && n <= 'Z') + || (n >= 'a' && n <= 'z') + || (n >= '0' && n <= '9') + || (n == '%' || n == '$' || n == '_' || n == '.') + || (n == '\'' || n == '\"'); +} + +inline auto literalNumber(const char*& s) -> string { + const char* p = s; + + //binary + if(p[0] == '%' || (p[0] == '0' && p[1] == 'b')) { + unsigned prefix = 1 + (p[0] == '0'); + p += prefix; + while(p[0] == '\'' || p[0] == '0' || p[0] == '1') p++; + if(p - s <= prefix) throw "invalid binary literal"; + string result = slice(s, 0, p - s); + s = p; + return result; + } + + //octal + if(p[0] == '0' && p[1] == 'o') { + unsigned prefix = 1 + (p[0] == '0'); + p += prefix; + while(p[0] == '\'' || (p[0] >= '0' && p[0] <= '7')) p++; + if(p - s <= prefix) throw "invalid octal literal"; + string result = slice(s, 0, p - s); + s = p; + return result; + } + + //hex + if(p[0] == '$' || (p[0] == '0' && p[1] == 'x')) { + unsigned prefix = 1 + (p[0] == '0'); + p += prefix; + while(p[0] == '\'' || (p[0] >= '0' && p[0] <= '9') || (p[0] >= 'A' && p[0] <= 'F') || (p[0] >= 'a' && p[0] <= 'f')) p++; + if(p - s <= prefix) throw "invalid hex literal"; + string result = slice(s, 0, p - s); + s = p; + return result; + } + + //decimal + while(p[0] == '\'' || (p[0] >= '0' && p[0] <= '9')) p++; + if(p[0] != '.') { + string result = slice(s, 0, p - s); + s = p; + return result; + } + + //floating-point + p++; + while(p[0] == '\'' || (p[0] >= '0' && p[0] <= '9')) p++; + string result = slice(s, 0, p - s); + s = p; + return result; +} + +inline auto literalString(const char*& s) -> string { + const char* p = s; + char escape = *p++; + + while(p[0] && p[0] != escape) p++; + if(*p++ != escape) throw "unclosed string literal"; + + string result = slice(s, 0, p - s); + s = p; + return result; +} + +inline auto literalVariable(const char*& s) -> string { + const char* p = s; + + while(p[0] == '_' || p[0] == '.' || (p[0] >= 'A' && p[0] <= 'Z') || (p[0] >= 'a' && p[0] <= 'z') || (p[0] >= '0' && p[0] <= '9')) p++; + + string result = slice(s, 0, p - s); + s = p; + return result; +} + +inline auto literal(const char*& s) -> string { + const char* p = s; + + if(p[0] >= '0' && p[0] <= '9') return literalNumber(s); + if(p[0] == '%' || p[0] == '$') return literalNumber(s); + if(p[0] == '\'' || p[0] == '\"') return literalString(s); + if(p[0] == '_' || p[0] == '.' || (p[0] >= 'A' && p[0] <= 'Z') || (p[0] >= 'a' && p[0] <= 'z')) return literalVariable(s); + + throw "invalid literal"; +} + +}} diff --git a/string/eval/node.hpp b/string/eval/node.hpp new file mode 100644 index 0000000..54f7e7a --- /dev/null +++ b/string/eval/node.hpp @@ -0,0 +1,37 @@ +#pragma once + +namespace nall { namespace Eval { + +struct Node { + enum class Type : uint { + Null, + Literal, + Function, Subscript, Member, SuffixIncrement, SuffixDecrement, + Reference, Dereference, LogicalNot, BitwiseNot, Positive, Negative, PrefixIncrement, PrefixDecrement, + Multiply, Divide, Modulo, + Add, Subtract, + RotateLeft, RotateRight, ShiftLeft, ShiftRight, + BitwiseAnd, BitwiseOr, BitwiseXor, + Concatenate, + Equal, NotEqual, LessThanEqual, GreaterThanEqual, LessThan, GreaterThan, + LogicalAnd, LogicalOr, + Coalesce, Condition, + Assign, Create, //all assignment operators have the same precedence + AssignMultiply, AssignDivide, AssignModulo, + AssignAdd, AssignSubtract, + AssignRotateLeft, AssignRotateRight, AssignShiftLeft, AssignShiftRight, + AssignBitwiseAnd, AssignBitwiseOr, AssignBitwiseXor, + AssignConcatenate, + Separator, + }; + + Type type; + string literal; + vector link; + + Node() : type(Type::Null) {} + Node(Type type) : type(type) {} + ~Node() { for(auto& node : link) delete node; } +}; + +}} diff --git a/string/eval/parser.hpp b/string/eval/parser.hpp new file mode 100644 index 0000000..1cae0d9 --- /dev/null +++ b/string/eval/parser.hpp @@ -0,0 +1,164 @@ +#pragma once + +namespace nall { namespace Eval { + +inline auto whitespace(char n) -> bool { + return n == ' ' || n == '\t' || n == '\r' || n == '\n'; +} + +inline auto parse(Node*& node, const char*& s, uint depth) -> void { + auto unaryPrefix = [&](Node::Type type, uint seek, uint depth) { + auto parent = new Node(type); + parse(parent->link(0) = new Node, s += seek, depth); + node = parent; + }; + + auto unarySuffix = [&](Node::Type type, uint seek, uint depth) { + auto parent = new Node(type); + parent->link(0) = node; + parse(parent, s += seek, depth); + node = parent; + }; + + auto binary = [&](Node::Type type, uint seek, uint depth) { + auto parent = new Node(type); + parent->link(0) = node; + parse(parent->link(1) = new Node, s += seek, depth); + node = parent; + }; + + auto ternary = [&](Node::Type type, uint seek, uint depth) { + auto parent = new Node(type); + parent->link(0) = node; + parse(parent->link(1) = new Node, s += seek, depth); + if(s[0] != ':') throw "mismatched ternary"; + parse(parent->link(2) = new Node, s += seek, depth); + node = parent; + }; + + auto separator = [&](Node::Type type, uint seek, uint depth) { + if(node->type != Node::Type::Separator) return binary(type, seek, depth); + uint n = node->link.size(); + parse(node->link(n) = new Node, s += seek, depth); + }; + + while(whitespace(s[0])) s++; + if(!s[0]) return; + + if(s[0] == '(' && node->link.empty()) { + parse(node, s += 1, 1); + if(*s++ != ')') throw "mismatched group"; + } + + if(isLiteral(s)) { + node->type = Node::Type::Literal; + node->literal = literal(s); + } + + #define p() (node->literal.empty() && node->link.empty()) + while(true) { + while(whitespace(s[0])) s++; + if(!s[0]) return; + + if(depth >= 13) break; + if(s[0] == '(' && !p()) { + binary(Node::Type::Function, 1, 1); + if(*s++ != ')') throw "mismatched function"; + continue; + } + if(s[0] == '[') { + binary(Node::Type::Subscript, 1, 1); + if(*s++ != ']') throw "mismatched subscript"; + continue; + } + if(s[0] == '.') { binary(Node::Type::Member, 1, 13); continue; } + if(s[0] == '+' && s[1] == '+' && !p()) { unarySuffix(Node::Type::SuffixIncrement, 2, 13); continue; } + if(s[0] == '-' && s[1] == '-' && !p()) { unarySuffix(Node::Type::SuffixDecrement, 2, 13); continue; } + + if(s[0] == '&' && p()) { unaryPrefix(Node::Type::Reference, 1, 12); continue; } + if(s[0] == '*' && p()) { unaryPrefix(Node::Type::Dereference, 1, 12); continue; } + if(s[0] == '!' && p()) { unaryPrefix(Node::Type::LogicalNot, 1, 12); continue; } + if(s[0] == '~' && p()) { unaryPrefix(Node::Type::BitwiseNot, 1, 12); continue; } + if(s[0] == '+' && s[1] != '+' && p()) { unaryPrefix(Node::Type::Positive, 1, 12); continue; } + if(s[0] == '-' && s[1] != '-' && p()) { unaryPrefix(Node::Type::Negative, 1, 12); continue; } + if(s[0] == '+' && s[1] == '+' && p()) { unaryPrefix(Node::Type::PrefixIncrement, 2, 12); continue; } + if(s[0] == '-' && s[1] == '-' && p()) { unaryPrefix(Node::Type::PrefixDecrement, 2, 12); continue; } + if(depth >= 12) break; + + if(depth >= 11) break; + if(s[0] == '*' && s[1] != '=') { binary(Node::Type::Multiply, 1, 11); continue; } + if(s[0] == '/' && s[1] != '=') { binary(Node::Type::Divide, 1, 11); continue; } + if(s[0] == '%' && s[1] != '=') { binary(Node::Type::Modulo, 1, 11); continue; } + + if(depth >= 10) break; + if(s[0] == '+' && s[1] != '=') { binary(Node::Type::Add, 1, 10); continue; } + if(s[0] == '-' && s[1] != '=') { binary(Node::Type::Subtract, 1, 10); continue; } + + if(depth >= 9) break; + if(s[0] == '<' && s[1] == '<' && s[2] == '<' && s[3] != '=') { binary(Node::Type::RotateLeft, 3, 9); continue; } + if(s[0] == '>' && s[1] == '>' && s[2] == '>' && s[3] != '=') { binary(Node::Type::RotateRight, 3, 9); continue; } + if(s[0] == '<' && s[1] == '<' && s[2] != '=') { binary(Node::Type::ShiftLeft, 2, 9); continue; } + if(s[0] == '>' && s[1] == '>' && s[2] != '=') { binary(Node::Type::ShiftRight, 2, 9); continue; } + + if(depth >= 8) break; + if(s[0] == '&' && s[1] != '&' && s[1] != '=') { binary(Node::Type::BitwiseAnd, 1, 8); continue; } + if(s[0] == '|' && s[1] != '|' && s[1] != '=') { binary(Node::Type::BitwiseOr, 1, 8); continue; } + if(s[0] == '^' && s[1] != '^' && s[1] != '=') { binary(Node::Type::BitwiseXor, 1, 8); continue; } + + if(depth >= 7) break; + if(s[0] == '~' && s[1] != '=') { binary(Node::Type::Concatenate, 1, 7); continue; } + + if(depth >= 6) break; + if(s[0] == '=' && s[1] == '=') { binary(Node::Type::Equal, 2, 6); continue; } + if(s[0] == '!' && s[1] == '=') { binary(Node::Type::NotEqual, 2, 6); continue; } + if(s[0] == '<' && s[1] == '=') { binary(Node::Type::LessThanEqual, 2, 6); continue; } + if(s[0] == '>' && s[1] == '=') { binary(Node::Type::GreaterThanEqual, 2, 6); continue; } + if(s[0] == '<') { binary(Node::Type::LessThan, 1, 6); continue; } + if(s[0] == '>') { binary(Node::Type::GreaterThan, 1, 6); continue; } + + if(depth >= 5) break; + if(s[0] == '&' && s[1] == '&') { binary(Node::Type::LogicalAnd, 2, 5); continue; } + if(s[0] == '|' && s[1] == '|') { binary(Node::Type::LogicalOr, 2, 5); continue; } + + if(s[0] == '?' && s[1] == '?') { binary(Node::Type::Coalesce, 2, 4); continue; } + if(s[0] == '?' && s[1] != '?') { ternary(Node::Type::Condition, 1, 4); continue; } + if(depth >= 4) break; + + if(s[0] == '=') { binary(Node::Type::Assign, 1, 3); continue; } + if(s[0] == ':' && s[1] == '=') { binary(Node::Type::Create, 2, 3); continue; } + if(s[0] == '*' && s[1] == '=') { binary(Node::Type::AssignMultiply, 2, 3); continue; } + if(s[0] == '/' && s[1] == '=') { binary(Node::Type::AssignDivide, 2, 3); continue; } + if(s[0] == '%' && s[1] == '=') { binary(Node::Type::AssignModulo, 2, 3); continue; } + if(s[0] == '+' && s[1] == '=') { binary(Node::Type::AssignAdd, 2, 3); continue; } + if(s[0] == '-' && s[1] == '=') { binary(Node::Type::AssignSubtract, 2, 3); continue; } + if(s[0] == '<' && s[1] == '<' && s[2] == '<' && s[3] == '=') { binary(Node::Type::AssignRotateLeft, 4, 3); continue; } + if(s[0] == '>' && s[1] == '>' && s[2] == '>' && s[3] == '=') { binary(Node::Type::AssignRotateRight, 4, 3); continue; } + if(s[0] == '<' && s[1] == '<' && s[2] == '=') { binary(Node::Type::AssignShiftLeft, 3, 3); continue; } + if(s[0] == '>' && s[1] == '>' && s[2] == '=') { binary(Node::Type::AssignShiftRight, 3, 3); continue; } + if(s[0] == '&' && s[1] == '=') { binary(Node::Type::AssignBitwiseAnd, 2, 3); continue; } + if(s[0] == '|' && s[1] == '=') { binary(Node::Type::AssignBitwiseOr, 2, 3); continue; } + if(s[0] == '^' && s[1] == '=') { binary(Node::Type::AssignBitwiseXor, 2, 3); continue; } + if(s[0] == '~' && s[1] == '=') { binary(Node::Type::AssignConcatenate, 2, 3); continue; } + if(depth >= 3) break; + + if(depth >= 2) break; + if(s[0] == ',') { separator(Node::Type::Separator, 1, 2); continue; } + + if(depth >= 1 && (s[0] == ')' || s[0] == ']')) break; + + while(whitespace(s[0])) s++; + if(!s[0]) break; + + throw "unrecognized terminal"; + } + #undef p +} + +inline auto parse(const string& expression) -> Node* { + auto result = new Node; + const char* p = expression; + parse(result, p, 0); + return result; +} + +}} diff --git a/string/find.hpp b/string/find.hpp new file mode 100644 index 0000000..872fece --- /dev/null +++ b/string/find.hpp @@ -0,0 +1,26 @@ +#pragma once + +namespace nall { + +template auto string::_find(int offset, rstring source) const -> maybe { + if(source.size() == 0) return nothing; + + auto p = data(); + for(uint n = offset, quoted = 0; n < size();) { + if(Quoted) { if(p[n] == '\"') { quoted ^= 1; n++; continue; } if(quoted) { n++; continue; } } + if(_compare(p + n, size() - n, source.data(), source.size())) { n++; continue; } + return n - offset; + } + + return nothing; +} + +auto string::find(rstring source) const -> maybe { return _find<0, 0>(0, source); } +auto string::ifind(rstring source) const -> maybe { return _find<1, 0>(0, source); } +auto string::qfind(rstring source) const -> maybe { return _find<0, 1>(0, source); } +auto string::iqfind(rstring source) const -> maybe { return _find<1, 1>(0, source); } + +auto string::findFrom(int offset, rstring source) const -> maybe { return _find<0, 0>(offset, source); } +auto string::ifindFrom(int offset, rstring source) const -> maybe { return _find<1, 0>(offset, source); } + +} diff --git a/string/format.hpp b/string/format.hpp new file mode 100644 index 0000000..8f8c463 --- /dev/null +++ b/string/format.hpp @@ -0,0 +1,177 @@ +#pragma once + +namespace nall { + +//nall::format is a vector of parameters that can be applied to a string +//each {#} token will be replaced with its appropriate format parameter + +auto string::format(const nall::format& params) -> type& { + auto size = (int)this->size(); + auto data = (char*)memory::allocate(size); + memory::copy(data, this->data(), size); + + signed x = 0; + while(x < size - 2) { //2 = minimum tag length + if(data[x] != '{') { x++; continue; } + + signed y = x + 1; + while(y < size - 1) { //-1 avoids going out of bounds on test after this loop + if(data[y] != '}') { y++; continue; } + break; + } + + if(data[y++] != '}') { x++; continue; } + + static auto isNumeric = [](char* s, char* e) -> bool { + if(s == e) return false; //ignore empty tags: {} + while(s < e) { + if(*s >= '0' && *s <= '9') { s++; continue; } + return false; + } + return true; + }; + if(!isNumeric(&data[x + 1], &data[y - 1])) { x++; continue; } + + uint index = nall::natural(&data[x + 1]); + if(index >= params.size()) { x++; continue; } + + uint sourceSize = y - x; + uint targetSize = params[index].size(); + uint remaining = size - x; + + if(sourceSize > targetSize) { + uint difference = sourceSize - targetSize; + memory::move(&data[x], &data[x + difference], remaining); + size -= difference; + } else if(targetSize > sourceSize) { + uint difference = targetSize - sourceSize; + data = (char*)realloc(data, size + difference); + size += difference; + memory::move(&data[x + difference], &data[x], remaining); + } + memory::copy(&data[x], params[index].data(), targetSize); + x += targetSize; + } + + resize(size); + memory::copy(get(), data, size); + memory::free(data); + return *this; +} + +template auto format::append(const T& value, P&&... p) -> format& { + vector::append(value); + return append(forward

(p)...); +} + +auto format::append() -> format& { + return *this; +} + +template auto print(P&&... p) -> void { + string s{forward

(p)...}; + fputs(s.data(), stdout); +} + +auto integer(intmax value, long precision, char padchar) -> string { + string buffer; + buffer.resize(1 + sizeof(intmax) * 3); + char* p = buffer.get(); + + bool negative = value < 0; + if(negative) value = -value; //make positive + uint size = 0; + do { + p[size++] = '0' + (value % 10); + value /= 10; + } while(value); + if(negative) p[size++] = '-'; + buffer.resize(size); + buffer.reverse(); + if(precision) buffer.size(precision, padchar); + return buffer; +} + +auto natural(uintmax value, long precision, char padchar) -> string { + string buffer; + buffer.resize(sizeof(uintmax) * 3); + char* p = buffer.get(); + + uint size = 0; + do { + p[size++] = '0' + (value % 10); + value /= 10; + } while(value); + buffer.resize(size); + buffer.reverse(); + if(precision) buffer.size(precision, padchar); + return buffer; +} + +auto hex(uintmax value, long precision, char padchar) -> string { + string buffer; + buffer.resize(sizeof(uintmax) * 2); + char* p = buffer.get(); + + uint size = 0; + do { + uint n = value & 15; + p[size++] = n < 10 ? '0' + n : 'a' + n - 10; + value >>= 4; + } while(value); + buffer.resize(size); + buffer.reverse(); + if(precision) buffer.size(precision, padchar); + return buffer; +} + +auto octal(uintmax value, long precision, char padchar) -> string { + string buffer; + buffer.resize(sizeof(uintmax) * 3); + char* p = buffer.get(); + + uint size = 0; + do { + p[size++] = '0' + (value & 7); + value >>= 3; + } while(value); + buffer.resize(size); + buffer.reverse(); + if(precision) buffer.size(precision, padchar); + return buffer; +} + +auto binary(uintmax value, long precision, char padchar) -> string { + string buffer; + buffer.resize(sizeof(uintmax) * 8); + char* p = buffer.get(); + + uint size = 0; + do { + p[size++] = '0' + (value & 1); + value >>= 1; + } while(value); + buffer.resize(size); + buffer.reverse(); + if(precision) buffer.size(precision, padchar); + return buffer; +} + +template auto pointer(const T* value, long precision) -> string { + if(value == nullptr) return "(nullptr)"; + return {"0x", hex((uintptr)value, precision)}; +} + +auto pointer(uintptr value, long precision) -> string { + if(value == 0) return "(nullptr)"; + return {"0x", hex(value, precision)}; +} + +auto real(long double value) -> string { + string temp; + temp.resize(real(nullptr, value)); + real(temp.get(), value); + return temp; +} + +} diff --git a/string/hash.hpp b/string/hash.hpp new file mode 100644 index 0000000..11371a3 --- /dev/null +++ b/string/hash.hpp @@ -0,0 +1,33 @@ +#pragma once + +namespace nall { + +namespace Hash { + auto CRC16::digest() -> string { + return hex(value(), 4L); + } + + auto CRC32::digest() -> string { + return hex(value(), 8L); + } + + auto SHA256::digest() const -> string { + string result; + for(auto n : value()) result.append(hex(n, 2L)); + return result; + } +} + +auto crc16(rstring self) -> string { + return Hash::CRC16(self.data(), self.size()).digest(); +} + +auto crc32(rstring self) -> string { + return Hash::CRC32(self.data(), self.size()).digest(); +} + +auto sha256(rstring self) -> string { + return Hash::SHA256(self.data(), self.size()).digest(); +} + +} diff --git a/string/list.hpp b/string/list.hpp new file mode 100644 index 0000000..cbdc375 --- /dev/null +++ b/string/list.hpp @@ -0,0 +1,73 @@ +#pragma once + +namespace nall { + +auto lstring::operator==(const lstring& source) const -> bool { + if(this == &source) return true; + if(size() != source.size()) return false; + for(uint n = 0; n < size(); n++) { + if(operator[](n) != source[n]) return false; + } + return true; +} + +auto lstring::operator!=(const lstring& source) const -> bool { + return !operator==(source); +} + +auto lstring::isort() -> lstring& { + nall::sort(pool, objectsize, [](const string& x, const string& y) { + return memory::icompare(x.data(), x.size(), y.data(), y.size()) < 0; + }); + return *this; +} + +template auto lstring::append(const string& data, P&&... p) -> lstring& { + vector::append(data); + append(forward

(p)...); + return *this; +} + +auto lstring::append() -> lstring& { + return *this; +} + +auto lstring::find(rstring source) const -> maybe { + for(uint n = 0; n < size(); n++) { + if(operator[](n).equals(source)) return n; + } + return nothing; +} + +auto lstring::ifind(rstring source) const -> maybe { + for(uint n = 0; n < size(); n++) { + if(operator[](n).iequals(source)) return n; + } + return nothing; +} + +auto lstring::match(rstring pattern) const -> lstring { + lstring result; + for(uint n = 0; n < size(); n++) { + if(operator[](n).match(pattern)) result.append(operator[](n)); + } + return result; +} + +auto lstring::merge(rstring separator) const -> string { + string output; + for(uint n = 0; n < size(); n++) { + output.append(operator[](n)); + if(n < size() - 1) output.append(separator.data()); + } + return output; +} + +auto lstring::strip() -> lstring& { + for(uint n = 0; n < size(); n++) { + operator[](n).strip(); + } + return *this; +} + +} diff --git a/string/markup/bml.hpp b/string/markup/bml.hpp new file mode 100644 index 0000000..d170d19 --- /dev/null +++ b/string/markup/bml.hpp @@ -0,0 +1,189 @@ +#pragma once + +//BML v1.0 parser +//revision 0.04 + +namespace nall { namespace BML { + +//metadata is used to store nesting level + +struct ManagedNode; +using SharedNode = shared_pointer; + +struct ManagedNode : Markup::ManagedNode { +protected: + //test to verify if a valid character for a node name + auto valid(char p) const -> bool { //A-Z, a-z, 0-9, -. + return p - 'A' < 26u || p - 'a' < 26u || p - '0' < 10u || p - '-' < 2u; + } + + //determine indentation level, without incrementing pointer + auto readDepth(const char* p) -> uint { + uint depth = 0; + while(p[depth] == '\t' || p[depth] == ' ') depth++; + return depth; + } + + //determine indentation level + auto parseDepth(const char*& p) -> uint { + uint depth = readDepth(p); + p += depth; + return depth; + } + + //read name + auto parseName(const char*& p) -> void { + uint length = 0; + while(valid(p[length])) length++; + if(length == 0) throw "Invalid node name"; + _name = slice(p, 0, length); + p += length; + } + + auto parseData(const char*& p) -> void { + if(*p == '=' && *(p + 1) == '\"') { + uint length = 2; + while(p[length] && p[length] != '\n' && p[length] != '\"') length++; + if(p[length] != '\"') throw "Unescaped value"; + _value = {slice(p, 2, length - 2), "\n"}; + p += length + 1; + } else if(*p == '=') { + uint length = 1; + while(p[length] && p[length] != '\n' && p[length] != '\"' && p[length] != ' ') length++; + if(p[length] == '\"') throw "Illegal character in value"; + _value = {slice(p, 1, length - 1), "\n"}; + p += length; + } else if(*p == ':') { + uint length = 1; + while(p[length] && p[length] != '\n') length++; + _value = {slice(p, 1, length - 1), "\n"}; + p += length; + } + } + + //read all attributes for a node + auto parseAttributes(const char*& p) -> void { + while(*p && *p != '\n') { + if(*p != ' ') throw "Invalid node name"; + while(*p == ' ') p++; //skip excess spaces + if(*(p + 0) == '/' && *(p + 1) == '/') break; //skip comments + + SharedNode node(new ManagedNode); + uint length = 0; + while(valid(p[length])) length++; + if(length == 0) throw "Invalid attribute name"; + node->_name = slice(p, 0, length); + node->parseData(p += length); + node->_value.rtrim("\n", 1L); + _children.append(node); + } + } + + //read a node and all of its child nodes + auto parseNode(const lstring& text, uint& y) -> void { + const char* p = text[y++]; + _metadata = parseDepth(p); + parseName(p); + parseData(p); + parseAttributes(p); + + while(y < text.size()) { + uint depth = readDepth(text[y]); + if(depth <= _metadata) break; + + if(text[y][depth] == ':') { + _value.append(slice(text[y++], depth + 1), "\n"); + continue; + } + + SharedNode node(new ManagedNode); + node->parseNode(text, y); + _children.append(node); + } + + _value.rtrim("\n", 1L); + } + + //read top-level nodes + auto parse(string document) -> void { + //in order to simplify the parsing logic; we do an initial pass to normalize the data + //the below code will turn '\r\n' into '\n'; skip empty lines; and skip comment lines + char* p = document.get(), *output = p; + while(*p) { + char* origin = p; + bool empty = true; + while(*p) { + //scan for first non-whitespace character. if it's a line feed or comment; skip the line + if(p[0] == ' ' || p[0] == '\t') { p++; continue; } + empty = p[0] == '\r' || p[0] == '\n' || (p[0] == '/' && p[1] == '/'); + break; + } + while(*p) { + if(p[0] == '\r') p[0] = '\n'; //turns '\r\n' into '\n\n' (second '\n' will be skipped) + if(*p++ == '\n') break; //include '\n' in the output to be copied + } + if(empty) continue; + + memory::move(output, origin, p - origin); + output += p - origin; + } + document.resize(document.size() - (p - output)).rtrim("\n"); + if(document.size() == 0) return; //empty document + + auto text = document.split("\n"); + uint y = 0; + while(y < text.size()) { + SharedNode node(new ManagedNode); + node->parseNode(text, y); + if(node->_metadata > 0) throw "Root nodes cannot be indented"; + _children.append(node); + } + } + + friend auto unserialize(const string&) -> Markup::Node; +}; + +inline auto unserialize(const string& markup) -> Markup::Node { + SharedNode node(new ManagedNode); + try { + node->parse(markup); + } catch(const char* error) { + node.reset(); + } + return (Markup::SharedNode&)node; +} + +inline auto serialize(const Markup::Node& node, uint depth = 0) -> string { + if(!node.name()) { + string result; + for(auto leaf : node) { + result.append(serialize(leaf, depth)); + } + return result; + } + + string padding; + padding.resize(depth * 2); + for(auto& byte : padding) byte = ' '; + + lstring lines; + if(auto value = node.value()) lines = value.split("\n"); + + string result; + result.append(padding); + result.append(node.name()); + if(lines.size() == 1) result.append(":", lines[0]); + result.append("\n"); + if(lines.size() > 1) { + padding.append(" "); + for(auto& line : lines) { + result.append(padding, ":", line, "\n"); + } + } + for(auto leaf : node) { + result.append(serialize(leaf, depth + 1)); + } + return result; +} + +}} diff --git a/string/markup/find.hpp b/string/markup/find.hpp new file mode 100644 index 0000000..9767bfd --- /dev/null +++ b/string/markup/find.hpp @@ -0,0 +1,131 @@ +#pragma once + +namespace nall { namespace Markup { + +auto ManagedNode::_evaluate(string query) const -> bool { + if(!query) return true; + + for(auto& rule : query.replace(" ", "").split(",")) { + enum class Comparator : uint { ID, EQ, NE, LT, LE, GT, GE }; + auto comparator = Comparator::ID; + if(rule.match("*!=*")) comparator = Comparator::NE; + else if(rule.match("*<=*")) comparator = Comparator::LE; + else if(rule.match("*>=*")) comparator = Comparator::GE; + else if(rule.match ("*=*")) comparator = Comparator::EQ; + else if(rule.match ("*<*")) comparator = Comparator::LT; + else if(rule.match ("*>*")) comparator = Comparator::GT; + + if(comparator == Comparator::ID) { + if(_find(rule).size()) continue; + return false; + } + + lstring side; + switch(comparator) { + case Comparator::EQ: side = rule.split ("=", 1L); break; + case Comparator::NE: side = rule.split("!=", 1L); break; + case Comparator::LT: side = rule.split ("<", 1L); break; + case Comparator::LE: side = rule.split("<=", 1L); break; + case Comparator::GT: side = rule.split (">", 1L); break; + case Comparator::GE: side = rule.split(">=", 1L); break; + } + + string data = string{_value}.strip(); + if(side(0).empty() == false) { + auto result = _find(side(0)); + if(result.size() == 0) return false; + data = result[0].value(); + } + + switch(comparator) { + case Comparator::EQ: if(data.match(side(1)) == true) continue; break; + case Comparator::NE: if(data.match(side(1)) == false) continue; break; + case Comparator::LT: if(data.natural() < side(1).natural()) continue; break; + case Comparator::LE: if(data.natural() <= side(1).natural()) continue; break; + case Comparator::GT: if(data.natural() > side(1).natural()) continue; break; + case Comparator::GE: if(data.natural() >= side(1).natural()) continue; break; + } + + return false; + } + + return true; +} + +auto ManagedNode::_find(const string& query) const -> vector { + vector result; + + lstring path = query.split("/"); + string name = path.take(0), rule; + uint lo = 0u, hi = ~0u; + + if(name.match("*[*]")) { + auto p = name.rtrim("]", 1L).split("[", 1L); + name = p(0); + if(p(1).find("-")) { + p = p(1).split("-", 1L); + lo = p(0).empty() ? 0u : p(0).natural(); + hi = p(1).empty() ? ~0u : p(1).natural(); + } else { + lo = hi = p(1).natural(); + } + } + + if(name.match("*(*)")) { + auto p = name.rtrim(")", 1L).split("(", 1L); + name = p(0); + rule = p(1); + } + + uint position = 0; + for(auto& node : _children) { + if(!node->_name.match(name)) continue; + if(!node->_evaluate(rule)) continue; + + bool inrange = position >= lo && position <= hi; + position++; + if(!inrange) continue; + + if(path.size() == 0) { + result.append(node); + } else for(auto& item : node->_find(path.merge("/"))) { + result.append(item); + } + } + + return result; +} + +auto ManagedNode::_lookup(const string& path) const -> Node { + if(auto position = path.find("/")) { + auto name = slice(path, 0, *position); + for(auto& node : _children) { + if(name == node->_name) { + return node->_lookup(slice(path, *position + 1)); + } + } + } else for(auto& node : _children) { + if(path == node->_name) return node; + } + return {}; +} + +auto ManagedNode::_create(const string& path) -> Node { + if(auto position = path.find("/")) { + auto name = slice(path, 0, *position); + for(auto& node : _children) { + if(name == node->_name) { + return node->_create(slice(path, *position + 1)); + } + } + _children.append(new ManagedNode(name)); + return _children.last()->_create(slice(path, *position + 1)); + } + for(auto& node : _children) { + if(path == node->_name) return node; + } + _children.append(new ManagedNode(path)); + return _children.last(); +} + +}} diff --git a/string/markup/node.hpp b/string/markup/node.hpp new file mode 100644 index 0000000..875e576 --- /dev/null +++ b/string/markup/node.hpp @@ -0,0 +1,141 @@ +#pragma once + +namespace nall { namespace Markup { + +struct Node; +struct ManagedNode; +using SharedNode = shared_pointer; + +struct ManagedNode { + ManagedNode() = default; + ManagedNode(const string& name) : _name(name) {} + ManagedNode(const string& name, const string& value) : _name(name), _value(value) {} + + auto clone() const -> SharedNode { + SharedNode clone{new ManagedNode(_name, _value)}; + for(auto& child : _children) { + clone->_children.append(child->clone()); + } + return clone; + } + + auto copy(SharedNode source) -> void { + _name = source->_name; + _value = source->_value; + _metadata = source->_metadata; + _children.reset(); + for(auto child : source->_children) { + _children.append(child->clone()); + } + } + +protected: + string _name; + string _value; + uintptr_t _metadata = 0; + vector _children; + + inline auto _evaluate(string query) const -> bool; + inline auto _find(const string& query) const -> vector; + inline auto _lookup(const string& path) const -> Node; + inline auto _create(const string& path) -> Node; + + friend class Node; +}; + +struct Node { + Node() : shared(new ManagedNode) {} + Node(const SharedNode& source) : shared(source ? source : new ManagedNode) {} + Node(const string& name) : shared(new ManagedNode(name)) {} + Node(const string& name, const string& value) : shared(new ManagedNode(name, value)) {} + + auto unique() const -> bool { return shared.unique(); } + auto clone() const -> Node { return shared->clone(); } + auto copy(Node source) -> void { return shared->copy(source.shared); } + + explicit operator bool() const { return shared->_name || shared->_children; } + auto name() const -> string { return shared->_name; } + auto value() const -> string { return shared->_value; } + + auto text() const -> string { return value().strip(); } + auto boolean() const -> bool { return text() == "true"; } + auto integer() const -> intmax { return text().integer(); } + auto natural() const -> uintmax { return text().natural(); } + auto real() const -> double { return text().real(); } + + auto setName(const string& name = "") -> Node& { shared->_name = name; return *this; } + auto setValue(const string& value = "") -> Node& { shared->_value = value; return *this; } + + auto reset() -> void { shared->_children.reset(); } + auto size() const -> uint { return shared->_children.size(); } + + auto prepend(const Node& node) -> void { shared->_children.prepend(node.shared); } + auto append(const Node& node) -> void { shared->_children.append(node.shared); } + auto remove(const Node& node) -> bool { + for(auto n : range(size())) { + if(node.shared == shared->_children[n]) { + return shared->_children.remove(n), true; + } + } + return false; + } + + auto insert(uint position, const Node& node) -> bool { + if(position > size()) return false; //used > instead of >= to allow indexed-equivalent of append() + return shared->_children.insert(position, node.shared), true; + } + + auto remove(uint position) -> bool { + if(position >= size()) return false; + return shared->_children.remove(position), true; + } + + auto swap(uint x, uint y) -> bool { + if(x >= size() || y >= size()) return false; + return std::swap(shared->_children[x], shared->_children[y]), true; + } + + auto sort(function comparator = [](auto x, auto y) { + return string::compare(x.shared->_name, y.shared->_name) < 0; + }) -> void { + nall::sort(shared->_children.data(), shared->_children.size(), [&](auto x, auto y) { + return comparator(x, y); //this call converts SharedNode objects to Node objects + }); + } + + auto operator[](int position) -> Node { + if(position >= size()) return {}; + return shared->_children[position]; + } + + auto operator[](const string& path) const -> Node { return shared->_lookup(path); } + auto operator()(const string& path) -> Node { return shared->_create(path); } + auto find(const string& query) const -> vector { return shared->_find(query); } + + struct iterator { + auto operator*() -> Node { return {source.shared->_children[position]}; } + auto operator!=(const iterator& source) const -> bool { return position != source.position; } + auto operator++() -> iterator& { return position++, *this; } + iterator(const Node& source, uint position) : source(source), position(position) {} + + private: + const Node& source; + uint position; + }; + + auto begin() const -> iterator { return iterator(*this, 0); } + auto end() const -> iterator { return iterator(*this, size()); } + +protected: + SharedNode shared; +}; + +}} + +namespace nall { + +inline range_t range(const Markup::Node& node) { + return range_t{0, (int)node.size(), 1}; +} + +} diff --git a/string/markup/xml.hpp b/string/markup/xml.hpp new file mode 100644 index 0000000..f53090e --- /dev/null +++ b/string/markup/xml.hpp @@ -0,0 +1,217 @@ +#pragma once + +//XML v1.0 subset parser +//revision 0.04 + +namespace nall { namespace XML { + +//metadata: +// 0 = element +// 1 = attribute + +struct ManagedNode; +using SharedNode = shared_pointer; + +struct ManagedNode : Markup::ManagedNode { +protected: + inline string escape() const { + string result = _value; + result.replace("&", "&"); + result.replace("<", "<"); + result.replace(">", ">"); + if(_metadata == 1) { + result.replace("\'", "'"); + result.replace("\"", """); + } + return result; + } + + inline bool isName(char c) const { + if(c >= 'A' && c <= 'Z') return true; + if(c >= 'a' && c <= 'z') return true; + if(c >= '0' && c <= '9') return true; + if(c == '.' || c == '_') return true; + if(c == '?') return true; + return false; + } + + inline bool isWhitespace(char c) const { + if(c == ' ' || c == '\t') return true; + if(c == '\r' || c == '\n') return true; + return false; + } + + //copy part of string from source document into target string; decode markup while copying + inline void copy(string& target, const char* source, uint length) { + target.reserve(length + 1); + + #if defined(NALL_XML_LITERAL) + memory::copy(target.pointer(), source, length); + target[length] = 0; + return; + #endif + + char* output = target.get(); + while(length) { + if(*source == '&') { + if(!memory::compare(source, "<", 4)) { *output++ = '<'; source += 4; length -= 4; continue; } + if(!memory::compare(source, ">", 4)) { *output++ = '>'; source += 4; length -= 4; continue; } + if(!memory::compare(source, "&", 5)) { *output++ = '&'; source += 5; length -= 5; continue; } + if(!memory::compare(source, "'", 6)) { *output++ = '\''; source += 6; length -= 6; continue; } + if(!memory::compare(source, """, 6)) { *output++ = '\"'; source += 6; length -= 6; continue; } + } + + if(_metadata == 0 && source[0] == '<' && source[1] == '!') { + //comment + if(!memory::compare(source, "", 3)) source++, length--; + source += 3, length -= 3; + continue; + } + + //CDATA + if(!memory::compare(source, "", 3)) *output++ = *source++, length--; + source += 3, length -= 3; + continue; + } + } + + *output++ = *source++, length--; + } + *output = 0; + } + + inline bool parseExpression(const char*& p) { + if(*(p + 1) != '!') return false; + + //comment + if(!memory::compare(p, "", 3)) p++; + if(!*p) throw "unclosed comment"; + p += 3; + return true; + } + + //CDATA + if(!memory::compare(p, "", 3)) p++; + if(!*p) throw "unclosed CDATA"; + p += 3; + return true; + } + + //DOCTYPE + if(!memory::compare(p, "') counter--; + } while(counter); + return true; + } + + return false; + } + + //returns true if tag closes itself (); false if not () + inline bool parseHead(const char*& p) { + //parse name + const char* nameStart = ++p; //skip '<' + while(isName(*p)) p++; + const char* nameEnd = p; + copy(_name, nameStart, nameEnd - nameStart); + if(_name.empty()) throw "missing element name"; + + //parse attributes + while(*p) { + while(isWhitespace(*p)) p++; + if(!*p) throw "unclosed attribute"; + if(*p == '?' || *p == '/' || *p == '>') break; + + //parse attribute name + SharedNode attribute(new ManagedNode); + attribute->_metadata = 1; + + const char* nameStart = p; + while(isName(*p)) p++; + const char* nameEnd = p; + copy(attribute->_name, nameStart, nameEnd - nameStart); + if(attribute->_name.empty()) throw "missing attribute name"; + + //parse attribute data + if(*p++ != '=') throw "missing attribute value"; + char terminal = *p++; + if(terminal != '\'' && terminal != '\"') throw "attribute value not quoted"; + const char* dataStart = p; + while(*p && *p != terminal) p++; + if(!*p) throw "missing attribute data terminal"; + const char* dataEnd = p++; //skip closing terminal + + copy(attribute->_value, dataStart, dataEnd - dataStart); + _children.append(attribute); + } + + //parse closure + if(*p == '?' && *(p + 1) == '>') { p += 2; return true; } + if(*p == '/' && *(p + 1) == '>') { p += 2; return true; } + if(*p == '>') { p += 1; return false; } + throw "invalid element tag"; + } + + //parse element and all of its child elements + inline void parseElement(const char*& p) { + SharedNode node(new ManagedNode); + if(node->parseHead(p) == false) node->parse(p); + _children.append(node); + } + + //return true if matches this node's name + inline bool parseClosureElement(const char*& p) { + if(p[0] != '<' || p[1] != '/') return false; + p += 2; + const char* nameStart = p; + while(*p && *p != '>') p++; + if(*p != '>') throw "unclosed closure element"; + const char* nameEnd = p++; + if(memory::compare(_name.data(), nameStart, nameEnd - nameStart)) throw "closure element name mismatch"; + return true; + } + + //parse contents of an element + inline void parse(const char*& p) { + const char* dataStart = p; + const char* dataEnd = p; + + while(*p) { + while(*p && *p != '<') p++; + if(!*p) break; + dataEnd = p; + if(parseClosureElement(p) == true) break; + if(parseExpression(p) == true) continue; + parseElement(p); + } + + copy(_value, dataStart, dataEnd - dataStart); + } + + friend auto unserialize(const string&) -> Markup::SharedNode; +}; + +inline auto unserialize(const string& markup) -> Markup::SharedNode { + auto node = new ManagedNode; + try { + const char* p = markup; + node->parse(p); + } catch(const char* error) { + delete node; + node = nullptr; + } + return node; +} + +}} diff --git a/string/match.hpp b/string/match.hpp new file mode 100644 index 0000000..80ff7c1 --- /dev/null +++ b/string/match.hpp @@ -0,0 +1,90 @@ +#pragma once + +namespace nall { + +//todo: these functions are not binary-safe + +auto string::match(rstring source) const -> bool { + const char* s = data(); + const char* p = source.data(); + + const char* cp = nullptr; + const char* mp = nullptr; + while(*s && *p != '*') { + if(*p != '?' && *s != *p) return false; + p++, s++; + } + while(*s) { + if(*p == '*') { + if(!*++p) return true; + mp = p, cp = s + 1; + } else if(*p == '?' || *p == *s) { + p++, s++; + } else { + p = mp, s = cp++; + } + } + while(*p == '*') p++; + return !*p; +} + +auto string::imatch(rstring source) const -> bool { + static auto chrlower = [](char c) -> char { + return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; + }; + + const char* s = data(); + const char* p = source.data(); + + const char* cp = nullptr; + const char* mp = nullptr; + while(*s && *p != '*') { + if(*p != '?' && chrlower(*s) != chrlower(*p)) return false; + p++, s++; + } + while(*s) { + if(*p == '*') { + if(!*++p) return true; + mp = p, cp = s + 1; + } else if(*p == '?' || chrlower(*p) == chrlower(*s)) { + p++, s++; + } else { + p = mp, s = cp++; + } + } + while(*p == '*') p++; + return !*p; +} + +auto tokenize(const char* s, const char* p) -> bool { + while(*s) { + if(*p == '*') { + while(*s) if(tokenize(s++, p + 1)) return true; + return !*++p; + } + if(*s++ != *p++) return false; + } + while(*p == '*') p++; + return !*p; +} + +auto tokenize(lstring& list, const char* s, const char* p) -> bool { + while(*s) { + if(*p == '*') { + const char* b = s; + while(*s) { + if(tokenize(list, s++, p + 1)) { + list.prepend(slice(b, 0, --s - b)); + return true; + } + } + list.prepend(b); + return !*++p; + } + if(*s++ != *p++) return false; + } + while(*p == '*') { list.prepend(s); p++; } + return !*p; +} + +} diff --git a/string/path.hpp b/string/path.hpp new file mode 100644 index 0000000..b5903d5 --- /dev/null +++ b/string/path.hpp @@ -0,0 +1,72 @@ +#pragma once + +namespace nall { + +// (/parent/child.type/) +// (/parent/child.type/)name.type +auto pathname(rstring self) -> string { + const char* p = self.data() + self.size() - 1; + for(int offset = self.size() - 1; offset >= 0; offset--, p--) { + if(*p == '/') return slice(self, 0, offset + 1); + } + return ""; //no path found +} + +// /parent/child.type/() +// /parent/child.type/(name.type) +auto filename(rstring self) -> string { + const char* p = self.data() + self.size() - 1; + for(int offset = self.size() - 1; offset >= 0; offset--, p--) { + if(*p == '/') return slice(self, offset + 1); + } + return self; //no path found +} + +// (/parent/)child.type/ +// (/parent/child.type/)name.type +auto dirname(rstring self) -> string { + const char* p = self.data() + self.size() - 1, *last = p; + for(int offset = self.size() - 1; offset >= 0; offset--, p--) { + if(*p == '/' && p == last) continue; + if(*p == '/') return slice(self, 0, offset + 1); + } + return rootpath(); //technically, directory is unknown; must return something +} + +// /parent/(child.type/) +// /parent/child.type/(name.type) +auto basename(rstring self) -> string { + const char* p = self.data() + self.size() - 1, *last = p; + for(int offset = self.size() - 1; offset >= 0; offset--, p--) { + if(*p == '/' && p == last) continue; + if(*p == '/') return slice(self, offset + 1); + } + return self; //no path found +} + +// /parent/(child).type/ +// /parent/child.type/(name).type +auto prefixname(rstring self) -> string { + const char* p = self.data() + self.size() - 1, *last = p; + for(int offset = self.size() - 1, suffix = -1; offset >= 0; offset--, p--) { + if(*p == '/' && p == last) continue; + if(*p == '/') return slice(self, offset + 1, suffix >= 0 ? suffix - offset - 1 : 0).rtrim("/"); + if(*p == '.' && suffix == -1) { suffix = offset; continue; } + if(offset == 0) return slice(self, offset, suffix).rtrim("/"); + } + return ""; //no prefix found +} + +// /parent/child(.type)/ +// /parent/child.type/name(.type) +auto suffixname(rstring self) -> string { + const char* p = self.data() + self.size() - 1, *last = p; + for(int offset = self.size() - 1; offset >= 0; offset--, p--) { + if(*p == '/' && p == last) continue; + if(*p == '/') break; + if(*p == '.') return slice(self, offset).rtrim("/"); + } + return ""; //no suffix found +} + +} diff --git a/string/platform.hpp b/string/platform.hpp new file mode 100644 index 0000000..86e38e0 --- /dev/null +++ b/string/platform.hpp @@ -0,0 +1,142 @@ +#pragma once + +namespace nall { + +auto activepath() -> string { + char path[PATH_MAX] = ""; + auto unused = getcwd(path, PATH_MAX); + string result = path; + if(!result) result = "."; + result.transform("\\", "/"); + if(result.endsWith("/") == false) result.append("/"); + return result; +} + +auto realpath(rstring name) -> string { + string result; + char path[PATH_MAX] = ""; + if(::realpath(name, path)) result = pathname(string{path}.transform("\\", "/")); + if(!result) return activepath(); + result.transform("\\", "/"); + if(result.endsWith("/") == false) result.append("/"); + return result; +} + +auto programpath() -> string { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + GetModuleFileName(nullptr, path, PATH_MAX); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + return realpath(result); + #else + Dl_info info; + dladdr((void*)&programpath, &info); + return realpath(info.dli_fname); + #endif +} + +// / +// c:/ +auto rootpath() -> string { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + SHGetFolderPathW(nullptr, CSIDL_WINDOWS | CSIDL_FLAG_CREATE, nullptr, 0, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + return slice(result, 0, 3); + #else + return "/"; + #endif +} + +// /home/username/ +// c:/users/username/ +auto userpath() -> string { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + SHGetFolderPathW(nullptr, CSIDL_PROFILE | CSIDL_FLAG_CREATE, nullptr, 0, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #else + struct passwd* userinfo = getpwuid(getuid()); + string result = userinfo->pw_dir; + #endif + if(!result) result = "."; + if(result.endsWith("/") == false) result.append("/"); + return result; +} + +// /home/username/.config/ +// c:/users/username/appdata/roaming/ +auto configpath() -> string { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + SHGetFolderPathW(nullptr, CSIDL_APPDATA | CSIDL_FLAG_CREATE, nullptr, 0, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #elif defined(PLATFORM_MACOSX) + string result = {userpath(), "Library/Application Support/"}; + #else + string result = {userpath(), ".config/"}; + #endif + if(!result) result = "."; + if(result.endsWith("/") == false) result.append("/"); + return result; +} + +// /home/username/.local/share/ +// c:/users/username/appdata/local/ +auto localpath() -> string { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE, nullptr, 0, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #elif defined(PLATFORM_MACOSX) + string result = {userpath(), "Library/Application Support/"}; + #else + string result = {userpath(), ".local/share/"}; + #endif + if(!result) result = "."; + if(result.endsWith("/") == false) result.append("/"); + return result; +} + +// /usr/share +// /Library/Application Support/ +// c:/ProgramData/ +auto sharedpath() -> string { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + SHGetFolderPathW(nullptr, CSIDL_COMMON_APPDATA | CSIDL_FLAG_CREATE, nullptr, 0, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #elif defined(PLATFORM_MACOSX) + string result = "/Library/Application Support/"; + #else + string result = "/usr/share/"; + #endif + if(!result) result = "."; + if(result.endsWith("/") == false) result.append("/"); + return result; +} + +// /tmp +// c:/users/username/AppData/Local/Temp/ +auto temppath() -> string { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + GetTempPathW(PATH_MAX, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #elif defined(P_tmpdir) + string result = P_tmpdir; + #else + string result = "/tmp/"; + #endif + if(result.endsWith("/") == false) result.append("/"); + return result; +} + +} diff --git a/string/replace.hpp b/string/replace.hpp new file mode 100644 index 0000000..0744caf --- /dev/null +++ b/string/replace.hpp @@ -0,0 +1,94 @@ +#pragma once + +namespace nall { + +template +auto string::_replace(rstring from, rstring to, long limit) -> string& { + if(limit <= 0 || from.size() == 0) return *this; + + signed size = this->size(); + signed matches = 0; + signed quoted = 0; + + //count matches first, so that we only need to reallocate memory once + //(recording matches would also require memory allocation, so this is not done) + { const char* p = data(); + for(signed n = 0; n <= size - (signed)from.size();) { + if(Quoted) { if(p[n] == '\"') { quoted ^= 1; n++; continue; } if(quoted) { n++; continue; } } + if(_compare(p + n, size - n, from.data(), from.size())) { n++; continue; } + + if(++matches >= limit) break; + n += from.size(); + } + } + if(matches == 0) return *this; + + //in-place overwrite + if(to.size() == from.size()) { + char* p = get(); + + for(signed n = 0, remaining = matches, quoted = 0; n <= size - (signed)from.size();) { + if(Quoted) { if(p[n] == '\"') { quoted ^= 1; n++; continue; } if(quoted) { n++; continue; } } + if(_compare(p + n, size - n, from.data(), from.size())) { n++; continue; } + + memory::copy(p + n, to.data(), to.size()); + + if(!--remaining) break; + n += from.size(); + } + } + + //left-to-right shrink + else if(to.size() < from.size()) { + char* p = get(); + signed offset = 0; + signed base = 0; + + for(signed n = 0, remaining = matches, quoted = 0; n <= size - (signed)from.size();) { + if(Quoted) { if(p[n] == '\"') { quoted ^= 1; n++; continue; } if(quoted) { n++; continue; } } + if(_compare(p + n, size - n, from.data(), from.size())) { n++; continue; } + + if(offset) memory::move(p + offset, p + base, n - base); + memory::copy(p + offset + (n - base), to.data(), to.size()); + offset += (n - base) + to.size(); + + n += from.size(); + base = n; + if(!--remaining) break; + } + + memory::move(p + offset, p + base, size - base); + resize(size - matches * (from.size() - to.size())); + } + + //right-to-left expand + else if(to.size() > from.size()) { + resize(size + matches * (to.size() - from.size())); + char* p = get(); + + signed offset = this->size(); + signed base = size; + + for(signed n = size, remaining = matches; n >= (signed)from.size();) { //quoted reused from parent scope since we are iterating backward + if(Quoted) { if(p[n] == '\"') { quoted ^= 1; n--; continue; } if(quoted) { n--; continue; } } + if(_compare(p + n - from.size(), size - n + from.size(), from.data(), from.size())) { n--; continue; } + + memory::move(p + offset - (base - n), p + base - (base - n), base - n); + memory::copy(p + offset - (base - n) - to.size(), to.data(), to.size()); + offset -= (base - n) + to.size(); + + if(!--remaining) break; + n -= from.size(); + base = n; + } + } + + return *this; +} + +auto string::replace(rstring from, rstring to, long limit) -> string& { return _replace<0, 0>(from, to, limit); } +auto string::ireplace(rstring from, rstring to, long limit) -> string& { return _replace<1, 0>(from, to, limit); } +auto string::qreplace(rstring from, rstring to, long limit) -> string& { return _replace<0, 1>(from, to, limit); } +auto string::iqreplace(rstring from, rstring to, long limit) -> string& { return _replace<1, 1>(from, to, limit); } + +}; diff --git a/string/split.hpp b/string/split.hpp new file mode 100644 index 0000000..b236e37 --- /dev/null +++ b/string/split.hpp @@ -0,0 +1,41 @@ +#pragma once + +namespace nall { + +template +auto lstring::_split(rstring source, rstring find, long limit) -> lstring& { + reset(); + if(limit <= 0 || find.size() == 0) return *this; + + const char* p = source.data(); + signed size = source.size(); + signed base = 0; + signed matches = 0; + + for(signed n = 0, quoted = 0; n <= size - (signed)find.size();) { + if(Quoted) { if(p[n] == '\"') { quoted ^= 1; n++; continue; } if(quoted) { n++; continue; } } + if(string::_compare(p + n, size - n, find.data(), find.size())) { n++; continue; } + if(matches >= limit) break; + + string& s = operator()(matches); + s.resize(n - base); + memory::copy(s.get(), p + base, n - base); + + n += find.size(); + base = n; + matches++; + } + + string& s = operator()(matches); + s.resize(size - base); + memory::copy(s.get(), p + base, size - base); + + return *this; +} + +auto string::split(rstring on, long limit) const -> lstring { return lstring()._split<0, 0>(*this, on, limit); } +auto string::isplit(rstring on, long limit) const -> lstring { return lstring()._split<1, 0>(*this, on, limit); } +auto string::qsplit(rstring on, long limit) const -> lstring { return lstring()._split<0, 1>(*this, on, limit); } +auto string::iqsplit(rstring on, long limit) const -> lstring { return lstring()._split<1, 1>(*this, on, limit); } + +} diff --git a/string/transform/cml.hpp b/string/transform/cml.hpp new file mode 100644 index 0000000..0b99669 --- /dev/null +++ b/string/transform/cml.hpp @@ -0,0 +1,103 @@ +#pragma once + +/* CSS Markup Language (CML) v1.0 parser + * revision 0.02 + */ + +namespace nall { namespace { + +struct CML { + auto& setPath(const string& pathname) { settings.path = pathname; return *this; } + auto& setReader(const function& reader) { settings.reader = reader; return *this; } + + auto parse(const string& filename) -> string; + auto parse(const string& filedata, const string& pathname) -> string; + +private: + struct Settings { + string path; + function reader; + } settings; + + struct State { + string output; + } state; + + struct Variable { + string name; + string value; + }; + vector variables; + + auto parseDocument(const string& filedata, const string& pathname, uint depth) -> bool; +}; + +auto CML::parse(const string& filename) -> string { + if(!settings.path) settings.path = pathname(filename); + string document = settings.reader ? settings.reader(filename) : string::read(filename); + parseDocument(document, settings.path, 0); + return state.output; +} + +auto CML::parse(const string& filedata, const string& pathname) -> string { + settings.path = pathname; + parseDocument(filedata, settings.path, 0); + return state.output; +} + +auto CML::parseDocument(const string& filedata, const string& pathname, uint depth) -> bool { + if(depth >= 100) return false; //prevent infinite recursion + + auto vendorAppend = [&](const string& name, const string& value) { + state.output.append(" -moz-", name, ": ", value, ";\n"); + state.output.append(" -webkit-", name, ": ", value, ";\n"); + }; + + for(auto& block : filedata.split("\n\n")) { + lstring lines = block.rstrip().split("\n"); + string name = lines.takeFirst(); + + if(name.beginsWith("include ")) { + name.ltrim("include ", 1L); + string filename{pathname, name}; + string document = settings.reader ? settings.reader(filename) : string::read(filename); + parseDocument(document, nall::pathname(filename), depth + 1); + continue; + } + + if(name == "variables") { + for(auto& line : lines) { + auto data = line.split(":", 1L).strip(); + variables.append({data(0), data(1)}); + } + continue; + } + + state.output.append(name, " {\n"); + for(auto& line : lines) { + auto data = line.split(":", 1L).strip(); + auto name = data(0), value = data(1); + while(auto offset = value.find("var(")) { + bool found = false; + if(auto length = value.findFrom(*offset, ")")) { + string name = slice(value, *offset + 4, *length - 4); + for(auto& variable : variables) { + if(variable.name == name) { + value = {slice(value, 0, *offset), variable.value, slice(value, *offset + *length + 1)}; + found = true; + break; + } + } + } + if(!found) break; + } + state.output.append(" ", name, ": ", value, ";\n"); + if(name == "box-sizing") vendorAppend(name, value); + } + state.output.append("}\n\n"); + } + + return true; +} + +}} diff --git a/string/transform/dml.hpp b/string/transform/dml.hpp new file mode 100644 index 0000000..9c6f915 --- /dev/null +++ b/string/transform/dml.hpp @@ -0,0 +1,267 @@ +#pragma once + +/* Document Markup Language (DML) v1.0 parser + * revision 0.03 + */ + +namespace nall { namespace { + +struct DML { + auto& setAllowHTML(bool allowHTML) { settings.allowHTML = allowHTML; return *this; } + auto& setHost(const string& hostname) { settings.host = {hostname, "/"}; return *this; } + auto& setPath(const string& pathname) { settings.path = pathname; return *this; } + auto& setReader(const function& reader) { settings.reader = reader; return *this; } + auto& setSectioned(bool sectioned) { settings.sectioned = sectioned; return *this; } + + auto parse(const string& filedata, const string& pathname) -> string; + auto parse(const string& filename) -> string; + +private: + struct Settings { + bool allowHTML = true; + string host = "localhost/"; + string path; + function reader; + bool sectioned = true; + } settings; + + struct State { + string output; + uint sections = 0; + } state; + + auto parseDocument(const string& filedata, const string& pathname, uint depth) -> bool; + auto parseBlock(string& block, const string& pathname, uint depth) -> bool; + auto count(const string& text, char value) -> uint; + + auto escape(const string& text) -> string; + auto markup(const string& text) -> string; +}; + +auto DML::parse(const string& filedata, const string& pathname) -> string { + settings.path = pathname; + parseDocument(filedata, settings.path, 0); + return state.output; +} + +auto DML::parse(const string& filename) -> string { + if(!settings.path) settings.path = pathname(filename); + string document = settings.reader ? settings.reader(filename) : string::read(filename); + parseDocument(document, settings.path, 0); + return state.output; +} + +auto DML::parseDocument(const string& filedata, const string& pathname, uint depth) -> bool { + if(depth >= 100) return false; //attempt to prevent infinite recursion with reasonable limit + + auto blocks = filedata.split("\n\n"); + for(auto& block : blocks) parseBlock(block, pathname, depth); + if(settings.sectioned && state.sections && depth == 0) state.output.append("\n"); + return true; +} + +auto DML::parseBlock(string& block, const string& pathname, uint depth) -> bool { + if(block.rstrip().empty()) return true; + auto lines = block.split("\n"); + + //include + if(block.beginsWith("")) { + string filename{pathname, block.trim("", 1L).strip()}; + string document = settings.reader ? settings.reader(filename) : string::read(filename); + parseDocument(document, nall::pathname(filename), depth + 1); + } + + //html + else if(block.beginsWith("\n") && settings.allowHTML) { + for(auto n : range(lines)) { + if(n == 0 || !lines[n].beginsWith(" ")) continue; + state.output.append(lines[n].ltrim(" ", 1L), "\n"); + } + } + + //section + else if(block.beginsWith("# ")) { + if(settings.sectioned) { + if(state.sections++) state.output.append(""); + state.output.append("

"); + } + auto content = lines.takeFirst().ltrim("# ", 1L).split(" => ", 1L); + auto data = markup(content[0]); + auto name = escape(content(1, crc32(data))); + state.output.append("
", data); + for(auto& line : lines) { + if(!line.beginsWith("# ")) continue; + state.output.append("", line.ltrim("# ", 1L), ""); + } + state.output.append("
\n"); + } + + //header + else if(auto depth = count(block, '=')) { + auto content = slice(lines.takeFirst(), depth + 1).split(" => ", 1L); + auto data = markup(content[0]); + auto name = escape(content(1, crc32(data))); + if(depth <= 6) { + state.output.append("", data); + for(auto& line : lines) { + if(count(line, '=') != depth) continue; + state.output.append("", slice(line, depth + 1), ""); + } + state.output.append("\n"); + } + } + + //navigation + else if(count(block, '-')) { + state.output.append("\n"); + } + + //list + else if(count(block, '*')) { + uint level = 0; + for(auto& line : lines) { + if(auto depth = count(line, '*')) { + while(level < depth) level++, state.output.append("
    \n"); + while(level > depth) level--, state.output.append("
\n"); + auto data = markup(slice(line, depth + 1)); + state.output.append("
  • ", data, "
  • \n"); + } + } + while(level--) state.output.append("\n"); + } + + //quote + else if(count(block, '>')) { + uint level = 0; + for(auto& line : lines) { + if(auto depth = count(line, '>')) { + while(level < depth) level++, state.output.append("
    \n"); + while(level > depth) level--, state.output.append("
    \n"); + auto data = markup(slice(line, depth + 1)); + state.output.append(data, "\n"); + } + } + while(level--) state.output.append("\n"); + } + + //code + else if(block.beginsWith(" ")) { + state.output.append("
    ");
    +    for(auto& line : lines) {
    +      if(!line.beginsWith("  ")) continue;
    +      state.output.append(escape(line.ltrim("  ", 1L)), "\n");
    +    }
    +    state.output.rtrim("\n", 1L).append("
    \n"); + } + + //divider + else if(block.equals("---")) { + state.output.append("
    \n"); + } + + //paragraph + else { + state.output.append("

    ", markup(block), "

    \n"); + } + + return true; +} + +auto DML::count(const string& text, char value) -> uint { + for(uint n = 0; n < text.size(); n++) { + if(text[n] != value) { + if(text[n] == ' ') return n; + break; + } + } + return 0; +} + +auto DML::escape(const string& text) -> string { + string output; + for(auto c : text) { + if(c == '&') { output.append("&"); continue; } + if(c == '<') { output.append("<"); continue; } + if(c == '>') { output.append(">"); continue; } + if(c == '"') { output.append("""); continue; } + output.append(c); + } + return output; +} + +auto DML::markup(const string& text) -> string { + string output; + + char match = 0; + uint offset = 0; + for(uint n = 0; n < text.size();) { + char a = n ? text[n - 1] : 0; + char b = text[n]; + char c = text[n++ + 1]; + + bool d = !a || a == ' ' || a == '\t' || a == '\r' || a == '\n'; //is previous character whitespace? + bool e = !c || c == ' ' || c == '\t' || c == '\r' || c == '\n'; //is next character whitespace? + bool f = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'); //is next character alphanumeric? + + if(!match && d && !e) { + if(b == '*') { match = '*'; offset = n; continue; } + if(b == '/') { match = '/'; offset = n; continue; } + if(b == '_') { match = '_'; offset = n; continue; } + if(b == '~') { match = '~'; offset = n; continue; } + if(b == '|') { match = '|'; offset = n; continue; } + if(b == '[') { match = ']'; offset = n; continue; } + if(b == '{') { match = '}'; offset = n; continue; } + } + + //if we reach the end of the string without a match; force a match so the content is still output + if(match && b != match && !c) { b = match; f = 0; n++; } + + if(match && b == match && !f) { + match = 0; + auto content = slice(text, offset, n - offset - 1); + if(b == '*') { output.append("", escape(content), ""); continue; } + if(b == '/') { output.append("", escape(content), ""); continue; } + if(b == '_') { output.append("", escape(content), ""); continue; } + if(b == '~') { output.append("", escape(content), ""); continue; } + if(b == '|') { output.append("", escape(content), ""); continue; } + if(b == ']') { + auto p = content.split(" => ", 1L); + p[0].replace("@/", settings.host); + output.append("", escape(p(1, p[0])), ""); + continue; + } + if(b == '}') { + auto p = content.split(" => ", 1L); + p[0].replace("@/", settings.host); + output.append("\"","); + continue; + } + continue; + } + + if(match) continue; + if(b == '\\' && c) { output.append(c); n++; continue; } //character escaping + if(b == '&') { output.append("&"); continue; } //entity escaping + if(b == '<') { output.append("<"); continue; } //... + if(b == '>') { output.append(">"); continue; } //... + if(b == '"') { output.append("""); continue; } //... + output.append(b); + } + + return output; +} + +}} diff --git a/string/trim.hpp b/string/trim.hpp new file mode 100644 index 0000000..35c5ece --- /dev/null +++ b/string/trim.hpp @@ -0,0 +1,102 @@ +#pragma once + +namespace nall { + +auto string::trim(rstring lhs, rstring rhs, long limit) -> string& { + rtrim(rhs, limit); + ltrim(lhs, limit); + return *this; +} + +auto string::ltrim(rstring lhs, long limit) -> string& { + if(lhs.size() == 0) return *this; + long matches = 0; + while(matches < limit) { + signed offset = lhs.size() * matches; + signed length = (signed)size() - offset; + if(length < (signed)lhs.size()) break; + if(memory::compare(data() + offset, lhs.data(), lhs.size()) != 0) break; + matches++; + } + if(matches) remove(0, lhs.size() * matches); + return *this; +} + +auto string::rtrim(rstring rhs, long limit) -> string& { + if(rhs.size() == 0) return *this; + long matches = 0; + while(matches < limit) { + signed offset = (signed)size() - rhs.size() * (matches + 1); + signed length = (signed)size() - offset; + if(offset < 0 || length < (signed)rhs.size()) break; + if(memory::compare(data() + offset, rhs.data(), rhs.size()) != 0) break; + matches++; + } + if(matches) resize(size() - rhs.size() * matches); + return *this; +} + +auto string::itrim(rstring lhs, rstring rhs, long limit) -> string& { + irtrim(rhs, limit); + iltrim(lhs, limit); + return *this; +} + +auto string::iltrim(rstring lhs, long limit) -> string& { + if(lhs.size() == 0) return *this; + long matches = 0; + while(matches < limit) { + signed offset = lhs.size() * matches; + signed length = (signed)size() - offset; + if(length < (signed)lhs.size()) break; + if(memory::icompare(data() + offset, lhs.data(), lhs.size()) != 0) break; + matches++; + } + if(matches) remove(0, lhs.size() * matches); + return *this; +} + +auto string::irtrim(rstring rhs, long limit) -> string& { + if(rhs.size() == 0) return *this; + long matches = 0; + while(matches < limit) { + signed offset = (signed)size() - rhs.size() * (matches + 1); + signed length = (signed)size() - offset; + if(offset < 0 || length < (signed)rhs.size()) break; + if(memory::icompare(data() + offset, rhs.data(), rhs.size()) != 0) break; + matches++; + } + if(matches) resize(size() - rhs.size() * matches); + return *this; +} + +auto string::strip() -> string& { + rstrip(); + lstrip(); + return *this; +} + +auto string::lstrip() -> string& { + unsigned length = 0; + while(length < size()) { + char input = operator[](length); + if(input != ' ' && input != '\t' && input != '\r' && input != '\n') break; + length++; + } + if(length) remove(0, length); + return *this; +} + +auto string::rstrip() -> string& { + unsigned length = 0; + while(length < size()) { + bool matched = false; + char input = operator[](size() - length - 1); + if(input != ' ' && input != '\t' && input != '\r' && input != '\n') break; + length++; + } + if(length) resize(size() - length); + return *this; +} + +} diff --git a/string/utility.hpp b/string/utility.hpp new file mode 100644 index 0000000..0faa2be --- /dev/null +++ b/string/utility.hpp @@ -0,0 +1,158 @@ +#pragma once + +namespace nall { + +auto string::read(rstring filename) -> string { + #if !defined(_WIN32) + FILE* fp = fopen(filename, "rb"); + #else + FILE* fp = _wfopen(utf16_t(filename), L"rb"); + #endif + + string result; + if(!fp) return result; + + fseek(fp, 0, SEEK_END); + int filesize = ftell(fp); + if(filesize < 0) return fclose(fp), result; + + rewind(fp); + result.resize(filesize); + auto unused = fread(result.get(), 1, filesize, fp); + return fclose(fp), result; +} + +auto string::repeat(rstring pattern, uint times) -> string { + string result; + while(times--) result.append(pattern.data()); + return result; +} + +auto string::fill(char fill) -> string& { + memory::fill(get(), size(), fill); + return *this; +} + +auto string::hash() const -> unsigned { + const char* p = data(); + uint length = size(); + uint result = 5381; + while(length--) result = (result << 5) + result + *p++; + return result; +} + +auto string::remove(uint offset, uint length) -> string& { + char* p = get(); + length = min(length, size()); + memory::move(p + offset, p + offset + length, size() - length); + return resize(size() - length); +} + +auto string::reverse() -> string& { + char* p = get(); + uint length = size(); + uint pivot = length >> 1; + for(int x = 0, y = length - 1; x < pivot && y >= 0; x++, y--) std::swap(p[x], p[y]); + return *this; +} + +//+length => insert/delete from start (right justify) +//-length => insert/delete from end (left justify) +auto string::size(int length, char fill) -> string& { + uint size = this->size(); + if(size == length) return *this; + + bool right = length >= 0; + length = abs(length); + + if(size < length) { //expand + resize(length); + char* p = get(); + uint displacement = length - size; + if(right) memory::move(p + displacement, p, size); + else p += size; + while(displacement--) *p++ = fill; + } else { //shrink + char* p = get(); + uint displacement = size - length; + if(right) memory::move(p, p + displacement, length); + resize(length); + } + + return *this; +} + +auto slice(rstring self, int offset, int length) -> string { + string result; + if(offset < self.size()) { + if(length < 0) length = self.size() - offset; + result.resize(length); + memory::copy(result.get(), self.data() + offset, length); + } + return result; +} + +auto integer(char* result, intmax value) -> char* { + bool negative = value < 0; + if(negative) value = -value; + + char buffer[64]; + uint size = 0; + + do { + uint n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + if(negative) buffer[size++] = '-'; + + for(int x = size - 1, y = 0; x >= 0 && y < size; x--, y++) result[x] = buffer[y]; + result[size] = 0; + return result; +} + +auto natural(char* result, uintmax value) -> char* { + char buffer[64]; + uint size = 0; + + do { + uint n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + + for(int x = size - 1, y = 0; x >= 0 && y < size; x--, y++) result[x] = buffer[y]; + result[size] = 0; + return result; +} + +//using sprintf is certainly not the most ideal method to convert +//a double to a string ... but attempting to parse a double by +//hand, digit-by-digit, results in subtle rounding errors. +auto real(char* result, long double value) -> uint { + char buffer[256]; + #ifdef _WIN32 + //Windows C-runtime does not support long double via sprintf() + sprintf(buffer, "%f", (double)value); + #else + sprintf(buffer, "%Lf", value); + #endif + + //remove excess 0's in fraction (2.500000 -> 2.5) + for(char* p = buffer; *p; p++) { + if(*p == '.') { + char* p = buffer + strlen(buffer) - 1; + while(*p == '0') { + if(*(p - 1) != '.') *p = 0; //... but not for eg 1.0 -> 1. + p--; + } + break; + } + } + + uint length = strlen(buffer); + if(result) strcpy(result, buffer); + return length + 1; +} + +} diff --git a/string/view.hpp b/string/view.hpp new file mode 100644 index 0000000..2f72337 --- /dev/null +++ b/string/view.hpp @@ -0,0 +1,88 @@ +#pragma once + +namespace nall { + +struct string_view { + string_view() { + _string = nullptr; + _data = ""; + _size = 0; + } + + string_view(const char* data) { + _string = nullptr; + _data = data; + _size = -1; //defer length calculation, as it is often unnecessary + } + + string_view(const char* data, unsigned size) { + _string = nullptr; + _data = data; + _size = size; + } + + string_view(const string& source) { + _string = nullptr; + _data = source.data(); + _size = source.size(); + } + + template + string_view(P&&... p) { + _string = new string{forward

    (p)...}; + _data = _string->data(); + _size = _string->size(); + } + + ~string_view() { + if(_string) delete _string; + } + + string_view(const string_view& source) { + _string = nullptr; + _data = source._data; + _size = source._size; + } + + string_view(string_view&& source) { + _string = source._string; + _data = source._data; + _size = source._size; + source._string = nullptr; + } + + auto operator=(const string_view& source) -> string_view& { + _string = nullptr; + _data = source._data; + _size = source._size; + return *this; + }; + + auto operator=(string_view&& source) -> string_view& { + _string = source._string; + _data = source._data; + _size = source._size; + source._string = nullptr; + return *this; + }; + + operator const char*() const { + return _data; + } + + auto data() const -> const char* { + return _data; + } + + auto size() const -> unsigned { + if(_size < 0) _size = strlen(_data); + return _size; + } + +protected: + string* _string; + const char* _data; + mutable signed _size; +}; + +} diff --git a/thread.hpp b/thread.hpp new file mode 100644 index 0000000..a01624b --- /dev/null +++ b/thread.hpp @@ -0,0 +1,137 @@ +#pragma once + +//simple thread library +//primary rationale is that std::thread does not support custom stack sizes +//this is highly critical in certain applications such as threaded web servers +//an added bonus is that it avoids licensing issues on Windows +//win32-pthreads (needed for std::thread) is licensed under the GPL only + +#include +#include +#include + +#if defined(API_POSIX) + +#include + +namespace nall { + +struct thread { + inline auto join() -> void; + + static inline auto create(const function& callback, uintptr_t parameter = 0, unsigned stacksize = 0) -> thread; + static inline auto detach() -> void; + static inline auto exit() -> void; + + struct context { + function callback; + uintptr_t parameter = 0; + }; + +private: + pthread_t handle; +}; + +inline auto _threadCallback(void* parameter) -> void* { + auto context = (thread::context*)parameter; + context->callback(context->parameter); + delete context; + return nullptr; +} + +auto thread::join() -> void { + pthread_join(handle, nullptr); +} + +auto thread::create(const function& callback, uintptr_t parameter, unsigned stacksize) -> thread { + thread instance; + + auto context = new thread::context; + context->callback = callback; + context->parameter = parameter; + + pthread_attr_t attr; + pthread_attr_init(&attr); + if(stacksize) pthread_attr_setstacksize(&attr, max(PTHREAD_STACK_MIN, stacksize)); + + pthread_create(&instance.handle, &attr, _threadCallback, (void*)context); + return instance; +} + +auto thread::detach() -> void { + pthread_detach(pthread_self()); +} + +auto thread::exit() -> void { + pthread_exit(nullptr); +} + +} + +#elif defined(API_WINDOWS) + +namespace nall { + +struct thread { + inline ~thread(); + inline auto join() -> void; + + static inline auto create(const function& callback, uintptr_t parameter = 0, unsigned stacksize = 0) -> thread; + static inline auto detach() -> void; + static inline auto exit() -> void; + + struct context { + function callback; + uintptr_t parameter = 0; + }; + +private: + HANDLE handle = 0; +}; + +inline auto WINAPI _threadCallback(void* parameter) -> DWORD { + auto context = (thread::context*)parameter; + context->callback(context->parameter); + delete context; + return 0; +} + +thread::~thread() { + if(handle) { + CloseHandle(handle); + handle = 0; + } +} + +auto thread::join() -> void { + if(handle) { + WaitForSingleObject(handle, INFINITE); + CloseHandle(handle); + handle = 0; + } +} + +auto thread::create(const function& callback, uintptr_t parameter, unsigned stacksize) -> thread { + thread instance; + + auto context = new thread::context; + context->callback = callback; + context->parameter = parameter; + + instance.handle = CreateThread(nullptr, stacksize, _threadCallback, (void*)context, 0, nullptr); + return instance; +} + +auto thread::detach() -> void { + //Windows threads do not use this concept: + //~thread() frees resources via CloseHandle() + //thread continues to run even after handle is closed +} + +auto thread::exit() -> void { + ExitThread(0); +} + +} + +#endif diff --git a/traits.hpp b/traits.hpp new file mode 100644 index 0000000..900df0d --- /dev/null +++ b/traits.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +namespace nall { + using std::add_const; + using std::decay; + using std::declval; + using std::false_type; + using std::forward; + using std::initializer_list; + using std::is_array; + using std::is_base_of; + using std::is_function; + using std::is_integral; + using std::is_same; + using std::move; + using std::nullptr_t; + using std::remove_extent; + using std::remove_reference; + using std::swap; + using std::true_type; +} + +namespace nall { + template struct expression { static constexpr bool value = C; }; +} + +namespace nall { + namespace traits { + enum class enable_type {}; + enum class disable_type {}; + + template struct enable_if { using type = T; }; + template struct enable_if {}; + + template struct disable_if { using type = T; }; + template struct disable_if {}; + } + + template using enable_if = typename traits::enable_if::type; + template using disable_if = typename traits::disable_if::type; +} + +namespace nall { + namespace traits { + template struct type_if { using type = T; }; + template struct type_if { using type = F; }; + } + + template using type_if = typename traits::type_if::type; +} diff --git a/unique-pointer.hpp b/unique-pointer.hpp new file mode 100644 index 0000000..fa6b141 --- /dev/null +++ b/unique-pointer.hpp @@ -0,0 +1,102 @@ +#pragma once + +namespace nall { + +template +struct unique_pointer { + using type = T; + T* pointer = nullptr; + function void> deleter; + + unique_pointer(const unique_pointer&) = delete; + auto operator=(const unique_pointer&) -> unique_pointer& = delete; + + unique_pointer(T* pointer = nullptr, const function& deleter = {}) : pointer(pointer), deleter(deleter) {} + ~unique_pointer() { reset(); } + + auto operator=(T* source) -> unique_pointer& { + reset(); + pointer = source; + return *this; + } + + explicit operator bool() const { return pointer; } + + auto operator->() -> T* { return pointer; } + auto operator->() const -> const T* { return pointer; } + + auto operator*() -> T& { return *pointer; } + auto operator*() const -> const T& { return *pointer; } + + auto operator()() -> T& { return *pointer; } + auto operator()() const -> const T& { return *pointer; } + + auto data() -> T* { return pointer; } + auto data() const -> const T* { return pointer; } + + auto release() -> T* { + auto result = pointer; + pointer = nullptr; + return result; + } + + auto reset() -> void { + if(pointer) { + if(deleter) { + deleter(pointer); + } else { + delete pointer; + } + pointer = nullptr; + } + } +}; + +template +struct unique_pointer { + using type = T; + T* pointer = nullptr; + function void> deleter; + + unique_pointer(const unique_pointer&) = delete; + auto operator=(const unique_pointer&) -> unique_pointer& = delete; + + unique_pointer(T* pointer = nullptr, const function& deleter = {}) : pointer(pointer), deleter(deleter) {} + ~unique_pointer() { reset(); } + + auto operator=(T* source) -> unique_pointer& { + reset(); + pointer = source; + return *this; + } + + explicit operator bool() const { return pointer; } + + auto operator()() -> T* { return pointer; } + auto operator()() const -> T* { return pointer; } + + alwaysinline auto operator[](uint offset) -> T& { return pointer[offset]; } + alwaysinline auto operator[](uint offset) const -> const T& { return pointer[offset]; } + + auto data() -> T* { return pointer; } + auto data() const -> const T* { return pointer; } + + auto release() -> T* { + auto result = pointer; + pointer = nullptr; + return result; + } + + auto reset() -> void { + if(pointer) { + if(deleter) { + deleter(pointer); + } else { + delete[] pointer; + } + pointer = nullptr; + } + } +}; + +} diff --git a/utility.hpp b/utility.hpp new file mode 100644 index 0000000..ac1d13e --- /dev/null +++ b/utility.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace nall { + +template struct base_from_member { + base_from_member(T value) : value(value) {} + T value; +}; + +template inline auto allocate(unsigned size, const T& value) -> T* { + T* array = new T[size]; + for(unsigned i = 0; i < size; i++) array[i] = value; + return array; +} + +} diff --git a/varint.hpp b/varint.hpp new file mode 100644 index 0000000..f684bf4 --- /dev/null +++ b/varint.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include + +namespace nall { + +struct varint { + virtual auto read() -> uint8_t = 0; + virtual auto write(uint8_t) -> void = 0; + + auto readvu() -> uintmax_t { + uintmax_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 readvs() -> intmax_t { + uintmax_t data = readvu(); + bool negate = data & 1; + data >>= 1; + if(negate) data = ~data; + return data; + } + + auto writevu(uintmax_t data) -> void { + while(true) { + uint8_t x = data & 0x7f; + data >>= 7; + if(data == 0) return write(0x80 | x); + write(x); + data--; + } + } + + auto writevs(intmax_t data) -> void { + bool negate = data < 0; + if(negate) data = ~data; + data = (data << 1) | negate; + writevu(data); + } +}; + +struct VariadicNatural { + inline VariadicNatural() : mask(~0ull) { assign(0); } + template inline VariadicNatural(const T& value) : mask(~0ull) { assign(value); } + + inline operator uint64_t() const { return data; } + template inline auto& operator=(const T& value) { return assign(value); } + + inline auto operator++(int) { auto value = data; assign(data + 1); return value; } + inline auto operator--(int) { auto value = data; assign(data - 1); return value; } + + inline auto& operator++() { return assign(data + 1); } + inline auto& operator--() { return assign(data - 1); } + + inline auto& operator &=(const uint64_t value) { return assign(data & value); } + inline auto& operator |=(const uint64_t value) { return assign(data | value); } + inline auto& operator ^=(const uint64_t value) { return assign(data ^ value); } + inline auto& operator<<=(const uint64_t value) { return assign(data << value); } + inline auto& operator>>=(const uint64_t value) { return assign(data >> value); } + inline auto& operator +=(const uint64_t value) { return assign(data + value); } + inline auto& operator -=(const uint64_t value) { return assign(data - value); } + inline auto& operator *=(const uint64_t value) { return assign(data * value); } + inline auto& operator /=(const uint64_t value) { return assign(data / value); } + inline auto& operator %=(const uint64_t value) { return assign(data % value); } + + inline auto resize(uint bits) { + assert(bits <= 64); + mask = ~0ull >> (64 - bits); + data &= mask; + } + + inline auto serialize(serializer& s) { + s(data); + s(mask); + } + + struct Reference { + inline Reference(VariadicNatural& self, uint lo, uint hi) : self(self), Lo(lo), Hi(hi) {} + + inline operator uint64_t() const { + const uint64_t RangeBits = Hi - Lo + 1; + const uint64_t RangeMask = (((1ull << RangeBits) - 1) << Lo) & self.mask; + return (self & RangeMask) >> Lo; + } + + inline auto& operator=(const uint64_t value) { + const uint64_t RangeBits = Hi - Lo + 1; + const uint64_t RangeMask = (((1ull << RangeBits) - 1) << Lo) & self.mask; + self.data = (self.data & ~RangeMask) | ((value << Lo) & RangeMask); + return *this; + } + + private: + VariadicNatural& self; + const uint Lo; + const uint 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}; } + +private: + auto assign(uint64_t value) -> VariadicNatural& { + data = value & mask; + return *this; + } + + uint64_t data; + uint64_t mask; +}; + +} diff --git a/vector.hpp b/vector.hpp new file mode 100644 index 0000000..73c7d36 --- /dev/null +++ b/vector.hpp @@ -0,0 +1,282 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + +template struct vector { + struct exception_out_of_bounds{}; + + explicit operator bool() const { return objectsize; } + auto data() -> T* { return pool + poolbase; } + auto data() const -> const T* { return pool + poolbase; } + + auto empty() const -> bool { return objectsize == 0; } + auto size() const -> unsigned { return objectsize; } + auto capacity() const -> unsigned { return poolsize; } + + auto release() -> T* { + T* result = pool + poolbase; + pool = nullptr; + poolbase = 0; + poolsize = 0; + objectsize = 0; + return result; + } + + auto reset() -> void { + if(pool) { + for(unsigned n = 0; n < objectsize; n++) pool[poolbase + n].~T(); + memory::free(pool); + } + pool = nullptr; + poolbase = 0; + poolsize = 0; + objectsize = 0; + } + + auto reserve(unsigned size) -> void { + if(size <= poolsize) return; + size = bit::round(size); //amortize growth + + T* copy = (T*)memory::allocate(size * sizeof(T)); + for(unsigned n = 0; n < objectsize; n++) new(copy + n) T(move(pool[poolbase + n])); + free(pool); + pool = copy; + poolbase = 0; + poolsize = size; + } + + auto resize(unsigned size, T value = T()) -> void { + T* copy = (T*)memory::allocate(size * sizeof(T)); + for(unsigned n = 0; n < size && n < objectsize; n++) new(copy + n) T(move(pool[poolbase + n])); + for(unsigned n = objectsize; n < size; n++) new(copy + n) T(value); + reset(); + pool = copy; + poolbase = 0; + poolsize = size; + objectsize = size; + } + + auto reallocate(unsigned size, T value = T()) -> void { + reset(); + resize(size, value); + } + + template auto prepend(const T& data, Args&&... args) -> void { + prepend(forward(args)...); + prepend(data); + } + + auto prepend(const T& data) -> T& { + reserve(objectsize + 1); + if(poolbase == 0) { + unsigned available = poolsize - objectsize; + poolbase = max(1u, available >> 1); + for(signed n = objectsize - 1; n >= 0; n--) { + pool[poolbase + n] = move(pool[n]); + } + } + new(pool + --poolbase) T(data); + objectsize++; + return first(); + } + + template auto append(const T& data, Args&&... args) -> void { + append(data); + append(forward(args)...); + } + + auto append(const T& data) -> T& { + reserve(poolbase + objectsize + 1); + new(pool + poolbase + objectsize++) T(data); + return last(); + } + + auto appendOnce(const T& data) -> bool { + if(find(data)) return false; + return append(data), true; + } + + auto insert(unsigned position, const T& data) -> void { + if(position == 0) { + prepend(data); + return; + } + append(data); + if(position == ~0u) return; + for(signed n = objectsize - 1; n > position; n--) { + pool[poolbase + n] = move(pool[poolbase + n - 1]); + } + pool[poolbase + position] = data; + } + + auto remove(unsigned position = ~0u, unsigned length = 1) -> void { + if(position == ~0u) position = objectsize - 1; + if(position + length > objectsize) throw exception_out_of_bounds{}; + + if(position == 0) { + for(unsigned n = 0; n < length; n++) pool[poolbase + n].~T(); + poolbase += length; + } else { + for(unsigned n = position; n < objectsize; n++) { + if(n + length < objectsize) { + pool[poolbase + n] = move(pool[poolbase + n + length]); + } else { + pool[poolbase + n].~T(); + } + } + } + objectsize -= length; + } + + auto removeFirst() -> void { return remove(0); } + auto removeLast() -> void { return remove(~0u); } + + auto take(unsigned position = ~0u) -> T { + if(position == ~0u) position = objectsize - 1; + T object = pool[poolbase + position]; + remove(position); + return object; + } + + auto takeFirst() -> T { return take(0); } + auto takeLast() -> T { return take(~0u); } + + auto reverse() -> void { + unsigned pivot = size() / 2; + for(unsigned l = 0, r = size() - 1; l < pivot; l++, r--) { + swap(pool[poolbase + l], pool[poolbase + r]); + } + } + + auto sort(const function& comparator = [](const T& lhs, const T& rhs) -> bool { + return lhs < rhs; + }) -> void { + nall::sort(pool + poolbase, objectsize, comparator); + } + + auto find(const T& data) const -> maybe { + for(unsigned n = 0; n < objectsize; n++) if(pool[poolbase + n] == data) return n; + return nothing; + } + + auto first() -> T& { + if(objectsize == 0) throw exception_out_of_bounds(); + return pool[poolbase]; + } + + auto first() const -> const T& { + if(objectsize == 0) throw exception_out_of_bounds(); + return pool[poolbase]; + } + + auto last() -> T& { + if(objectsize == 0) throw exception_out_of_bounds(); + return pool[poolbase + objectsize - 1]; + } + + auto last() const -> const T& { + if(objectsize == 0) throw exception_out_of_bounds(); + return pool[poolbase + objectsize - 1]; + } + + //access + inline auto operator[](unsigned position) -> T& { + if(position >= objectsize) throw exception_out_of_bounds(); + return pool[poolbase + position]; + } + + inline auto operator[](unsigned position) const -> const T& { + if(position >= objectsize) throw exception_out_of_bounds(); + return pool[poolbase + position]; + } + + inline auto operator()(unsigned position) -> T& { + if(position >= poolsize) reserve(position + 1); + while(position >= objectsize) append(T()); + return pool[poolbase + position]; + } + + inline auto operator()(unsigned position, const T& data) const -> const T& { + if(position >= objectsize) return data; + return pool[poolbase + position]; + } + + //iteration + struct iterator { + iterator(vector& source, unsigned position) : source(source), position(position) {} + auto operator*() -> T& { return source.operator[](position); } + auto operator!=(const iterator& source) const -> bool { return position != source.position; } + auto operator++() -> iterator& { position++; return *this; } + + private: + vector& source; + unsigned position; + }; + + auto begin() -> iterator { return iterator(*this, 0); } + auto end() -> iterator { return iterator(*this, size()); } + + struct constIterator { + constIterator(const vector& source, unsigned position) : source(source), position(position) {} + auto operator*() const -> const T& { return source.operator[](position); } + auto operator!=(const constIterator& source) const -> bool { return position != source.position; } + auto operator++() -> constIterator& { position++; return *this; } + + private: + const vector& source; + unsigned position; + }; + + auto begin() const -> const constIterator { return constIterator(*this, 0); } + auto end() const -> const constIterator { return constIterator(*this, size()); } + + //copy + inline auto operator=(const vector& source) -> vector& { + if(this == &source) return *this; + reset(); + reserve(source.size()); + for(auto& data : source) append(data); + return *this; + } + + //move + inline auto operator=(vector&& source) -> vector& { + if(this == &source) return *this; + reset(); + pool = source.pool; + poolbase = source.poolbase; + poolsize = source.poolsize; + objectsize = source.objectsize; + source.pool = nullptr; + source.poolbase = 0; + source.poolsize = 0; + source.objectsize = 0; + return *this; + } + + //construction and destruction + vector() = default; + vector(initializer_list list) { for(auto& data : list) append(data); } + vector(const vector& source) { operator=(source); } + vector(vector&& source) { operator=(move(source)); } + ~vector() { reset(); } + +protected: + T* pool = nullptr; + unsigned poolbase = 0; + unsigned poolsize = 0; + unsigned objectsize = 0; +}; + +} diff --git a/windows/detour.hpp b/windows/detour.hpp new file mode 100644 index 0000000..6e57a59 --- /dev/null +++ b/windows/detour.hpp @@ -0,0 +1,189 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace nall { + +#define Copy 0 +#define RelNear 1 + +struct detour { + static auto insert(const string& moduleName, const string& functionName, void*& source, void* target) -> bool; + static auto remove(const string& moduleName, const string& functionName, void*& source) -> bool; + +protected: + static auto length(const uint8* function) -> uint; + static auto mirror(uint8* target, const uint8* source) -> uint; + + struct opcode { + uint16 prefix; + uint length; + uint mode; + uint16 modify; + }; + static opcode opcodes[]; +}; + +//TODO: +//* fs:, gs: should force another opcode copy +//* conditional branches within +5-byte range should fail +detour::opcode detour::opcodes[] = { + {0x50, 1}, //push eax + {0x51, 1}, //push ecx + {0x52, 1}, //push edx + {0x53, 1}, //push ebx + {0x54, 1}, //push esp + {0x55, 1}, //push ebp + {0x56, 1}, //push esi + {0x57, 1}, //push edi + {0x58, 1}, //pop eax + {0x59, 1}, //pop ecx + {0x5a, 1}, //pop edx + {0x5b, 1}, //pop ebx + {0x5c, 1}, //pop esp + {0x5d, 1}, //pop ebp + {0x5e, 1}, //pop esi + {0x5f, 1}, //pop edi + {0x64, 1}, //fs: + {0x65, 1}, //gs: + {0x68, 5}, //push dword + {0x6a, 2}, //push byte + {0x74, 2, RelNear, 0x0f84}, //je near -> je far + {0x75, 2, RelNear, 0x0f85}, //jne near -> jne far + {0x89, 2}, //mov reg,reg + {0x8b, 2}, //mov reg,reg + {0x90, 1}, //nop + {0xa1, 5}, //mov eax,[dword] + {0xeb, 2, RelNear, 0xe9}, //jmp near -> jmp far +}; + +auto detour::insert(const string& moduleName, const string& functionName, void*& source, void* target) -> bool { + HMODULE module = GetModuleHandleW(utf16_t(moduleName)); + if(!module) return false; + + uint8* sourceData = (uint8_t*)GetProcAddress(module, functionName); + if(!sourceData) return false; + + uint sourceLength = detour::length(sourceData); + if(sourceLength < 5) { + //unable to clone enough bytes to insert hook + #if 1 + string output = {"detour::insert(", moduleName, "::", functionName, ") failed: "}; + for(uint n = 0; n < 16; n++) output.append(hex<2>(sourceData[n]), " "); + output.rtrim(" ", 1L); + MessageBoxA(0, output, "nall::detour", MB_OK); + #endif + return false; + } + + auto mirrorData = new uint8[512](); + detour::mirror(mirrorData, sourceData); + + DWORD privileges; + VirtualProtect((void*)mirrorData, 512, PAGE_EXECUTE_READWRITE, &privileges); + VirtualProtect((void*)sourceData, 256, PAGE_EXECUTE_READWRITE, &privileges); + uintmax address = (uintmax)target - ((uintmax)sourceData + 5); + sourceData[0] = 0xe9; //jmp target + sourceData[1] = address >> 0; + sourceData[2] = address >> 8; + sourceData[3] = address >> 16; + sourceData[4] = address >> 24; + VirtualProtect((void*)sourceData, 256, privileges, &privileges); + + source = (void*)mirrorData; + return true; +} + +auto detour::remove(const string& moduleName, const string& functionName, void*& source) -> bool { + HMODULE module = GetModuleHandleW(utf16_t(moduleName)); + if(!module) return false; + + auto sourceData = (uint8*)GetProcAddress(module, functionName); + if(!sourceData) return false; + + auto mirrorData = (uint8*)source; + if(mirrorData == sourceData) return false; //hook was never installed + + uint length = detour::length(256 + mirrorData); + if(length < 5) return false; + + DWORD privileges; + VirtualProtect((void*)sourceData, 256, PAGE_EXECUTE_READWRITE, &privileges); + for(uint n = 0; n < length; n++) sourceData[n] = mirrorData[256 + n]; + VirtualProtect((void*)sourceData, 256, privileges, &privileges); + + source = (void*)sourceData; + delete[] mirrorData; + return true; +} + +auto detour::length(const uint8* function) -> uint { + uint length = 0; + while(length < 5) { + detour::opcode *opcode = 0; + foreach(op, detour::opcodes) { + if(function[length] == op.prefix) { + opcode = &op; + break; + } + } + if(opcode == 0) break; + length += opcode->length; + } + return length; +} + +auto detour::mirror(uint8* target, const uint8* source) -> uint { + const uint8* entryPoint = source; + for(uint n = 0; n < 256; n++) target[256 + n] = source[n]; + + uint size = detour::length(source); + while(size) { + detour::opcode* opcode = nullptr; + foreach(op, detour::opcodes) { + if(*source == op.prefix) { + opcode = &op; + break; + } + } + + switch(opcode->mode) { + case Copy: + for(uint n = 0; n < opcode->length; n++) *target++ = *source++; + break; + case RelNear: { + source++; + uintmax sourceAddress = (uintmax)source + 1 + (int8)*source; + *target++ = opcode->modify; + if(opcode->modify >> 8) *target++ = opcode->modify >> 8; + uintmax targetAddress = (uintmax)target + 4; + uintmax address = sourceAddress - targetAddress; + *target++ = address >> 0; + *target++ = address >> 8; + *target++ = address >> 16; + *target++ = address >> 24; + source += 2; + } break; + } + + size -= opcode->length; + } + + uintmax address = (entryPoint + detour::length(entryPoint)) - (target + 5); + *target++ = 0xe9; //jmp entryPoint + *target++ = address >> 0; + *target++ = address >> 8; + *target++ = address >> 16; + *target++ = address >> 24; + + return source - entryPoint; +} + +#undef Implied +#undef RelNear + +} diff --git a/windows/guard.hpp b/windows/guard.hpp new file mode 100644 index 0000000..60c9269 --- /dev/null +++ b/windows/guard.hpp @@ -0,0 +1,13 @@ +#ifndef NALL_WINDOWS_GUARD_HPP +#define NALL_WINDOWS_GUARD_HPP + +#define boolean WindowsBoolean +#define interface WindowsInterface + +#else +#undef NALL_WINDOWS_GUARD_HPP + +#undef boolean +#undef interface + +#endif diff --git a/windows/guid.hpp b/windows/guid.hpp new file mode 100644 index 0000000..056c7ae --- /dev/null +++ b/windows/guid.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace nall { + +//generate unique GUID +inline auto guid() -> string { + LinearFeedbackShiftRegisterGenerator lfsr; + lfsr.seed(time(nullptr)); + for(uint n = 0; n < 256; n++) lfsr(); + + string output; + for(uint n = 0; n < 4; n++) output.append(hex(lfsr(), 2L)); + output.append("-"); + for(uint n = 0; n < 2; n++) output.append(hex(lfsr(), 2L)); + output.append("-"); + for(uint n = 0; n < 2; n++) output.append(hex(lfsr(), 2L)); + output.append("-"); + for(uint n = 0; n < 2; n++) output.append(hex(lfsr(), 2L)); + output.append("-"); + for(uint n = 0; n < 6; n++) output.append(hex(lfsr(), 2L)); + return {"{", output, "}"}; +} + +} diff --git a/windows/launcher.hpp b/windows/launcher.hpp new file mode 100644 index 0000000..1ae13c3 --- /dev/null +++ b/windows/launcher.hpp @@ -0,0 +1,91 @@ +#pragma once + +namespace nall { + +//launch a new process and inject specified DLL into it + +auto launch(const char* applicationName, const char* libraryName, uint32 entryPoint) -> bool { + //if a launcher does not send at least one message, a wait cursor will appear + PostThreadMessage(GetCurrentThreadId(), WM_USER, 0, 0); + MSG msg; + GetMessage(&msg, 0, 0, 0); + + STARTUPINFOW si; + PROCESS_INFORMATION pi; + + memset(&si, 0, sizeof(STARTUPINFOW)); + BOOL result = CreateProcessW( + utf16_t(applicationName), GetCommandLineW(), NULL, NULL, TRUE, + DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS, //do not break if application creates its own processes + NULL, NULL, &si, &pi + ); + if(result == false) return false; + + uint8 entryData[1024], entryHook[1024] = { + 0x68, 0x00, 0x00, 0x00, 0x00, //push libraryName + 0xb8, 0x00, 0x00, 0x00, 0x00, //mov eax,LoadLibraryW + 0xff, 0xd0, //call eax + 0xcd, 0x03, //int 3 + }; + + entryHook[1] = (uint8)((entryPoint + 14) >> 0); + entryHook[2] = (uint8)((entryPoint + 14) >> 8); + entryHook[3] = (uint8)((entryPoint + 14) >> 16); + entryHook[4] = (uint8)((entryPoint + 14) >> 24); + + auto pLoadLibraryW = (uint32)GetProcAddress(GetModuleHandleW(L"kernel32"), "LoadLibraryW"); + entryHook[6] = pLoadLibraryW >> 0; + entryHook[7] = pLoadLibraryW >> 8; + entryHook[8] = pLoadLibraryW >> 16; + entryHook[9] = pLoadLibraryW >> 24; + + utf16_t buffer = utf16_t(libraryName); + memcpy(entryHook + 14, buffer, 2 * wcslen(buffer) + 2); + + while(true) { + DEBUG_EVENT event; + WaitForDebugEvent(&event, INFINITE); + + if(event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) break; + + if(event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) { + if(event.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) { + if(event.u.Exception.ExceptionRecord.ExceptionAddress == (void*)(entryPoint + 14 - 1)) { + HANDLE hProcess = OpenProcess(0, FALSE, event.dwProcessId); + HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, event.dwThreadId); + + CONTEXT context; + context.ContextFlags = CONTEXT_FULL; + GetThreadContext(hThread, &context); + + WriteProcessMemory(pi.hProcess, (void*)entryPoint, (void*)&entryData, sizeof entryData, NULL); + context.Eip = entryPoint; + SetThreadContext(hThread, &context); + + CloseHandle(hThread); + CloseHandle(hProcess); + } + + ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_CONTINUE); + continue; + } + + ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); + continue; + } + + if(event.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) { + ReadProcessMemory(pi.hProcess, (void*)entryPoint, (void*)&entryData, sizeof entryData, NULL); + WriteProcessMemory(pi.hProcess, (void*)entryPoint, (void*)&entryHook, sizeof entryHook, NULL); + + ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_CONTINUE); + continue; + } + + ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_CONTINUE); + } + + return true; +} + +} diff --git a/windows/registry.hpp b/windows/registry.hpp new file mode 100644 index 0000000..cd5767d --- /dev/null +++ b/windows/registry.hpp @@ -0,0 +1,118 @@ +#pragma once + +#include +#include + +#include +#undef interface +#ifndef KEY_WOW64_64KEY + #define KEY_WOW64_64KEY 0x0100 +#endif +#ifndef KEY_WOW64_32KEY + #define KEY_WOW64_32KEY 0x0200 +#endif + +#ifndef NWR_FLAGS + #define NWR_FLAGS KEY_WOW64_64KEY +#endif + +#ifndef NWR_SIZE + #define NWR_SIZE 4096 +#endif + +namespace nall { + +struct registry { + static auto exists(const string& name) -> bool { + lstring part = name.split("/"); + HKEY handle, rootKey = root(part.take(0)); + string node = part.take(); + string path = part.merge("\\"); + if(RegOpenKeyExW(rootKey, utf16_t(path), 0, NWR_FLAGS | KEY_READ, &handle) == ERROR_SUCCESS) { + wchar_t data[NWR_SIZE] = L""; + DWORD size = NWR_SIZE * sizeof(wchar_t); + LONG result = RegQueryValueExW(handle, utf16_t(node), nullptr, nullptr, (LPBYTE)&data, (LPDWORD)&size); + RegCloseKey(handle); + if(result == ERROR_SUCCESS) return true; + } + return false; + } + + static auto read(const string& name) -> string { + lstring part = name.split("/"); + HKEY handle, rootKey = root(part.take(0)); + string node = part.take(); + string path = part.merge("\\"); + if(RegOpenKeyExW(rootKey, utf16_t(path), 0, NWR_FLAGS | KEY_READ, &handle) == ERROR_SUCCESS) { + wchar_t data[NWR_SIZE] = L""; + DWORD size = NWR_SIZE * sizeof(wchar_t); + LONG result = RegQueryValueExW(handle, utf16_t(node), nullptr, nullptr, (LPBYTE)&data, (LPDWORD)&size); + RegCloseKey(handle); + if(result == ERROR_SUCCESS) return (const char*)utf8_t(data); + } + return ""; + } + + static auto write(const string& name, const string& data = "") -> void { + lstring part = name.split("/"); + HKEY handle, rootKey = root(part.take(0)); + string node = part.take(), path; + DWORD disposition; + for(uint n = 0; n < part.size(); n++) { + path.append(part[n]); + if(RegCreateKeyExW(rootKey, utf16_t(path), 0, nullptr, 0, NWR_FLAGS | KEY_ALL_ACCESS, nullptr, &handle, &disposition) == ERROR_SUCCESS) { + if(n == part.size() - 1) { + RegSetValueExW(handle, utf16_t(node), 0, REG_SZ, (BYTE*)(wchar_t*)utf16_t(data), (data.length() + 1) * sizeof(wchar_t)); + } + RegCloseKey(handle); + } + path.append("\\"); + } + } + + static auto remove(const string& name) -> bool { + lstring part = name.split("/"); + HKEY rootKey = root(part.take(0)); + string node = part.take(); + string path = part.merge("\\"); + if(node.empty()) return SHDeleteKeyW(rootKey, utf16_t(path)) == ERROR_SUCCESS; + return SHDeleteValueW(rootKey, utf16_t(path), utf16_t(node)) == ERROR_SUCCESS; + } + + static auto contents(const string& name) -> lstring { + lstring part = name.split("/"), result; + HKEY handle, rootKey = root(part.take(0)); + part.remove(); + string path = part.merge("\\"); + if(RegOpenKeyExW(rootKey, utf16_t(path), 0, NWR_FLAGS | KEY_READ, &handle) == ERROR_SUCCESS) { + DWORD folders, nodes; + RegQueryInfoKey(handle, nullptr, nullptr, nullptr, &folders, nullptr, nullptr, &nodes, nullptr, nullptr, nullptr, nullptr); + for(uint n = 0; n < folders; n++) { + wchar_t name[NWR_SIZE] = L""; + DWORD size = NWR_SIZE * sizeof(wchar_t); + RegEnumKeyEx(handle, n, (wchar_t*)&name, &size, nullptr, nullptr, nullptr, nullptr); + result.append(string{(const char*)utf8_t(name), "/"}); + } + for(uint n = 0; n < nodes; n++) { + wchar_t name[NWR_SIZE] = L""; + DWORD size = NWR_SIZE * sizeof(wchar_t); + RegEnumValueW(handle, n, (wchar_t*)&name, &size, nullptr, nullptr, nullptr, nullptr); + result.append((const char*)utf8_t(name)); + } + RegCloseKey(handle); + } + return result; + } + +private: + static auto root(const string& name) -> HKEY { + if(name == "HKCR") return HKEY_CLASSES_ROOT; + if(name == "HKCC") return HKEY_CURRENT_CONFIG; + if(name == "HKCU") return HKEY_CURRENT_USER; + if(name == "HKLM") return HKEY_LOCAL_MACHINE; + if(name == "HKU" ) return HKEY_USERS; + return nullptr; + } +}; + +} diff --git a/windows/service.hpp b/windows/service.hpp new file mode 100644 index 0000000..fa5d87f --- /dev/null +++ b/windows/service.hpp @@ -0,0 +1,13 @@ +#pragma once + +namespace nall { + +struct service { + explicit operator bool() const { return false; } + auto command(const string& name, const string& command) -> bool { return false; } + auto receive() -> string { return ""; } + auto name() const -> string { return ""; } + auto stop() const -> bool { return false; } +}; + +} diff --git a/windows/shared-memory.hpp b/windows/shared-memory.hpp new file mode 100644 index 0000000..aa45938 --- /dev/null +++ b/windows/shared-memory.hpp @@ -0,0 +1,27 @@ +#pragma once + +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 false; } + auto empty() const -> bool { return true; } + auto size() const -> uint { return 0; } + auto acquired() const -> bool { return false; } + auto acquire() -> uint8* { return nullptr; } + auto release() -> void {} + auto reset() -> void {} + auto create(const string& name, uint size) -> bool { return false; } + auto remove() -> void {} + auto open(const string& name, uint size) -> bool { return false; } + auto close() -> void {} +}; + +} diff --git a/windows/utf8.hpp b/windows/utf8.hpp new file mode 100644 index 0000000..9944a23 --- /dev/null +++ b/windows/utf8.hpp @@ -0,0 +1,88 @@ +#pragma once + +//UTF-8 <> UTF-16 conversion +//used only for Win32; every other OS uses UTF-8 internally + +#if defined(_WIN32) + +#undef UNICODE +#define UNICODE +#undef NOMINMAX +#define NOMINMAX + +#include +#include +#include +#include + +#if !defined(PATH_MAX) + #define PATH_MAX 260 +#endif + +using uint = unsigned; + +namespace nall { + //UTF-8 to UTF-16 + struct utf16_t { + utf16_t(const char* s = "") { + if(!s) s = ""; + uint length = MultiByteToWideChar(CP_UTF8, 0, s, -1, nullptr, 0); + buffer = new wchar_t[length + 1](); + MultiByteToWideChar(CP_UTF8, 0, s, -1, buffer, length); + } + + ~utf16_t() { + delete[] buffer; + } + + operator wchar_t*() { + return buffer; + } + + operator const wchar_t*() const { + return buffer; + } + + private: + wchar_t* buffer = nullptr; + }; + + //UTF-16 to UTF-8 + struct utf8_t { + utf8_t(const wchar_t* s = L"") { + if(!s) s = L""; + uint length = WideCharToMultiByte(CP_UTF8, 0, s, -1, nullptr, 0, nullptr, nullptr); + buffer = new char[length + 1](); + WideCharToMultiByte(CP_UTF8, 0, s, -1, buffer, length, nullptr, nullptr); + } + + ~utf8_t() { + delete[] buffer; + } + + utf8_t(const utf8_t&) = delete; + utf8_t& operator=(const utf8_t&) = delete; + + operator char*() { + return buffer; + } + + operator const char*() const { + return buffer; + } + + private: + char* buffer = nullptr; + }; + + inline auto utf8_args(int& argc, char**& argv) -> void { + wchar_t** wargv = CommandLineToArgvW(GetCommandLineW(), &argc); + argv = new char*[argc]; + for(uint i = 0; i < argc; i++) { + argv[i] = new char[PATH_MAX]; + strcpy(argv[i], nall::utf8_t(wargv[i])); + } + } +} + +#endif //if defined(_WIN32) diff --git a/xorg/guard.hpp b/xorg/guard.hpp new file mode 100644 index 0000000..9b52886 --- /dev/null +++ b/xorg/guard.hpp @@ -0,0 +1,31 @@ +#ifndef NALL_XORG_GUARD_HPP +#define NALL_XORG_GUARD_HPP + +#define None +#undef XlibNone +#define XlibNone 0L +#define Bool XlibBool +#define Button1 XlibButton1 +#define Button2 XlibButton2 +#define Button3 XlibButton3 +#define Button4 XlibButton4 +#define Button5 XlibButton5 +#define Display XlibDisplay +#define Screen XlibScreen +#define Window XlibWindow + +#else +#undef NALL_XORG_GUARD_HPP + +#undef None +#undef Bool +#undef Button1 +#undef Button2 +#undef Button3 +#undef Button4 +#undef Button5 +#undef Display +#undef Screen +#undef Window + +#endif diff --git a/xorg/xorg.hpp b/xorg/xorg.hpp new file mode 100644 index 0000000..56ff316 --- /dev/null +++ b/xorg/xorg.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include