#include "CrossLang.hpp" #include namespace Tesses::CrossLang { void CrossArchiveCreate( std::shared_ptr vfs, std::shared_ptr strm, std::string name, TVMVersion version, std::string info, std::string icon) { std::vector ignored_files; std::string file = "/.crossarchiveignore"; if (vfs->FileExists(file)) { auto strm = vfs->OpenFile(file, "rb"); if (strm != nullptr) { Tesses::Framework::TextStreams::StreamReader reader(strm); std::string ignores; while (reader.ReadLine(ignores)) { if (!ignores.empty() && ignores[0] != '#') { ignored_files.push_back(ignores); } ignores.clear(); } } } static std::vector error_message_byte_code = { PUSHSTRING, 0, 0, 0, 2, GETVARIABLE, PUSHSTRING, 0, 0, 0, 3, PUSHSTRING, 0, 0, 0, 4, PUSHLONG, 0, 0, 0, 0, 0, 0, 0, 1, CALLMETHOD, RET}; auto writeInt = [](std::shared_ptr strm, uint32_t number) -> void { uint8_t buff[4]; BitConverter::FromUint32BE(buff[0], number); strm->WriteBlock(buff, 4); }; auto writeStr = [&writeInt](std::shared_ptr strm, std::string text) -> void { writeInt(strm, (uint32_t)text.size()); strm->WriteBlock((const uint8_t *)text.c_str(), text.size()); }; std::vector strs; strs.push_back(name); strs.push_back(info); strs.push_back("Console"); strs.push_back("WriteLine"); strs.push_back("You are trying to run a crvm archive, stop it you won't " "get anywhere!\nUse crosslang archiveextract instead."); std::vector resources; auto ensureResource = [&resources](std::string path) -> uint32_t { for (uint32_t i = 0; i < (uint32_t)resources.size(); i++) { if (resources[i] == path) return i; } uint32_t index = (uint32_t)resources.size(); resources.push_back(path); return index; }; auto ensureString = [&strs](std::string str) -> uint32_t { for (uint32_t i = 0; i < (uint32_t)strs.size(); i++) { if (strs[i] == str) return i; } uint32_t index = (uint32_t)strs.size(); strs.push_back(str); return index; }; auto ms = std::make_shared(true); std::function walkFS = [&ignored_files, vfs, &ensureString, &ensureResource, &ms, &walkFS, &writeInt](Tesses::Framework::Filesystem::VFSPath path) -> void { if (vfs->DirectoryExists(path)) { ms->WriteByte(1); std::vector paths; for (auto item : vfs->EnumeratePaths(path)) { if (!item.relative && item.path.size() == 1 && item.path[0] == "__resdir_tmp") continue; if (!item.relative && item.path.size() == 1 && item.path[0] == ".crossarchiveignore") continue; std::string filename = item.GetFileName(); bool shallIgnore = false; for (auto ign : ignored_files) if (ign == filename) { shallIgnore = true; break; } if (shallIgnore) continue; if (vfs->DirectoryExists(item) || vfs->RegularFileExists(item)) paths.push_back(item.GetFileName()); } writeInt(ms, (uint32_t)paths.size()); for (auto item : paths) { writeInt(ms, ensureString(item)); walkFS(path / item); } } else if (vfs->RegularFileExists(path)) { ms->WriteByte(0); writeInt(ms, ensureResource(path.ToString())); } }; walkFS(std::string("/")); if (!icon.empty()) ensureResource(icon); uint8_t main_header[18]; memcpy(main_header, "TCROSSVM", 8); TVMVersion rtVersion(CROSSLANG_BYTECODE_MAJOR, CROSSLANG_BYTECODE_MINOR, CROSSLANG_BYTECODE_PATCH, CROSSLANG_BYTECODE_BUILD, CROSSLANG_BYTECODE_VERSIONSTAGE); rtVersion.ToArray(main_header + 8); version.ToArray(main_header + 13); strm->WriteBlock(main_header, sizeof(main_header)); writeInt(strm, (uint32_t)((icon.empty() ? 5 : 6) + resources.size())); strm->WriteBlock((const uint8_t *)"STRS", 4); uint32_t sz = 4; for (auto str : strs) sz += (uint32_t)(4 + str.size()); writeInt(strm, sz); writeInt(strm, (uint32_t)strs.size()); for (auto str : strs) { writeStr(strm, str); } strm->WriteBlock((const uint8_t *)"NAME", 4); writeInt(strm, 4); writeInt(strm, 0); strm->WriteBlock((const uint8_t *)"INFO", 4); writeInt(strm, 4); writeInt(strm, 1); if (!icon.empty()) { strm->WriteBlock((const uint8_t *)"ICON", 4); writeInt(strm, 4); writeInt(strm, ensureResource(icon)); } strm->WriteBlock((const uint8_t *)"CHKS", 4); writeInt(strm, (uint32_t)(12 + error_message_byte_code.size())); writeInt(strm, 1); writeInt(strm, 0); writeInt(strm, (uint32_t)error_message_byte_code.size()); strm->WriteBlock(error_message_byte_code.data(), error_message_byte_code.size()); for (auto res : resources) { strm->WriteBlock((const uint8_t *)"RESO", 4); auto strm2 = vfs->OpenFile(res, "rb"); writeInt(strm, (uint32_t)strm2->GetLength()); strm2->CopyTo(strm); } strm->WriteBlock((const uint8_t *)"ARCV", 4); writeInt(strm, (uint32_t)ms->GetLength()); ms->Seek(0, Tesses::Framework::Streams::SeekOrigin::Begin); ms->CopyTo(strm); } std::pair, std::string> CrossArchiveExtract(std::shared_ptr strm, std::shared_ptr vfs) { auto ensure = [strm](uint8_t *buffer, size_t count) -> void { size_t read = strm->ReadBlock(buffer, count); if (read < count) throw VMException( "End of file, could not read " + std::to_string((int64_t)count) + " byte(s)., offset=" + std::to_string(strm->GetLength())); }; auto ensureInt = [&ensure]() -> uint32_t { uint8_t buffer[4]; ensure(buffer, 4); return BitConverter::ToUint32BE(buffer[0]); }; auto ensureStr = [&ensure, &ensureInt]() -> std::string { auto len = ensureInt(); if (len == 0) return {}; std::string str = {}; str.resize((size_t)len); ensure((uint8_t *)str.data(), str.size()); return str; }; std::vector strs; auto getStr = [&ensure, &ensureInt, &strs]() -> std::string { auto index = ensureInt(); if (index > strs.size()) throw VMException("String does not exist in TCrossVM file, " "expected string index: " + std::to_string(index) + ", total strings: " + std::to_string(strs.size())); return strs[index]; }; uint8_t main_header[18]; ensure(main_header, sizeof(main_header)); if (strncmp((const char *)main_header, "TCROSSVM", 8) != 0) throw VMException("Invalid TCrossVM image."); TVMVersion version(main_header + 8); if (version.CompareToRuntime() == 1) { throw VMException("Runtime is too old."); } TVMVersion v2(main_header + 13); std::string name; std::string info; size_t _len = (size_t)ensureInt(); char table_name[4]; uint32_t resource_id = 0; Tesses::Framework::Filesystem::VFSPath tmpDir("/__resdir_tmp"); vfs->CreateDirectory(tmpDir); for (size_t i = 0; i < _len; i++) { ensure((uint8_t *)table_name, sizeof(table_name)); size_t tableLen = (size_t)ensureInt(); if (strncmp(table_name, "NAME", 4) == 0) { name = getStr(); } else if (strncmp(table_name, "INFO", 4) == 0) { info = getStr(); } else if (strncmp(table_name, "RESO", 4) == 0) // resources (using embed) { auto path = tmpDir / std::to_string(resource_id); auto strm2 = vfs->OpenFile(path, "wb"); size_t read = 0; size_t offset = 0; uint8_t buff[1024]; do { read = std::min(std::min(tableLen - offset, tableLen), sizeof(buff)); read = strm->Read(buff, read); if (read > 0) strm2->WriteBlock(buff, read); offset += read; } while (read > 0); resource_id++; } else if (strncmp(table_name, "STRS", 4) == 0) // strings { size_t strsLen = (size_t)ensureInt(); for (size_t j = 0; j < strsLen; j++) { strs.push_back(ensureStr()); } } else if (strncmp(table_name, "ARCV", 4) == 0) { size_t offset = 0; std::function walkEntry = [strm, vfs, &getStr, &ensureInt, &tmpDir, &tableLen, &offset, &walkEntry]( Tesses::Framework::Filesystem::VFSPath path) -> void { if (offset + 1 > tableLen) return; uint8_t type = strm->ReadByte(); offset++; if (type == 1) { // ISDIR vfs->CreateDirectory(path); if (offset + 4 > tableLen) return; uint32_t count = ensureInt(); offset += 4; for (uint32_t i = 0; i < count; i++) { if (offset + 4 > tableLen) return; std::string name = getStr(); offset += 4; walkEntry(path / name); } } else if (type == 0) { if (offset + 4 > tableLen) return; uint32_t index = ensureInt(); auto fSrc = tmpDir / std::to_string(index); vfs->MoveFile(fSrc, path); } }; walkEntry(std::string("/")); } else { if (strm->CanSeek()) { strm->Seek((int64_t)tableLen, Tesses::Framework::Streams::SeekOrigin::Current); } else { uint8_t *buffer = new uint8_t[tableLen]; ensure(buffer, tableLen); delete[] buffer; } } } vfs->DeleteDirectoryRecurse(tmpDir); return std::pair, std::string>( std::pair(name, v2), info); } } // namespace Tesses::CrossLang