diff options
| author | Xavier Del Campo Romero <xavi92@disroot.org> | 2025-12-04 13:58:02 +0100 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi92@disroot.org> | 2025-12-15 23:04:39 +0100 |
| commit | 600ff28dd73f2cf17725382b68a4b1b2573f2e34 (patch) | |
| tree | a105c686458d8998438652aeca6299cf9000edcd /src/rez | |
Diffstat (limited to 'src/rez')
| -rw-r--r-- | src/rez/CMakeLists.txt | 9 | ||||
| -rw-r--r-- | src/rez/dir.cpp | 117 | ||||
| -rw-r--r-- | src/rez/entry.cpp | 10 | ||||
| -rw-r--r-- | src/rez/file.cpp | 100 | ||||
| -rw-r--r-- | src/rez/io.cpp | 174 | ||||
| -rw-r--r-- | src/rez/resource.cpp | 46 | ||||
| -rw-r--r-- | src/rez/rez.cpp | 124 | ||||
| -rw-r--r-- | src/rez/rez.h | 46 | ||||
| -rw-r--r-- | src/rez/rez/dir.h | 34 | ||||
| -rw-r--r-- | src/rez/rez/entry.h | 31 | ||||
| -rw-r--r-- | src/rez/rez/file.h | 34 | ||||
| -rw-r--r-- | src/rez/rez/io.h | 34 | ||||
| -rw-r--r-- | src/rez/rez/resource.h | 30 |
13 files changed, 789 insertions, 0 deletions
diff --git a/src/rez/CMakeLists.txt b/src/rez/CMakeLists.txt new file mode 100644 index 0000000..7a767fc --- /dev/null +++ b/src/rez/CMakeLists.txt @@ -0,0 +1,9 @@ +target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR}) +target_sources(${PROJECT_NAME} PRIVATE + dir.cpp + entry.cpp + file.cpp + io.cpp + resource.cpp + rez.cpp +) diff --git a/src/rez/dir.cpp b/src/rez/dir.cpp new file mode 100644 index 0000000..62e37fb --- /dev/null +++ b/src/rez/dir.cpp @@ -0,0 +1,117 @@ +#include <rez/dir.h> +#include <rez/io.h> +#include <iostream> + +const rez::dir::entrymap &rez::dir::entries() const +{ + return m_entries; +} + +const std::string &rez::dir::name() const +{ + return m_name; +} + +int rez::dir::read(const struct entry::cfg &cfg, rez::io &io, + std::string &name, direntry &de, unsigned level) +{ + rez::entry entry; + + if (entry.read(io)) + return -1; + + switch (entry.type) + { + case entry::type::dir: + { + long offset; + dir subdir; + + if (io.read(subdir.m_name, cfg.maxdirlen) + || (offset = io.tell()) < 0) + return -1; + + struct entry::cfg scfg = cfg; + +#if 0 + for (unsigned i = 0; i < level + 1; i++) + std::cout << "|\t"; + + std::cout << "name: " << subdir.m_name << std::endl; +#endif + + subdir.entry = entry; + scfg.size = entry.size; + scfg.offset = entry.pos; + + if (subdir.read(scfg, io, level + 1) || io.seek(offset)) + return -1; + + name = subdir.m_name; + de = subdir; + } + break; + + case entry::type::resource: + { + resource resource; + + resource.entry = entry; + + if (resource.read(cfg, io)) + return -1; + +#if 0 + for (unsigned i = 0; i < level + 1; i++) + std::cout << "|\t"; + + std::cout << "name: " << resource.name() << std::endl; +#endif + + name = resource.name(); + de = resource; + } + break; + + default: + std::cerr << "Invalid entry type " << entry.type << std::endl; + return -1; + } + + return 0; +} + +int rez::dir::read(const struct entry::cfg &cfg, rez::io &io, unsigned level) +{ + unsigned long size = cfg.size; + long before = cfg.offset; + + if (io.seek(cfg.offset)) + return -1; + + while (size) + { + std::string name; + direntry de; + long after; + + if (read(cfg, io, name, de, level) + || (after = io.tell()) < 0) + return -1; + + unsigned long r = after - before; + + if (r > size) + { + std::cerr << m_name << ": exceeded maximum direntry length " + "(" << cfg.size << ")" << std::endl; + return -1; + } + + m_entries[name] = de; + before = after; + size -= r; + } + + return 0; +} diff --git a/src/rez/entry.cpp b/src/rez/entry.cpp new file mode 100644 index 0000000..2e2f73e --- /dev/null +++ b/src/rez/entry.cpp @@ -0,0 +1,10 @@ +#include <rez/entry.h> +#include <rez/io.h> + +int rez::entry::read(rez::io &io) +{ + return io.read_le(type) + || io.read_le(pos) + || io.read_le(size) + || io.read_le(time); +} diff --git a/src/rez/file.cpp b/src/rez/file.cpp new file mode 100644 index 0000000..9a6a1f3 --- /dev/null +++ b/src/rez/file.cpp @@ -0,0 +1,100 @@ +#include <rez/file.h> +#include <rez/entry.h> +#include <rez/resource.h> +#include <cstddef> +#include <iostream> + +std::string rez::file::path() const +{ + return resource.name(); +} + +long rez::file::tell() +{ + return offset; +} + +int rez::file::seek(long offset) +{ + if (offset < 0 || offset > resource.entry.size) + return -1; + else if (io.seek(resource.entry.pos + offset)) + return -1; + + this->offset = offset; + return 0; +} + +int rez::file::eof() const +{ + return offset >= resource.entry.size; +} + +int rez::file::read(void *b, size_t n) +{ + const rez::entry &entry = resource.entry; + unsigned long roffset = offset + entry.pos, + end = entry.pos + entry.size, remaining = end - roffset; + + if (n > remaining) + { + std::cerr << path() << ": requested " << n << " bytes, only " + << remaining << " remaining" << std::endl; + return -1; + } + + if (seek(offset) || io.read(b, n)) + return -1; + + offset += n; + return 0; +} + +int rez::file::read(unsigned char &b) +{ + return read(&b, sizeof b); +} + +int rez::file::read(std::string &s, size_t len) +{ + for (size_t i = 0; i < len; i++) + { + char b; + + if (read(&b, sizeof b)) + return -1; + + s += b; + } + + return 0; +} + +int rez::file::read_le(uint16_t &w) +{ + unsigned char b[sizeof w]; + + if (read(b, sizeof b)) + return -1; + + w = b[0] | (b[1] << 8); + return 0; +} + +int rez::file::read_le(uint32_t &w) +{ + unsigned char b[sizeof w]; + + if (read(b, sizeof b)) + return -1; + + w = b[0] | (b[1] << 8u) | (b[2] << 16u) | (b[3] << 24u); + return 0; +} + +rez::file::file(const rez::resource &resource, rez::io &io) : + resource(resource), + offset(0), + io(io) +{ +} diff --git a/src/rez/io.cpp b/src/rez/io.cpp new file mode 100644 index 0000000..019fdab --- /dev/null +++ b/src/rez/io.cpp @@ -0,0 +1,174 @@ +#include <rez/io.h> +#include <cerrno> +#include <cstring> +#include <fstream> +#include <iostream> + +int rez::io::read(unsigned char &b) +{ + std::ifstream::pos_type off = f.tellg(); + + f.read((char *)&b, sizeof b); + + if (!f || f.eof()) + { + std::cerr << m_path << ": failed to read at offset " << off + << ", eof=" << f.eof() << std::endl; + return -1; + } + + return 0; +} + +int rez::io::check(char byte) +{ + char b; + std::ifstream::pos_type off = f.tellg(); + + f.read(&b, sizeof b); + + if (!f || f.eof()) + { + std::cerr << m_path << ": failed to read at offset " << off + << ", eof=" << f.eof() << std::endl; + return -1; + } + else if (b != byte) + { + std::cerr << m_path << ": expected " << byte << " at offset: " << off + << ", got " << b << std::endl; + return -1; + } + + return 0; +} + +int rez::io::check(const char *s) +{ + while (*s) + if (check(*s++)) + return -1; + + return 0; +} + +int rez::io::read_le(uint32_t &w) +{ + std::ifstream::pos_type off = f.tellg(); + unsigned char buf[sizeof w]; + + f.read((char *)buf, sizeof buf); + + if (!f || f.eof()) + { + std::cerr << m_path << ": failed to read word at offset " << off + << ", eof=" << f.eof() << std::endl; + return -1; + } + + w = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + return 0; +} + +int rez::io::read(std::string &s, unsigned maxlen) +{ + unsigned i = 0; + + while (i++ < maxlen) + { + unsigned char b; + + if (read(b)) + return -1; + else if (!b) + return 0; + + s += b; + } + + std::cerr << m_path << ": exceeded maximum string length (" << maxlen + << "): \"" << s << "\"" << std::endl; + return -1; +} + +int rez::io::read(void *b, size_t n) +{ + std::ifstream::pos_type off = f.tellg(); + + f.read((char *)(b), n); + + if (!f) + { + std::cerr << m_path << ": failed to read " << n << " bytes at offset " + << off << ", eof=" << f.eof() << std::endl; + return -1; + } + + return 0; +} + +long rez::io::tell() +{ + std::ifstream::pos_type offset = f.tellg(); + + if (!f) + { + std::cerr << m_path << ": failed to retrieve offset" << std::endl; + return -1; + } + + return offset; +} + +int rez::io::seek(long offset) +{ + long cur = f.tellg(); + + if (!f) + { + std::cerr << m_path << ": failed to tell offset " << std::endl; + return -1; + } + else if (cur != offset) + { + f.seekg(static_cast<std::ifstream::pos_type>(offset)); + + if (!f) + { + std::cerr << m_path << ": failed to seek to offset " << offset + << std::endl; + return -1; + } + } + + return 0; +} + +int rez::io::open() +{ + if (!f.is_open()) + { + const char *error; + +#ifdef _WIN32 + error = GetLastError(); +#else + error = strerror(errno); +#endif + std::cerr << "Failed to open " << m_path << ": " << error << std::endl; + return -1; + } + + return 0; +} + +const std::string &rez::io::path() const +{ + return m_path; +} + +rez::io::io(const char *path) : + f(path, std::ios::binary), + m_path(path) +{ +} diff --git a/src/rez/resource.cpp b/src/rez/resource.cpp new file mode 100644 index 0000000..3fc94a7 --- /dev/null +++ b/src/rez/resource.cpp @@ -0,0 +1,46 @@ +#include <rez/resource.h> +#include <rez/io.h> + +std::string rez::resource::name() const +{ + std::string ret = m_name; + + if (*type) + ret += std::string(".") + type; + + return ret; +} + +const std::string &rez::resource::description() const +{ + return descr; +} + +int rez::resource::read(const struct entry::cfg &cfg, rez::io &io) +{ + char letype[sizeof type]; + uint32_t n_keys; + + if (io.read_le(id) + || io.read(letype, sizeof letype) + || io.read_le(n_keys) + || io.read(m_name, cfg.maxreslen) + || io.read(descr, sizeof ".XYZ")) + return -1; + + for (uint32_t i = 0; i < n_keys; i++) + { + uint32_t key; + + if (io.read_le(key)) + return -1; + + keys.push_back(key); + } + + type[0] = letype[2]; + type[1] = letype[1]; + type[2] = letype[0]; + type[3] = '\0'; + return 0; +} diff --git a/src/rez/rez.cpp b/src/rez/rez.cpp new file mode 100644 index 0000000..2351c05 --- /dev/null +++ b/src/rez/rez.cpp @@ -0,0 +1,124 @@ +#include <rez.h> +#include <rez/dir.h> +#include <rez/io.h> +#include <cctype> +#include <iostream> +#include <memory> +#include <sstream> +#include <variant> + +std::string rez::ball::toupper(const std::string &s) const +{ + std::string ret; + + // Assume ASCII encoding. + for (const auto &c: s) + ret += std::toupper(c); + + return ret; +} + +std::unique_ptr<rez::file> rez::ball::open(const char *path) +{ + std::istringstream stream(path); + std::string token; + const rez::dir *dir = &root_dir; + const rez::resource *resource = nullptr; + + while (std::getline(stream, token, '/')) + { + std::string uctoken = toupper(token); + const auto &map = dir->entries(); + auto it = map.find(uctoken); + + if (it == map.end()) + return nullptr; + + const rez::dir::direntry &de = it->second; + + if (std::holds_alternative<rez::dir>(de)) + dir = &std::get<rez::dir>(de); + else if (std::holds_alternative<rez::resource>(de)) + { + dir = nullptr; + resource = &std::get<rez::resource>(de); + } + } + + if (dir) + { + std::cerr << path << " is a directory\n"; + return nullptr; + } + else if (!resource) + { + std::cerr << io.path() << ": could not find " << path << '\n'; + return nullptr; + } + + return std::make_unique<rez::file>(*resource, io); +} + +int rez::ball::parse() +{ + static const char title[63] = + "\r\nRezMgr Version 1 Copyright (C) 1995 MONOLITH INC. ", + subtitle[65] = + "\r\nLithTech Resource File \r\n"; + + if (io.open()) + return -1; + else if (io.check(title)) + { + std::cerr << path << ": wrong title\n"; + return -1; + } + else if (io.check(subtitle)) + { + std::cerr << path << ": wrong subtitle\n"; + return -1; + } + else if (io.check(0x1a)) + { + std::cerr << path << ": wrong EOF\n"; + return -1; + } + + struct + { + uint32_t w[N_WORDS]; + uint8_t is_sorted; + } header; + + for (auto &w : header.w) + if (io.read_le(w)) + return -1; + + if (header.w[FILE_FORMAT_VERSION] != 1) + { + std::cerr << path << ": expected file format version 1, got " + << header.w[FILE_FORMAT_VERSION] << '\n'; + return -1; + } + else if (io.read(header.is_sorted)) + { + std::cerr << path << ": could not read sorted flag" << '\n'; + return -1; + } + + struct rez::entry::cfg cfg = {0}; + + cfg.size = header.w[ROOT_DIR_SIZE]; + cfg.offset = header.w[ROOT_DIR_POS]; + cfg.maxcomlen = header.w[MAX_COMMENT_SIZE]; + cfg.maxdirlen = header.w[MAX_DIR_NAME_SIZE]; + cfg.maxreslen = header.w[MAX_REZ_NAME_SIZE]; + root_dir.read(cfg, io, 0); + return 0; +} + +rez::ball::ball(const char *path) : + path(path), + io(path) +{ +} diff --git a/src/rez/rez.h b/src/rez/rez.h new file mode 100644 index 0000000..5bbe206 --- /dev/null +++ b/src/rez/rez.h @@ -0,0 +1,46 @@ +#ifndef REZ_H +#define REZ_H + +#include <rez/dir.h> +#include <rez/file.h> +#include <rez/io.h> +#include <memory> +#include <string> + +namespace rez +{ + +class ball +{ +public: + ball(const char *path); + int parse(); + std::unique_ptr<rez::file> open(const char *path); + +private: + std::string toupper(const std::string &s) const; + + enum + { + FILE_FORMAT_VERSION, + ROOT_DIR_POS, + ROOT_DIR_SIZE, + ROOT_DIR_TIME, + NEXT_WRITE_POS, + TIME, + MAX_KEY_ARRAY, + MAX_DIR_NAME_SIZE, + MAX_REZ_NAME_SIZE, + MAX_COMMENT_SIZE, + + N_WORDS + }; + + const std::string path; + rez::io io; + rez::dir root_dir; +}; + +} + +#endif diff --git a/src/rez/rez/dir.h b/src/rez/rez/dir.h new file mode 100644 index 0000000..5485052 --- /dev/null +++ b/src/rez/rez/dir.h @@ -0,0 +1,34 @@ +#ifndef REZ_DIR_H +#define REZ_DIR_H + +#include <rez/io.h> +#include <rez/entry.h> +#include <rez/resource.h> +#include <map> +#include <string> +#include <variant> + +namespace rez +{ + +class dir +{ +public: + typedef std::variant<resource, rez::dir> direntry; + typedef std::map<std::string, direntry> entrymap; + int read(const struct entry::cfg &cfg, rez::io &io, unsigned level); + const std::string &name() const; + const entrymap &entries() const; + +private: + int read(const struct entry::cfg &cfg, rez::io &io, std::string &name, + direntry &de, unsigned level); + + rez::entry entry; + std::string m_name; + entrymap m_entries; +}; + +} + +#endif diff --git a/src/rez/rez/entry.h b/src/rez/rez/entry.h new file mode 100644 index 0000000..0e76321 --- /dev/null +++ b/src/rez/rez/entry.h @@ -0,0 +1,31 @@ +#ifndef REZ_ENTRY_H +#define REZ_ENTRY_H + +#include <rez/io.h> +#include <cstdint> + +namespace rez +{ + +struct entry +{ + int read(rez::io &io); + + struct cfg + { + long offset; + unsigned long size, maxdirlen, maxreslen, maxcomlen; + }; + + enum type + { + resource, + dir + }; + + uint32_t type, pos, size, time; +}; + +} + +#endif diff --git a/src/rez/rez/file.h b/src/rez/rez/file.h new file mode 100644 index 0000000..20b2310 --- /dev/null +++ b/src/rez/rez/file.h @@ -0,0 +1,34 @@ +#ifndef REZ_FILE_H +#define REZ_FILE_H + +#include <rez/io.h> +#include <rez/resource.h> +#include <cstddef> +#include <cstdint> + +namespace rez +{ + +class file +{ +public: + file(const rez::resource &resource, rez::io &io); + std::string path() const; + int read(void *b, size_t n); + int read(unsigned char &b); + int read(std::string &s, size_t len); + int read_le(uint16_t &w); + int read_le(uint32_t &w); + long tell(); + int seek(long offset); + int eof() const; + +private: + const rez::resource &resource; + long offset; + rez::io &io; +}; + +} + +#endif diff --git a/src/rez/rez/io.h b/src/rez/rez/io.h new file mode 100644 index 0000000..238a925 --- /dev/null +++ b/src/rez/rez/io.h @@ -0,0 +1,34 @@ +#ifndef REZ_IO_H +#define REZ_IO_H + +#include <cstddef> +#include <cstdint> +#include <fstream> +#include <string> + +namespace rez +{ + +class io +{ +public: + io(const char *path); + int open(); + int read(unsigned char &b); + int read(std::string &s, unsigned maxlen); + int read(void *b, size_t n); + int check(char byte); + int check(const char *s); + int read_le(uint32_t &w); + long tell(); + int seek(long offset); + const std::string &path() const; + +private: + std::ifstream f; + const std::string m_path; +}; + +} + +#endif diff --git a/src/rez/rez/resource.h b/src/rez/rez/resource.h new file mode 100644 index 0000000..4daafa0 --- /dev/null +++ b/src/rez/rez/resource.h @@ -0,0 +1,30 @@ +#ifndef REZ_RESOURCE_H +#define REZ_RESOURCE_H + +#include <rez/entry.h> +#include <rez/io.h> +#include <cstdint> +#include <string> +#include <vector> + +namespace rez +{ + +class resource +{ +public: + int read(const struct entry::cfg &cfg, rez::io &io); + const std::string &description() const; + std::string name() const; + rez::entry entry; + +private: + uint32_t id; + char type[sizeof "xyz"]; + std::string m_name, descr; + std::vector<uint32_t> keys; +}; + +} + +#endif |
