From e5a3717e643172d20748cef378db50f03a4cbda0 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 30 Jul 2024 22:30:55 -0700 Subject: [PATCH] Makes some stuff work --- .vscode/settings.json | 72 ++++++ CMakeLists.txt | 2 +- src/nbt/Accountant.cpp | 40 ++++ src/nbt/Accountant.h | 21 ++ src/nbt/ByteArrayTag.cpp | 104 +++++++++ src/nbt/ByteArrayTag.h | 37 +++ src/nbt/ByteTag.cpp | 66 ++++++ src/nbt/ByteTag.h | 37 +++ src/nbt/DoubleTag.cpp | 62 ++++++ src/nbt/DoubleTag.h | 30 +++ src/nbt/EndTag.cpp | 59 +++++ src/nbt/EndTag.h | 29 +++ src/nbt/FloatTag.cpp | 62 ++++++ src/nbt/FloatTag.h | 30 +++ src/nbt/Tag.cpp | 255 +++++++++++++++++++++ src/nbt/Tag.h | 129 +++++++++++ src/types/dynamic.cpp | 139 ++++++++++++ src/types/dynamic.h | 44 ++++ src/utils/ByteLayer.cpp | 433 ++++++++++++++++++++++++++++++++++++ src/utils/ByteLayer.h | 92 ++++++++ src/utils/StringBuilder.cpp | 65 ++++++ src/utils/StringBuilder.h | 31 +++ src/utils/StringReader.cpp | 168 ++++++++++++++ src/utils/StringReader.h | 42 ++++ 24 files changed, 2048 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json create mode 100644 src/nbt/Accountant.cpp create mode 100644 src/nbt/Accountant.h create mode 100644 src/nbt/ByteArrayTag.cpp create mode 100644 src/nbt/ByteArrayTag.h create mode 100644 src/nbt/ByteTag.cpp create mode 100644 src/nbt/ByteTag.h create mode 100644 src/nbt/DoubleTag.cpp create mode 100644 src/nbt/DoubleTag.h create mode 100644 src/nbt/EndTag.cpp create mode 100644 src/nbt/EndTag.h create mode 100644 src/nbt/FloatTag.cpp create mode 100644 src/nbt/FloatTag.h create mode 100644 src/nbt/Tag.cpp create mode 100644 src/nbt/Tag.h create mode 100644 src/types/dynamic.cpp create mode 100644 src/types/dynamic.h create mode 100644 src/utils/ByteLayer.cpp create mode 100644 src/utils/ByteLayer.h create mode 100644 src/utils/StringBuilder.cpp create mode 100644 src/utils/StringBuilder.h create mode 100644 src/utils/StringReader.cpp create mode 100644 src/utils/StringReader.h diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7003ca4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,72 @@ +{ + "files.associations": { + "vector": "cpp", + "iostream": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "format": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "print": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "text_encoding": "cpp", + "thread": "cpp", + "typeinfo": "cpp", + "variant": "cpp", + "__bit_reference": "cpp", + "__locale": "cpp", + "__threading_support": "cpp", + "__verbose_abort": "cpp", + "execution": "cpp", + "ios": "cpp", + "locale": "cpp", + "queue": "cpp", + "stack": "cpp" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e3e19c..7cdece6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) # Add source files -file(GLOB SOURCES "src/utils/*.cpp" "src/nbt/*.cpp") +file(GLOB SOURCES "src/utils/*.cpp" "src/types/*.cpp") # Add an shared library target add_library(ac SHARED ${SOURCES}) diff --git a/src/nbt/Accountant.cpp b/src/nbt/Accountant.cpp new file mode 100644 index 0000000..52066e0 --- /dev/null +++ b/src/nbt/Accountant.cpp @@ -0,0 +1,40 @@ +#include "Accountant.h" +#include "Tag.h" +#include + +using namespace nbt; +using namespace libac; + +namespace nbt +{ + + int NBTAccountant::_prettyIndex = 0; + + void NBTAccountant::printRead(const Tag &tag) + { + tag.prettyPrint(_prettyIndex, false); + } + + void NBTAccountant::visitTag() + { + _prettyIndex++; + } + + void NBTAccountant::leaveTag(const Tag &tag) + { + // Assuming Tag has virtual methods to check its type or a `getType` method + if (dynamic_cast(&tag) || dynamic_cast(&tag)) + { + if (const CompoundTag *ct = dynamic_cast(&tag)) + { + ct->endPrettyPrint(_prettyIndex); + } + else if (const ListTag *lt = dynamic_cast(&tag)) + { + lt->endPrettyPrint(_prettyIndex); + } + } + _prettyIndex--; + } + +} \ No newline at end of file diff --git a/src/nbt/Accountant.h b/src/nbt/Accountant.h new file mode 100644 index 0000000..d93bcab --- /dev/null +++ b/src/nbt/Accountant.h @@ -0,0 +1,21 @@ +#ifndef NBTACCOUNTANT_H +#define NBTACCOUNTANT_H + +#include "Tag.h" // Assuming this includes definitions for Tag, CompoundTag, and ListTag + +namespace nbt +{ + + class NBTAccountant + { + public: + static void printRead(const Tag &tag); + static void visitTag(); + static void leaveTag(const Tag &tag); + + private: + static int _prettyIndex; + }; +} + +#endif // NBTACCOUNTANT_H diff --git a/src/nbt/ByteArrayTag.cpp b/src/nbt/ByteArrayTag.cpp new file mode 100644 index 0000000..8f979fd --- /dev/null +++ b/src/nbt/ByteArrayTag.cpp @@ -0,0 +1,104 @@ +#include "ByteArrayTag.h" +#include +#include +#include +#include + +namespace nbt +{ + + ByteArrayTag::ByteArrayTag() : Tag() + { + // Default constructor + } + + ByteArrayTag::ByteArrayTag(const std::vector &value) : Tag(), value(value) + { + // Constructor with initial value + } + + void ByteArrayTag::readValue(ByteLayer &data) + { + int len = data.readInt(); + value.clear(); + for (int i = 0; i < len; i++) + { + value.push_back(data.readByte()); + } + } + + void ByteArrayTag::writeValue(ByteLayer &data) + { + data.writeInt(static_cast(value.size())); + for (int i : value) + { + data.writeByte(i); + } + } + + TagType ByteArrayTag::getTagType() const + { + return TagType::ByteArray; + } + + dynamic ByteArrayTag::getValue() const + { + return dynamic(value); + } + + void ByteArrayTag::setValue(const dynamic &val) + { + value = val.get>(); + } + + void ByteArrayTag::prettyPrint(int indent, bool recurse) const + { + std::cout << std::setw(indent) << std::setfill('\t') << "" + << Tag::getCanonicalName(getTagType()) << ": ["; + for (size_t i = 0; i < value.size(); i++) + { + std::cout << value[i]; + if (i < value.size() - 1) + { + std::cout << ", "; + } + } + std::cout << "]"; + } + + void ByteArrayTag::writeStringifiedValue(StringBuilder &builder, int indent, bool isList) const + { + builder << (isList ? std::string(indent, '\t') : "") << "[B; "; + for (size_t i = 0; i < value.size(); i++) + { + builder << value[i] << "B"; + if (i < value.size() - 1) + { + builder << ", "; + } + } + builder << "]"; + } + + void ByteArrayTag::readStringifiedValue(StringReader &reader) + { + std::string temp; + reader.expect('['); + reader.expect('B'); + reader.expect(';'); + value.clear(); + + while (reader.peek() != ']') + { + uint8_t num = stoi(reader.readNumber()); + value.push_back(num); + + reader.expect('b'); + + if (reader.peek() == ',') + reader.next(); + } + reader.expect(']'); + } + +} \ No newline at end of file diff --git a/src/nbt/ByteArrayTag.h b/src/nbt/ByteArrayTag.h new file mode 100644 index 0000000..d335c8a --- /dev/null +++ b/src/nbt/ByteArrayTag.h @@ -0,0 +1,37 @@ +#ifndef BYTEARRAYTAG_H +#define BYTEARRAYTAG_H + +#include "Tag.h" +#include "../utils/ByteLayer.h" // Assuming ByteLayer is a class you have +#include +#include +#include + +using namespace libac; +using namespace std; + +namespace nbt +{ + + class ByteArrayTag : public Tag + { + public: + ByteArrayTag(); + ByteArrayTag(const std::vector &value); + + // Override functions from the Tag class + void readValue(ByteLayer &data) override; + void writeValue(ByteLayer &data) override; + TagType getTagType() const override; + dynamic getValue() const; + void setValue(const dynamic &val) override; + void prettyPrint(int indent, bool recurse) const override; + void writeStringifiedValue(StringBuilder &builder, int indent, bool isList) const override; + void readStringifiedValue(StringReader &reader) override; + + private: + vector value; + }; +} + +#endif // BYTEARRAYTAG_H diff --git a/src/nbt/ByteTag.cpp b/src/nbt/ByteTag.cpp new file mode 100644 index 0000000..a4f3f87 --- /dev/null +++ b/src/nbt/ByteTag.cpp @@ -0,0 +1,66 @@ +#include "ByteTag.h" +#include "../utils/ByteLayer.h" +#include "../utils/StringBuilder.h" +#include "../utils/StringReader.h" +#include + +using namespace libac; +using namespace std; + +namespace nbt +{ + + ByteTag::ByteTag() : Tag(), value(0) + { + // Default constructor + } + + ByteTag::ByteTag(int value) : Tag(), value(value) + { + } + + void ByteTag::readValue(ByteLayer &data) + { + value = data.readByte(); + } + + void ByteTag::writeValue(ByteLayer &data) + { + data.writeByte(value); + } + + TagType ByteTag::getTagType() const + { + return TagType::Byte; + } + + dynamic ByteTag::getValue() const + { + return value; + } + + void ByteTag::setValue(const dynamic &val) + { + value = val.get(); + } + + void ByteTag::prettyPrint(int indent, bool recurse) const + { + std::cout << std::setw(indent) << std::setfill('\t') << "" + << Tag::getCanonicalName(getTagType()) << ": " << value; + } + + void ByteTag::writeStringifiedValue(StringBuilder &builder, int indent, bool isList) const + { + builder << (isList ? std::string(indent, '\t') : "") << value << "b"; + } + + void ByteTag::readStringifiedValue(StringReader &reader) + { + std::string val = reader.readNumber(); + value = stoi(val); + + reader.expect('b'); + } + +} \ No newline at end of file diff --git a/src/nbt/ByteTag.h b/src/nbt/ByteTag.h new file mode 100644 index 0000000..450ec2d --- /dev/null +++ b/src/nbt/ByteTag.h @@ -0,0 +1,37 @@ +#ifndef BYTETAG_H +#define BYTETAG_H + +#include "Tag.h" +#include +#include "../utils/ByteLayer.h" +#include "../utils/StringBuilder.h" +#include "../utils/StringReader.h" + +using namespace libac; + +namespace nbt +{ + + class ByteTag : public Tag + { + public: + ByteTag(); + ByteTag(int value); + ~ByteTag(); + + // Override functions from the Tag class + void readValue(ByteLayer &data) override; + void writeValue(ByteLayer &data) override; + TagType getTagType() const override; + dynamic getValue() const override; + void setValue(const dynamic &val) override; + void prettyPrint(int indent, bool recurse) const override; + void writeStringifiedValue(StringBuilder &builder, int indent, bool isList) const override; + void readStringifiedValue(StringReader &reader) override; + + private: + uint8_t value; + }; +} + +#endif // BYTETAG_H diff --git a/src/nbt/DoubleTag.cpp b/src/nbt/DoubleTag.cpp new file mode 100644 index 0000000..c7c0742 --- /dev/null +++ b/src/nbt/DoubleTag.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include "DoubleTag.h" + +using namespace libac; +using namespace std; + +namespace nbt +{ + + DoubleTag::DoubleTag() : Tag(), value(0.0) + { + } + + DoubleTag::DoubleTag(double val) : Tag(), value(val) + { + } + + void DoubleTag::readValue(ByteLayer &data) + { + value = data.readDouble(); + } + + void DoubleTag::writeValue(ByteLayer &data) + { + data.writeDouble(value); + } + + TagType DoubleTag::getTagType() const + { + return TagType::Double; + } + + dynamic DoubleTag::getValue() const + { + return dynamic(value); + } + + void DoubleTag::setValue(const dynamic &val) + { + value = val.get(); + } + + void DoubleTag::prettyPrint(int indent, bool recurse) const + { + cout << setw(indent) << setfill('\t') << Tag::getCanonicalName(getTagType()) << ": " << value; + } + + void DoubleTag::writeStringifiedValue(StringBuilder &builder, int indent, bool isList) const + { + builder << (isList ? std::string(indent, '\t') : "") << value << "d"; + } + + void DoubleTag::readStringifiedValue(StringReader &reader) + { + double val = stod(reader.readNumber()); + value = val; + + reader.expect('d'); + } +} \ No newline at end of file diff --git a/src/nbt/DoubleTag.h b/src/nbt/DoubleTag.h new file mode 100644 index 0000000..d099a0d --- /dev/null +++ b/src/nbt/DoubleTag.h @@ -0,0 +1,30 @@ +#ifndef DOUBLE_TAG_H +#define DOUBLE_TAG_H + +#include "Tag.h" + +namespace nbt +{ + class DoubleTag : public Tag + { + public: + DoubleTag(); + DoubleTag(double val); + ~DoubleTag(); + + // Function overrides + void readValue(ByteLayer &data) override; + void writeValue(ByteLayer &data) override; + TagType getTagType() const override; + dynamic getValue() const override; + void setValue(const dynamic &val) override; + void prettyPrint(int indent, bool recurse) const override; + void writeStringifiedValue(StringBuilder &builder, int indent, bool isList) const override; + void readStringifiedValue(StringReader &reader) override; + + private: + double value; + }; +} + +#endif \ No newline at end of file diff --git a/src/nbt/EndTag.cpp b/src/nbt/EndTag.cpp new file mode 100644 index 0000000..b45da97 --- /dev/null +++ b/src/nbt/EndTag.cpp @@ -0,0 +1,59 @@ +#include "EndTag.h" +#include "Tag.h" // Ensure you include the Tag class header +#include +#include // For std::setw if needed + +namespace nbt +{ + + EndTag::EndTag() : Tag() + { + // Constructor implementation if needed + } + + EndTag::~EndTag(){ + + } + + void EndTag::readValue(ByteLayer &data) + { + // Implementation here + } + + void EndTag::writeValue(ByteLayer &data) + { + // Implementation here + } + + TagType EndTag::getTagType() const + { + return TagType::End; + } + + void EndTag::setValue(const dynamic &val) + { + // Implementation here + } + + dynamic EndTag::getValue() const + { + // Implementation here + return new dynamic(new string("")); // Return appropriate value + } + + void EndTag::prettyPrint(int indent, bool recurse) const + { + std::cout << std::setw(indent) << std::setfill('\t') << "" << Tag::getCanonicalName(getTagType()); + } + + void EndTag::writeStringifiedValue(StringBuilder &builder, int indent, bool isList) const + { + // Implementation here + } + + void EndTag::readStringifiedValue(StringReader &reader) + { + // Implementation here + } + +} \ No newline at end of file diff --git a/src/nbt/EndTag.h b/src/nbt/EndTag.h new file mode 100644 index 0000000..f5b5982 --- /dev/null +++ b/src/nbt/EndTag.h @@ -0,0 +1,29 @@ +#ifndef ENDTAG_H +#define ENDTAG_H + +#include "Tag.h" +#include "../utils/ByteLayer.h" + +using namespace std; + +namespace nbt +{ + class EndTag : public Tag + { + public: + EndTag(); + ~EndTag(); + + // Override functions from the Tag class + void readValue(ByteLayer &data) override; + void writeValue(ByteLayer &data) override; + TagType getTagType() const override; + void setValue(const dynamic &val) override; + dynamic getValue() const override; + void prettyPrint(int indent, bool recurse) const override; + void writeStringifiedValue(StringBuilder &builder, int indent, bool isList) const override; + void readStringifiedValue(StringReader &reader) override; + }; +} + +#endif \ No newline at end of file diff --git a/src/nbt/FloatTag.cpp b/src/nbt/FloatTag.cpp new file mode 100644 index 0000000..82f2c64 --- /dev/null +++ b/src/nbt/FloatTag.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include "FloatTag.h" + +using namespace libac; +using namespace std; + +namespace nbt +{ + + FloatTag::FloatTag() : Tag(), value(0.0) + { + } + + FloatTag::FloatTag(float val) : Tag(), value(val) + { + } + + void FloatTag::readValue(ByteLayer &data) + { + value = data.readFloat(); + } + + void FloatTag::writeValue(ByteLayer &data) + { + data.writeFloat(value); + } + + TagType FloatTag::getTagType() const + { + return TagType::Float; + } + + dynamic FloatTag::getValue() const + { + return dynamic(value); + } + + void FloatTag::setValue(const dynamic &val) + { + value = val.get(); + } + + void FloatTag::prettyPrint(int indent, bool recurse) const + { + cout << setw(indent) << setfill('\t') << Tag::getCanonicalName(getTagType()) << ": " << value; + } + + void FloatTag::writeStringifiedValue(StringBuilder &builder, int indent, bool isList) const + { + builder << (isList ? std::string(indent, '\t') : "") << value << "f"; + } + + void FloatTag::readStringifiedValue(StringReader &reader) + { + float val = stof(reader.readNumber()); + value = val; + + reader.expect('f'); + } +} \ No newline at end of file diff --git a/src/nbt/FloatTag.h b/src/nbt/FloatTag.h new file mode 100644 index 0000000..42bcbad --- /dev/null +++ b/src/nbt/FloatTag.h @@ -0,0 +1,30 @@ +#ifndef FLOAT_TAG_H +#define FLOAT_TAG_H + +#include "Tag.h" + +namespace nbt +{ + class FloatTag : public Tag + { + public: + FloatTag(); + FloatTag(float val); + ~FloatTag(); + + // Function overrides + void readValue(ByteLayer &data) override; + void writeValue(ByteLayer &data) override; + TagType getTagType() const override; + dynamic getValue() const override; + void setValue(const dynamic &val) override; + void prettyPrint(int indent, bool recurse) const override; + void writeStringifiedValue(StringBuilder &builder, int indent, bool isList) const override; + void readStringifiedValue(StringReader &reader) override; + + private: + float value; + }; +} + +#endif \ No newline at end of file diff --git a/src/nbt/Tag.cpp b/src/nbt/Tag.cpp new file mode 100644 index 0000000..430adac --- /dev/null +++ b/src/nbt/Tag.cpp @@ -0,0 +1,255 @@ +#include "Tag.h" +#include "EndTag.h" +#include +#include + +#include "../utils/ByteLayer.h" +#include "../utils/StringBuilder.h" +#include "../utils/StringReader.h" + +using namespace libac; + +namespace nbt +{ + + std::shared_ptr Tag::readNamedTag(ByteLayer &data) + { + int type = data.readByte(); + switch (static_cast(type)) + { + case TagType::End: + return std::make_shared(); + case TagType::Byte: + // handle other cases similarly + default: + // default handling + return nullptr; + } + } + + bool Tag::shouldQuoteName() + { + std::string key = getKey(); + if (key.empty()) + { + return false; + } + else + { + std::string letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + for (char digit : key) + { + if (letters.find(digit) != std::string::npos) + { + return true; + } + } + + return false; + } + } + + bool Tag::shouldQuote(const std::string &value) + { + if (value.empty()) + { + return false; + } + else + { + std::string letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + for (char digit : value) + { + if (letters.find(digit) != std::string::npos) + { + return true; + } + } + + return false; + } + } + + void Tag::writeNamedTag(Tag &tag, ByteLayer &data) + { + data.writeByte(tag.getType()); + if (tag.getType() != static_cast(TagType::End)) + { + data.writeString(tag.getKey()); + tag.writeValue(data); + } + } + + std::shared_ptr Tag::readStringifiedNamedTag(StringReader &string) + { + std::string name; + if (string.peek() == '{' || string.peek() == '[') + { + name = ""; + } + else + { + name = string.readString(); + string.expect(':'); + } + TagType type = Tag::getStringifiedTagType(string); + std::shared_ptr tag = makeTagOfType(type); + tag->setKey(name); + tag->readStringifiedValue(string); + + return tag; + } + + void Tag::writeStringifiedNamedTag(Tag &tag, StringBuilder &builder, int indents) + { + if (tag.getType() != (int)TagType::End) + { + if (tag.getKey().empty()) + { + builder << std::string(indents, '\t'); + } + else + { + if (tag.shouldQuoteName()) + { + builder << std::string(indents, '\t') << "\"" << tag.getKey() << "\": "; + } + else + { + builder << std::string(indents, '\t') << tag.getKey() << ": "; + } + } + tag.writeStringifiedValue(builder, indents + 1, false); + } + } + + std::string Tag::getCanonicalName(TagType type) + { + switch (type) + { + case TagType::String: + return "TAG_String"; + case TagType::List: + return "TAG_List"; + case TagType::LongArray: + return "TAG_LongArray"; + case TagType::Long: + return "TAG_Long"; + case TagType::IntArray: + return "TAG_IntArray"; + case TagType::Int: + return "TAG_Int"; + case TagType::Compound: + return "TAG_Compound"; + case TagType::ByteArray: + return "TAG_ByteArray"; + case TagType::Byte: + return "TAG_Byte"; + case TagType::Double: + return "TAG_Double"; + case TagType::Float: + return "TAG_Float"; + case TagType::Short: + return "TAG_Short"; + case TagType::End: + return "TAG_End"; + default: + return ""; + } + } + + TagType Tag::getStringifiedTagType(StringReader &str) + { + str.startSeek(); + TagType ret = TagType::End; + bool isNumber = true; + bool isAlpha = true; // Is digits only + + while (str.canRead() && ret == TagType::End) + { + char val = str.next(); + val = std::toupper(val); // Convert to uppercase + + std::string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + if (alphabet.find(val) == std::string::npos) + { + isAlpha = false; + } + + switch (val) + { + case '{': + ret = TagType::Compound; + break; + case '[': + { + // Check for a type Prefix + std::string X = str.readUntil(';'); + std::transform(X.begin(), X.end(), X.begin(), ::toupper); // Convert to uppercase + if (X == "B") + { + ret = TagType::ByteArray; + } + else if (X == "I") + { + ret = TagType::IntArray; + } + else if (X == "L") + { + ret = TagType::LongArray; + } + else + { + ret = TagType::List; + } + break; + } + case 'B': + ret = TagType::Byte; + break; + case 'D': + ret = TagType::Double; + break; + case 'F': + ret = TagType::Float; + break; + case 'I': + ret = TagType::Int; + break; + case 'L': + ret = TagType::Long; + break; + case 'S': + ret = TagType::Short; + break; + case '\"': + ret = TagType::String; + break; + case ',': + case '\n': + if (str.getSeeked() >= 1) + ret = TagType::String; + if (isNumber) + ret = TagType::Int; + break; + default: + if (!str.isDigit(val)) + { + if (isNumber) + isNumber = false; + } + break; + } + } + + str.endSeek(); + if (!isNumber && isAlpha) + ret = TagType::String; + + return ret; + } + +} \ No newline at end of file diff --git a/src/nbt/Tag.h b/src/nbt/Tag.h new file mode 100644 index 0000000..db06c83 --- /dev/null +++ b/src/nbt/Tag.h @@ -0,0 +1,129 @@ +#ifndef LIBNBT_TAG_H +#define LIBNBT_TAG_H + +#include +#include +#include +#include +#include "../utils/ByteLayer.h" +#include "../utils/StringBuilder.h" +#include "../utils/StringReader.h" +#include "../types/dynamic.h" + +using namespace libac; +using namespace std; + +namespace nbt +{ + + enum class TagType + { + End = 0, + Byte = 1, + Short = 2, + Int = 3, + Long = 4, + Float = 5, + Double = 6, + ByteArray = 7, + String = 8, + List = 9, + Compound = 10, + IntArray = 11, + LongArray = 12 + + }; + class Tag + { + public: + virtual ~Tag() = default; + + int getType() const + { + return static_cast(getTagType()); + } + + virtual TagType getTagType() const = 0; + + bool hasParent() const + { + return _parentTag != nullptr; + } + + static TagType getStringifiedTagType(StringReader &str); + + void updateParent(std::shared_ptr tag) + { + if (tag == nullptr) + { + _parentTag.reset(); + setParentTagType(TagType::End); + } + else + { + _parentTag = tag; + setParentTagType(tag->getTagType()); + } + } + + std::shared_ptr getParent() const + { + return _parentTag; + } + + TagType getParentTagType() const + { + return _parentTagType; + } + + void setParentTagType(TagType type) + { + _parentTagType = type; + } + + virtual void writeValue(ByteLayer &data) const = 0; + virtual void readValue(ByteLayer &data) = 0; + + std::string getKey() const + { + return _key.empty() ? "" : _key; + } + + void setKey(const std::string &key) + { + _key = key; + } + + virtual void writeStringifiedValue(StringBuilder &stream, int indent, bool isList) const = 0; + virtual void readStringifiedValue(StringReader &reader) = 0; + virtual void writeValue(ByteLayer &data); + virtual void setValue(const dynamic &val); + virtual void prettyPrint(int indent, bool recurse) const; + virtual void readStringifiedValue(StringReader &reader); + virtual dynamic getValue() const; + virtual void setValue(int val); + + virtual bool equals(const Tag &other) const + { + return getType() == other.getType() && getKey() == other.getKey(); + } + + static std::shared_ptr readNamedTag(ByteLayer &data); + static void writeNamedTag(Tag &tag, ByteLayer &data); + static std::shared_ptr readStringifiedNamedTag(StringReader &string); + static void writeStringifiedNamedTag(Tag &tag, StringBuilder &builder, int indents); + + static std::shared_ptr makeTagOfType(TagType type); + + static std::string getCanonicalName(TagType type); + bool shouldQuoteName(); + static bool shouldQuote(const string &value); + + protected: + std::shared_ptr _parentTag; + TagType _parentTagType = TagType::End; + std::string _key; + }; +} + +#endif \ No newline at end of file diff --git a/src/types/dynamic.cpp b/src/types/dynamic.cpp new file mode 100644 index 0000000..c6d41ca --- /dev/null +++ b/src/types/dynamic.cpp @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include "dynamic.h" +using namespace std; + +namespace libac +{ + template + dynamic::dynamic(T y) + { + if constexpr (std::is_same_v) + { + Type = 1; + x.c = y; + } + else if constexpr (std::is_same_v) + { + Type = 2; + x.uc = y; + } + else if constexpr (std::is_same_v) + { + Type = 3; + x.s = y; + } + else if constexpr (std::is_same_v) + { + Type = 4; + x.l = y; + } + else if constexpr (std::is_same_v) + { + Type = 5; + x.i = y; + } + else if constexpr (std::is_same_v) + { + Type = 6; + x.f = y; + } + else if constexpr (std::is_same_v) + { + Type = 7; + x.d = y; + } + else if constexpr (std::is_same_v) + { + Type = 8; + x.str = y; + } + else if constexpr (std::is_same_v) + { + Type = 9; + x.u8 = y; + } + else if constexpr (std::is_same_v < T, vector) + { + Type = 10; + x.u8l = y; + } + else + { + static_assert(!std::is_same_v, "Unsupported type for dynamic"); + } + } + + template + T dynamic::get() const + { + if constexpr (std::is_same_v && Type == 1) + { + return x.c; + } + else if constexpr (std::is_same_v && Type == 2) + { + return x.uc; + } + else if constexpr (std::is_same_v && Type == 3) + { + return x.s; + } + else if constexpr (std::is_same_v && Type == 4) + { + return x.l; + } + else if constexpr (std::is_same_v && Type == 5) + { + return x.i; + } + else if constexpr (std::is_same_v && Type == 6) + { + return x.f; + } + else if constexpr (std::is_same_v && Type == 7) + { + return x.d; + } + else if constexpr (std::is_same_v && Type == 8) + { + return x.str; + } + else if constexpr (std::is_same_v && Type == 9) + { + return x.u8; + } + else if constexpr (std::is_same_v> && Type == 10) + { + return x.u8l; + } + else + { + throw std::runtime_error("Type mismatch or unsupported type"); + } + } + + template dynamic::dynamic(char); + template dynamic::dynamic(unsigned char); + template dynamic::dynamic(short); + template dynamic::dynamic(long); + template dynamic::dynamic(int); + template dynamic::dynamic(float); + template dynamic::dynamic(double); + template dynamic::dynamic(std::string); + template dynamic::dynamic(uint8_t); + template dynamic::dynamic(vector); + + template char dynamic::get() const; + template unsigned char dynamic::get() const; + template short dynamic::get() const; + template long dynamic::get() const; + template int dynamic::get() const; + template float dynamic::get() const; + template double dynamic::get() const; + template std::string dynamic::get() const; + template uint8_t dynamic::get() const; + template vector dynamic::get>() const; +} \ No newline at end of file diff --git a/src/types/dynamic.h b/src/types/dynamic.h new file mode 100644 index 0000000..8e011a6 --- /dev/null +++ b/src/types/dynamic.h @@ -0,0 +1,44 @@ +#ifndef DYNAMIC_H +#define DYNAMIC_H + +#include +#include +#include +#include + +using namespace std; + +namespace libac +{ + + union all + { + char c; + unsigned char uc; + short s; + long l; + int i; + float f; + double d; + string str; + uint8_t u8; + vector u8l; + }; + + class dynamic + { + public: + ~dynamic(); + + char Type; + all x; + + template + dynamic(T y); + + template + T get() const; + }; +} + +#endif \ No newline at end of file diff --git a/src/utils/ByteLayer.cpp b/src/utils/ByteLayer.cpp new file mode 100644 index 0000000..dd60721 --- /dev/null +++ b/src/utils/ByteLayer.cpp @@ -0,0 +1,433 @@ +#include "ByteLayer.h" +#include +#include +#include +#include +using namespace std; + +namespace libac +{ + + void ByteLayer::ensureCapacity(size_t additionalBytes) + { + size_t requiredCapacity = position + additionalBytes; + if (requiredCapacity > byteBuffer.size()) + { + byteBuffer.resize(requiredCapacity); + } + } + + void ByteLayer::writeInt(int value) + { + ensureCapacity(4); + std::memcpy(&byteBuffer[position], &value, 4); + position += 4; + } + + void ByteLayer::writeDouble(double value) + { + ensureCapacity(8); + std::memcpy(&byteBuffer[position], &value, 8); + position += 8; + } + + void ByteLayer::writeString(const std::string &value) + { + writeShort(static_cast(value.size())); + ensureCapacity(value.size()); + std::memcpy(&byteBuffer[position], value.data(), value.size()); + position += value.size(); + } + + int ByteLayer::readInt() + { + if (position + 4 > byteBuffer.size()) + throw std::out_of_range("Read past buffer end"); + int value; + std::memcpy(&value, &byteBuffer[position], 4); + position += 4; + return value; + } + + double ByteLayer::readDouble() + { + if (position + 8 > byteBuffer.size()) + throw std::out_of_range("Read past buffer end"); + double value; + std::memcpy(&value, &byteBuffer[position], 8); + position += 8; + return value; + } + + std::string ByteLayer::readString() + { + int16_t length = readShort(); + if (position + length > byteBuffer.size()) + throw std::out_of_range("Read past buffer end"); + std::string value(reinterpret_cast(&byteBuffer[position]), length); + position += length; + return value; + } + + void ByteLayer::writeIntZigZag(int value) + { + int zigzag = (value << 1) ^ (value >> 31); + writeInt(zigzag); + } + + int ByteLayer::readIntZigZag() + { + int zigzag = readInt(); + return (zigzag >> 1) ^ -(zigzag & 1); + } + + void ByteLayer::writeByte(uint8_t value) + { + ensureCapacity(1); + byteBuffer[position] = value; + position++; + } + + void ByteLayer::writeUnsignedByte(uint8_t value) + { + ensureCapacity(1); + byteBuffer[position] = value; + position++; + } + + uint8_t ByteLayer::readByte() + { + if (position >= byteBuffer.size()) + throw std::out_of_range("Read past buffer end"); + return byteBuffer[position++]; + } + + uint8_t ByteLayer::readUnsignedByte() + { + return readByte(); + } + + void ByteLayer::writeVarInt(int value) + { + while ((value & ~0x7F) != 0) + { + writeByte((value & 0x7F) | 0x80); + value >>= 7; + } + writeByte(value & 0x7F); + } + + int ByteLayer::readVarInt() + { + int result = 0; + int shift = 0; + uint8_t byte; + do + { + byte = readByte(); + result |= (byte & 0x7F) << shift; + shift += 7; + } while (byte & 0x80); + return result; + } + + void ByteLayer::writeVarIntNoZigZag(int value) + { + while ((value & ~0x7F) != 0) + { + writeByte((value & 0x7F) | 0x80); + value >>= 7; + } + writeByte(value & 0x7F); + } + + int ByteLayer::readVarIntNoZigZag() + { + int result = 0; + int shift = 0; + uint8_t byte; + do + { + byte = readByte(); + result |= (byte & 0x7F) << shift; + shift += 7; + } while (byte & 0x80); + return result; + } + + void ByteLayer::writeShort(int16_t value) + { + ensureCapacity(2); + std::memcpy(&byteBuffer[position], &value, 2); + position += 2; + } + + int16_t ByteLayer::readShort() + { + if (position + 2 > byteBuffer.size()) + throw std::out_of_range("Read past buffer end"); + int16_t value; + std::memcpy(&value, &byteBuffer[position], 2); + position += 2; + return value; + } + + void ByteLayer::writeFloat(float value) + { + ensureCapacity(4); + std::memcpy(&byteBuffer[position], &value, 4); + position += 4; + } + + float ByteLayer::readFloat() + { + if (position + 4 > byteBuffer.size()) + throw std::out_of_range("Read past buffer end"); + float value; + std::memcpy(&value, &byteBuffer[position], 4); + position += 4; + return value; + } + + void ByteLayer::writeTagName(const std::string &name) + { + writeShort(static_cast(name.size())); + ensureCapacity(name.size()); + std::memcpy(&byteBuffer[position], name.data(), name.size()); + position += name.size(); + } + + std::string ByteLayer::readTagName() + { + int16_t length = readShort(); + if (position + length > byteBuffer.size()) + throw std::out_of_range("Read past buffer end"); + std::string name(reinterpret_cast(&byteBuffer[position]), length); + position += length; + return name; + } + + void ByteLayer::resetPosition() + { + position = 0; + } + + void ByteLayer::clear() + { + resetPosition(); + byteBuffer.clear(); + } + + void ByteLayer::writeToFile(const std::string &filePath) + { + std::ofstream file(filePath, std::ios::binary); + if (!file) + throw std::runtime_error("Unable to open file for writing"); + file.write(reinterpret_cast(byteBuffer.data()), byteBuffer.size()); + } + + void ByteLayer::readFromFile(const std::string &filePath) + { + std::ifstream file(filePath, std::ios::binary | std::ios::ate); + if (!file) + throw std::runtime_error("Unable to open file for reading"); + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + byteBuffer.resize(size); + if (!file.read(reinterpret_cast(byteBuffer.data()), size)) + { + throw std::runtime_error("Error reading file"); + } + resetPosition(); + } + + void ByteLayer::doCompress() + { + uLongf compressedSize = compressBound(byteBuffer.size()); + std::vector compressedData(compressedSize); + if (compress(compressedData.data(), &compressedSize, byteBuffer.data(), byteBuffer.size()) != Z_OK) + { + throw std::runtime_error("Compression failed"); + } + compressedData.resize(compressedSize); + byteBuffer = std::move(compressedData); + position = byteBuffer.size(); + } + + void ByteLayer::doDecompress() + { + uLongf decompressedSize = byteBuffer.size() * 4; // Rough estimate + std::vector decompressedData(decompressedSize); + if (uncompress(decompressedData.data(), &decompressedSize, byteBuffer.data(), byteBuffer.size()) != Z_OK) + { + throw std::runtime_error("Decompression failed"); + } + decompressedData.resize(decompressedSize); + byteBuffer = std::move(decompressedData); + position = byteBuffer.size(); + } + + void ByteLayer::writeLong(int64_t value) + { + ensureCapacity(8); + std::memcpy(&byteBuffer[position], &value, 8); + position += 8; + } + + void ByteLayer::writeUnsignedLong(uint64_t value) + { + ensureCapacity(8); + std::memcpy(&byteBuffer[position], &value, 8); + position += 8; + } + + int64_t ByteLayer::readLong() + { + if (position + 8 > byteBuffer.size()) + throw std::out_of_range("Read past buffer end"); + int64_t value; + std::memcpy(&value, &byteBuffer[position], 8); + position += 8; + return value; + } + + uint64_t ByteLayer::readUnsignedLong() + { + if (position + 8 > byteBuffer.size()) + throw std::out_of_range("Read past buffer end"); + uint64_t value; + std::memcpy(&value, &byteBuffer[position], 8); + position += 8; + return value; + } + + void ByteLayer::writeVarLongNoZigZag(uint64_t value) + { + while (true) + { + if ((value & ~0x7F) == 0) + { + writeByte(static_cast(value)); + return; + } + writeByte((value & 0x7F) | 0x80); + value >>= 7; + } + } + + void ByteLayer::writeVarLongZigZag(int64_t value) + { + value = (value << 1) ^ (value >> 63); + writeVarLongNoZigZag(static_cast(value)); + } + + void ByteLayer::writeLongZigZag(int64_t value) + { + value = (value << 1) ^ (value >> 63); + writeLong(value); + } + + uint64_t ByteLayer::readVarLongNoZigZag() + { + uint64_t result = 0; + int shift = 0; + uint8_t byte; + do + { + byte = readByte(); + result |= (static_cast(byte & 0x7F) << shift); + shift += 7; + } while (byte & 0x80); + return result; + } + + int64_t ByteLayer::readVarLongZigZag() + { + uint64_t zigzag = readVarLongNoZigZag(); + return static_cast((zigzag >> 1) ^ -(zigzag & 1)); + } + + int64_t ByteLayer::readLongZigZag() + { + int64_t value = readLong(); + return (value >> 1) ^ -(value & 1); + } + + void ByteLayer::writeBytes(const std::vector &bytes) + { + ensureCapacity(bytes.size()); + std::memcpy(&byteBuffer[position], bytes.data(), bytes.size()); + position += bytes.size(); + } + + std::vector ByteLayer::readBytes(size_t num) + { + if (position + num > byteBuffer.size()) + throw std::out_of_range("Read past buffer end"); + std::vector result(byteBuffer.begin() + position, byteBuffer.begin() + position + num); + position += num; + return result; + } + + void ByteLayer::setBit(size_t position, uint8_t maskToSet) + { + if (position >= byteBuffer.size()) + throw std::out_of_range("Position out of range"); + seek(position); + uint8_t current = readUnsignedByte(); + seek(position); + writeUnsignedByte(current | maskToSet); + } + + void ByteLayer::clearBit(size_t position, uint8_t maskToClear) + { + if (position >= byteBuffer.size()) + throw std::out_of_range("Position out of range"); + seek(position); + uint8_t current = readUnsignedByte(); + seek(position); + writeUnsignedByte(current & ~maskToClear); + } + + bool ByteLayer::checkBit(size_t position, uint8_t mask) + { + if (position >= byteBuffer.size()) + return false; + seek(position); + uint8_t current = readUnsignedByte(); + return (current & mask) == mask; + } + + uint8_t ByteLayer::getBit(size_t position) + { + if (position >= byteBuffer.size()) + return 0; + seek(position); + return readUnsignedByte(); + } + + void ByteLayer::seek(size_t newPosition) + { + if (newPosition > byteBuffer.size()) + throw std::out_of_range("Seek past buffer end"); + position = newPosition; + } + + void ByteLayer::unsetSetBit(size_t position, uint8_t maskToClear, uint8_t maskToSet) + { + clearBit(position, maskToClear); + setBit(position, maskToSet); + } + + void ByteLayer::insertRandomBytes(size_t count) + { + std::random_device rd; + std::mt19937 rng(rd()); + std::uniform_int_distribution dist(0, 255); + for (size_t i = 0; i < count; ++i) + { + writeByte(dist(rng)); + } + } + +} \ No newline at end of file diff --git a/src/utils/ByteLayer.h b/src/utils/ByteLayer.h new file mode 100644 index 0000000..32ae9b2 --- /dev/null +++ b/src/utils/ByteLayer.h @@ -0,0 +1,92 @@ +#ifndef BYTELAYER_H +#define BYTELAYER_H + +#include +#include +#include +#include +#include +#include +#include +#include // For compression and decompression + +namespace libac +{ + class ByteLayer + { + public: + ByteLayer() : position(0) {} + + size_t getLength() const + { + return byteBuffer.size(); + } + + size_t getCurrentPosition() const + { + return position; + } + + std::vector getBytes() const + { + return std::vector(byteBuffer.begin(), byteBuffer.begin() + position); + } + + void writeInt(int value); + void writeDouble(double value); + void writeString(const std::string &value); + int readInt(); + double readDouble(); + std::string readString(); + void writeIntZigZag(int value); + int readIntZigZag(); + void writeByte(uint8_t value); + void writeUnsignedByte(uint8_t value); + uint8_t readByte(); + uint8_t readUnsignedByte(); + void writeVarInt(int value); + int readVarInt(); + void writeVarIntNoZigZag(int value); + int readVarIntNoZigZag(); + void writeShort(int16_t value); + int16_t readShort(); + void writeFloat(float value); + float readFloat(); + void writeTagName(const std::string &name); + std::string readTagName(); + void resetPosition(); + void clear(); + void writeToFile(const std::string &filePath); + void readFromFile(const std::string &filePath); + void doCompress(); + void doDecompress(); + void writeLong(int64_t value); + void writeUnsignedLong(uint64_t value); + int64_t readLong(); + uint64_t readUnsignedLong(); + void writeVarLongNoZigZag(uint64_t value); + void writeVarLongZigZag(int64_t value); + void writeLongZigZag(int64_t value); + uint64_t readVarLongNoZigZag(); + int64_t readVarLongZigZag(); + int64_t readLongZigZag(); + void writeBytes(const std::vector &bytes); + std::vector readBytes(size_t num); + void setBit(size_t position, uint8_t maskToSet); + void clearBit(size_t position, uint8_t maskToClear); + bool checkBit(size_t position, uint8_t mask); + uint8_t getBit(size_t position); + void seek(size_t position); + void unsetSetBit(size_t position, uint8_t maskToClear, uint8_t maskToSet); + void insertRandomBytes(size_t count); + + private: + std::vector byteBuffer; + size_t position; + + void ensureCapacity(size_t additionalBytes); + }; + +} + +#endif // BYTELAYER_H diff --git a/src/utils/StringBuilder.cpp b/src/utils/StringBuilder.cpp new file mode 100644 index 0000000..c9c76d0 --- /dev/null +++ b/src/utils/StringBuilder.cpp @@ -0,0 +1,65 @@ +#include "StringBuilder.h" +#include +#include + +using namespace std; + +namespace libac +{ + + StringBuilder::StringBuilder() : buffer() {} + + bool StringBuilder::isEmpty() const + { + return buffer.empty(); + } + + void StringBuilder::append(const std::string &value) + { + buffer.append(value); + } + + void StringBuilder::clear() + { + buffer.clear(); + } + + std::string StringBuilder::toString() const + { + return buffer; + } + + // Overload operator<< for std::string + StringBuilder& StringBuilder::operator<<(const std::string& value) { + append(value); + return *this; + } + + // Overload operator<< for C-style strings + StringBuilder& StringBuilder::operator<<(const char* value) { + append(value); + return *this; + } + + // Overload operator<< for single characters + StringBuilder& StringBuilder::operator<<(char value) { + buffer += value; + return *this; + } + + // Overload operator<< for integers + StringBuilder& StringBuilder::operator<<(int value) { + std::stringstream oss; + oss << value; + append(oss.str()); + return *this; + } + + // Overload operator<< for doubles + StringBuilder& StringBuilder::operator<<(double value) { + std::stringstream oss; + oss << value; + append(oss.str()); + return *this; + } +} diff --git a/src/utils/StringBuilder.h b/src/utils/StringBuilder.h new file mode 100644 index 0000000..e362828 --- /dev/null +++ b/src/utils/StringBuilder.h @@ -0,0 +1,31 @@ +#ifndef STRINGBUILDER_H +#define STRINGBUILDER_H + +#include +using namespace std; + +namespace libac +{ + + class StringBuilder + { + public: + StringBuilder(); + + bool isEmpty() const; + void append(const std::string &value); + void clear(); + std::string toString() const; + + // Overload operator<< to append strings and other types + StringBuilder& operator<<(const std::string& value); + StringBuilder& operator<<(const char* value); + StringBuilder& operator<<(char value); + StringBuilder& operator<<(int value); + StringBuilder& operator<<(double value); + private: + std::string buffer; + }; + +} +#endif // STRINGBUILDER_H diff --git a/src/utils/StringReader.cpp b/src/utils/StringReader.cpp new file mode 100644 index 0000000..d85d135 --- /dev/null +++ b/src/utils/StringReader.cpp @@ -0,0 +1,168 @@ +#include "StringReader.h" +#include +#include + +namespace libac +{ + + // Constructor + StringReader::StringReader(const std::string &buffer) + : _buffer(buffer), _position(0), _lastPosition(0), _quotedString(false) {} + + // Check if there's more to read + bool StringReader::canRead() const + { + return _position < _buffer.length(); + } + + // Get the number of characters seeked + int StringReader::getSeeked() const + { + return static_cast(_lastPosition - _position); + } + + // Read the next character + char StringReader::next() + { + if (canRead()) + { + skipWhitespace(); + return _buffer[_position++]; + } + else + { + throw std::out_of_range("End of buffer reached"); + } + } + + // Peek the next character without advancing the position + char StringReader::peek() const + { + if (_position < _buffer.length()) + { + return _buffer[_position]; + } + else + { + throw std::out_of_range("End of buffer reached"); + } + } + + // Skip any whitespace characters + void StringReader::skipWhitespace() + { + if (_quotedString) + return; // We need whitespace for strings + while (canRead() && isWhitespace(peek())) + { + _position++; + } + } + + // Check if a character is whitespace + bool StringReader::isWhitespace(char c) const + { + return std::isspace(static_cast(c)); + } + + // Read until a specific character is found + std::string StringReader::readUntil(char stopChar) + { + std::ostringstream result; + while (canRead() && peek() != stopChar) + { + result << next(); + } + return result.str(); + } + + // Read a string enclosed in double quotes + std::string StringReader::readQuotedString() + { + _quotedString = true; + if (next() != '"') + { + throw std::runtime_error("Expected double quotes at the start of a string"); + } + std::ostringstream result; + while (canRead()) + { + char c = next(); + if (c == '"') + { + break; + } + result << c; + } + _quotedString = false; + return result.str(); + } + + // Read a number (int or double) + std::string StringReader::readNumber() + { + std::ostringstream result; + while (canRead() && (isDigit(peek()) || peek() == '.' || peek() == '-')) + { + result << next(); + } + return result.str(); + } + + // Check if a character is a digit + bool StringReader::isDigit(char c) const + { + return std::isdigit(static_cast(c)); + } + + // Read an unquoted string (used for keys in SNBT) + std::string StringReader::readUnquotedString() + { + std::ostringstream result; + while (canRead() && + !isWhitespace(peek()) && + peek() != ':' && + peek() != ',' && + peek() != '{' && + peek() != '}' && + peek() != '[' && + peek() != ']') + { + result << next(); + } + return result.str(); + } + + std::string StringReader::readString() + { + if (peek() == '"') + { + return readQuotedString(); + } + else + { + return readUnquotedString(); + } + } + + // Read a specific character and throw an exception if it's not found + void StringReader::expect(char expectedChar) + { + if (std::tolower(next()) != std::tolower(expectedChar)) + { + throw std::runtime_error(std::string("Expected ") + expectedChar); + } + } + + void StringReader::startSeek() + { + _lastPosition = _position; + } + + void StringReader::endSeek() + { + _position = _lastPosition; + _lastPosition = 0; + } + +} \ No newline at end of file diff --git a/src/utils/StringReader.h b/src/utils/StringReader.h new file mode 100644 index 0000000..4d28f56 --- /dev/null +++ b/src/utils/StringReader.h @@ -0,0 +1,42 @@ +#ifndef STRINGREADER_H +#define STRINGREADER_H + +#include +#include + +using namespace std; + +namespace libac +{ + + class StringReader + { + public: + explicit StringReader(const std::string &buffer); + + bool canRead() const; + int getSeeked() const; + char next(); + char peek() const; + std::string readUntil(char stopChar); + std::string readQuotedString(); + std::string readNumber(); + std::string readUnquotedString(); + std::string readString(); + void expect(char expectedChar); + void startSeek(); + void endSeek(); + bool isDigit(char c) const; + + private: + void skipWhitespace(); + bool isWhitespace(char c) const; + + std::string _buffer; + size_t _position; + size_t _lastPosition; + bool _quotedString; + }; + +} +#endif // STRINGREADER_H