Checkpoint 1: implement native subsystems and begin the gameplay manual

This commit is contained in:
Emil
2026-09-18 03:01:30 +03:00
parent decf49084d
commit 903c97444b
73 changed files with 3932 additions and 6 deletions
+72
View File
@@ -0,0 +1,72 @@
#include <faset/core/hash.hpp>
#include <faset/core/error.hpp>
#include <array>
#include <bit>
#include <cstdint>
#include <fstream>
#include <vector>
namespace faset {
namespace {
constexpr std::array<std::uint32_t,64> constants = {
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
};
class Digest {
std::array<std::uint32_t,8> state_{0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19};
std::array<std::uint8_t,64> pending_{};
std::uint64_t count_=0;
std::size_t used_=0;
void block() {
std::array<std::uint32_t,64> w{};
for (int i=0;i<16;++i) w[i]=(std::uint32_t(pending_[i*4])<<24)|(std::uint32_t(pending_[i*4+1])<<16)|(std::uint32_t(pending_[i*4+2])<<8)|pending_[i*4+3];
for (int i=16;i<64;++i) {
const auto x=w[i-15],y=w[i-2];
w[i]=w[i-16]+(std::rotr(x,7)^std::rotr(x,18)^(x>>3))+w[i-7]+(std::rotr(y,17)^std::rotr(y,19)^(y>>10));
}
auto a=state_[0],b=state_[1],c=state_[2],d=state_[3],e=state_[4],f=state_[5],g=state_[6],h=state_[7];
for (int i=0;i<64;++i) {
const auto t1=h+(std::rotr(e,6)^std::rotr(e,11)^std::rotr(e,25))+((e&f)^(~e&g))+constants[i]+w[i];
const auto t2=(std::rotr(a,2)^std::rotr(a,13)^std::rotr(a,22))+((a&b)^(a&c)^(b&c));
h=g;g=f;f=e;e=d+t1;d=c;c=b;b=a;a=t1+t2;
}
state_[0]+=a;state_[1]+=b;state_[2]+=c;state_[3]+=d;state_[4]+=e;state_[5]+=f;state_[6]+=g;state_[7]+=h;
}
public:
void update(std::span<const std::byte> bytes) {
count_+=bytes.size();
for (auto byte:bytes) {
pending_[used_++]=std::to_integer<std::uint8_t>(byte);
if (used_==64) { block();used_=0; }
}
}
std::string finish() {
const std::uint64_t bits=count_*8;
pending_[used_++]=0x80;
if (used_>56) { while(used_<64) pending_[used_++]=0;block();used_=0; }
while(used_<56) pending_[used_++]=0;
for (int i=7;i>=0;--i) pending_[used_++]=std::uint8_t(bits>>(i*8));
block();
constexpr char hex[]="0123456789abcdef";
std::string result;result.reserve(64);
for (auto word:state_) for (int i=7;i>=0;--i) result+=hex[(word>>(i*4))&15];
return result;
}
};
}
std::string sha256(std::span<const std::byte> bytes) { Digest digest;digest.update(bytes);return digest.finish(); }
std::string sha256_file(const std::filesystem::path& path) {
std::ifstream stream(path,std::ios::binary);
require(bool(stream),"io.open","Cannot open file for hashing: "+path.string());
Digest digest;std::array<char,65536> buffer{};
while(stream) { stream.read(buffer.data(),buffer.size());digest.update(std::as_bytes(std::span(buffer.data(),static_cast<std::size_t>(stream.gcount())))); }
require(stream.eof(),"io.read","Cannot read file for hashing: "+path.string());
return digest.finish();
}
}
+72
View File
@@ -0,0 +1,72 @@
#include <faset/core/io.hpp>
#include <faset/core/error.hpp>
#include <array>
#include <fstream>
#include <random>
#include <mutex>
#ifdef _WIN32
#define NOMINMAX
#include <windows.h>
#else
#include <fcntl.h>
#include <unistd.h>
#endif
namespace faset {
std::string new_id() {
static std::mutex mutex;
static std::random_device random;
std::array<unsigned char,16> bytes{};
{ std::lock_guard lock(mutex); for(auto& byte:bytes) byte=static_cast<unsigned char>(random()); }
bytes[6]=(bytes[6]&0x0f)|0x40;bytes[8]=(bytes[8]&0x3f)|0x80;
constexpr char hex[]="0123456789abcdef";
std::string result;result.reserve(36);
for(std::size_t i=0;i<bytes.size();++i) { if(i==4||i==6||i==8||i==10)result+='-';result+=hex[bytes[i]>>4];result+=hex[bytes[i]&15]; }
return result;
}
std::string read_text(const std::filesystem::path& path) {
std::ifstream stream(path,std::ios::binary);
require(bool(stream),"io.open","Cannot open file: "+path.string());
std::string value((std::istreambuf_iterator<char>(stream)),{});
require(!stream.bad(),"io.read","Cannot read file: "+path.string());return value;
}
Json read_json(const std::filesystem::path& path) {
try { return Json::parse(read_text(path)); }
catch(const Json::exception& error) { throw Error("format.json","Invalid JSON in "+path.string(),{{"reason",error.what()}}); }
}
void atomic_write(const std::filesystem::path& path,std::string_view bytes) {
const auto parent=path.has_parent_path()?path.parent_path():std::filesystem::path(".");
std::filesystem::create_directories(parent);
const auto temporary=parent/(path.filename().string()+".tmp-"+new_id());
try {
#ifdef _WIN32
HANDLE file=CreateFileW(temporary.c_str(),GENERIC_WRITE,0,nullptr,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,nullptr);
require(file!=INVALID_HANDLE_VALUE,"io.create","Cannot create temporary file");
bool ok=true;std::size_t offset=0;
while(offset<bytes.size()) { DWORD written=0; const auto count=static_cast<DWORD>(std::min<std::size_t>(bytes.size()-offset,1u<<30)); if(!WriteFile(file,bytes.data()+offset,count,&written,nullptr)||written==0){ok=false;break;} offset+=written; }
ok=FlushFileBuffers(file)&&ok;CloseHandle(file);
require(ok,"io.write","Cannot flush temporary file");
require(MoveFileExW(temporary.c_str(),path.c_str(),MOVEFILE_REPLACE_EXISTING|MOVEFILE_WRITE_THROUGH)!=0,"io.replace","Cannot publish file: "+path.string());
#else
const int fd=::open(temporary.c_str(),O_WRONLY|O_CREAT|O_EXCL,0644);
require(fd>=0,"io.create","Cannot create temporary file");
bool ok=true;std::size_t offset=0;
while(offset<bytes.size()) { const auto count=::write(fd,bytes.data()+offset,bytes.size()-offset);if(count<0&&errno==EINTR)continue;if(count<=0){ok=false;break;}offset+=static_cast<std::size_t>(count); }
ok=(::fsync(fd)==0)&&ok;const auto closed=::close(fd);ok=ok&&(closed==0);
require(ok,"io.write","Cannot flush temporary file");
std::filesystem::rename(temporary,path);
const int directory=::open(parent.c_str(),O_RDONLY|O_DIRECTORY);
if(directory>=0){::fsync(directory);::close(directory);}
#endif
} catch(...) { std::error_code ignored;std::filesystem::remove(temporary,ignored);throw; }
}
void atomic_write_json(const std::filesystem::path& path,const Json& value) { atomic_write(path,value.dump(2)+"\n"); }
std::filesystem::path project_path(const std::filesystem::path& root,const std::filesystem::path& relative) {
require(!relative.is_absolute(),"path.outside_project","Expected a path relative to the project");
const auto canonical=std::filesystem::weakly_canonical(root);
const auto target=std::filesystem::weakly_canonical(canonical/relative);
auto a=canonical.begin(),b=target.begin();
for(;a!=canonical.end();++a,++b) require(b!=target.end()&&*a==*b,"path.outside_project","Path escapes the project root");
return target;
}
}