aboutsummaryrefslogtreecommitdiff
path: root/src/rez
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi92@disroot.org>2025-12-04 13:58:02 +0100
committerXavier Del Campo Romero <xavi92@disroot.org>2025-12-15 23:04:39 +0100
commit600ff28dd73f2cf17725382b68a4b1b2573f2e34 (patch)
treea105c686458d8998438652aeca6299cf9000edcd /src/rez
First commitHEADabcmain
Diffstat (limited to 'src/rez')
-rw-r--r--src/rez/CMakeLists.txt9
-rw-r--r--src/rez/dir.cpp117
-rw-r--r--src/rez/entry.cpp10
-rw-r--r--src/rez/file.cpp100
-rw-r--r--src/rez/io.cpp174
-rw-r--r--src/rez/resource.cpp46
-rw-r--r--src/rez/rez.cpp124
-rw-r--r--src/rez/rez.h46
-rw-r--r--src/rez/rez/dir.h34
-rw-r--r--src/rez/rez/entry.h31
-rw-r--r--src/rez/rez/file.h34
-rw-r--r--src/rez/rez/io.h34
-rw-r--r--src/rez/rez/resource.h30
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