text:
<file_sep>#pragma once

#include <cstdint>

#include <ostream>
#include <string>
#include <vector>

#if LAVA_TOOLSET_gcc && !LAVA_SYSTEM_windows
	#define LAVA_MICROSOFT_CALL_ABI __attribute__((ms_abi))
#else
	#define LAVA_MICROSOFT_CALL_ABI
#endif

#if LAVA_TOOLSET_msc
	#define LAVA_NOINLINE __declspec(noinline)
#else
	#define LAVA_NOINLINE __attribute__((noinline))
#endif

//-------------
// Class Flags
//-------------

class EAccessFlags {
public:
	constexpr EAccessFlags(std::uint16_t value) : value(value) { }

	operator std::uint16_t() const { return this->value; }

	friend constexpr bool operator==(EAccessFlags lhs, EAccessFlags rhs) { return lhs.value == rhs.value; }
	friend constexpr bool operator!=(EAccessFlags lhs, EAccessFlags rhs) { return lhs.value != rhs.value; }
	friend constexpr bool operator>(EAccessFlags lhs, EAccessFlags rhs) { return lhs.value > rhs.value; }
	friend constexpr bool operator<(EAccessFlags lhs, EAccessFlags rhs) { return lhs.value < rhs.value; }
	friend constexpr bool operator>=(EAccessFlags lhs, EAccessFlags rhs) { return lhs.value >= rhs.value; }
	friend constexpr bool operator<=(EAccessFlags lhs, EAccessFlags rhs) { return lhs.value <= rhs.value; }
	friend constexpr EAccessFlags operator&(EAccessFlags lhs, EAccessFlags rhs) { return lhs.value & rhs.value; }
	friend constexpr EAccessFlags operator|(EAccessFlags lhs, EAccessFlags rhs) { return lhs.value | rhs.value; }
	friend constexpr EAccessFlags operator^(EAccessFlags lhs, EAccessFlags rhs) { return lhs.value ^ rhs.value; }
	friend constexpr EAccessFlags operator~(EAccessFlags flags) { return ~flags.value; }
	friend constexpr EAccessFlags operator<<(EAccessFlags flags, std::size_t count) { return flags.value << count; }
	friend constexpr EAccessFlags operator>>(EAccessFlags flags, std::size_t count) { return flags.value >> count; }
	friend std::ostream& operator<<(std::ostream& stream, EAccessFlags flags);

private:
	std::uint16_t value;
};

namespace EAccessFlag {
	static constexpr EAccessFlags Public       = 0x0001;
	static constexpr EAccessFlags Private      = 0x0002;
	static constexpr EAccessFlags Protected    = 0x0004;
	static constexpr EAccessFlags Static       = 0x0008;
	static constexpr EAccessFlags Final        = 0x0010;
	static constexpr EAccessFlags Super        = 0x0020;
	static constexpr EAccessFlags Synchronized = 0x0020;
	static constexpr EAccessFlags Volatile     = 0x0040;
	static constexpr EAccessFlags Bridge       = 0x0040;
	static constexpr EAccessFlags Transient    = 0x0080;
	static constexpr EAccessFlags Varargs      = 0x0080;
	static constexpr EAccessFlags Native       = 0x0100;
	static constexpr EAccessFlags Interface    = 0x0200;
	static constexpr EAccessFlags Abstract     = 0x0400;
	static constexpr EAccessFlags Strict       = 0x0800;
	static constexpr EAccessFlags Synthetic    = 0x1000;
	static constexpr EAccessFlags Annotation   = 0x2000;
	static constexpr EAccessFlags Enum         = 0x4000;
	static constexpr EAccessFlags Module       = 0x8000;
} // namespace EAccessFlag

//------------------
// Class structures
//------------------

template <class Left, class Right>
union LavaUBCast {
	static_assert(!std::is_same_v<Left, Right>);

	constexpr LavaUBCast(Left left) : left(left) { }
	LavaUBCast(const LavaUBCast&) = delete;
	LavaUBCast(LavaUBCast&&)      = delete;
	LavaUBCast& operator=(const LavaUBCast&) = delete;
	LavaUBCast& operator=(LavaUBCast&&) = delete;
	~LavaUBCast() { }

	Left left;
	Right right;
};

struct Field;
struct Method;
struct Class;

struct Field {
	std::string name;
	std::string descriptor;
	EAccessFlags accessFlags = EAccessFlag::Public;
};

struct Method {
	~Method();

	std::string name;
	std::string descriptor;
	EAccessFlags accessFlags = EAccessFlag::Public;
	std::size_t codeLength   = 0;
	std::uint8_t* pCode      = nullptr;
	bool allocated           = false;

	template <class T>
	void setMethod(T method) { pCode = LavaUBCast<T, std::uint8_t*>(method).right; }

	void allocateCode(std::vector<std::uint8_t>& code);
	void makeCodeReadWrite();
	void makeCodeExecutable();
	bool isInvokable() const { return this->pCode; }
	template <class R, class... Ts>
	R invoke(Ts&&... args) {
		if constexpr (std::is_void_v<R>)
			LavaUBCast<void*, void(LAVA_MICROSOFT_CALL_ABI*)(Ts...)>(this->pCode).right(args...);
		else
			return LavaUBCast<void*, R(LAVA_MICROSOFT_CALL_ABI*)(Ts...)>(this->pCode).right(args...);
	}
};

struct Class {
	std::string name;
	EAccessFlags accessFlags = EAccessFlag::Public;
	std::vector<Class*> supers;
	std::vector<Field> fields;
	std::vector<Method> methods;

	Method* getMethod(std::string_view name);
	Method* getMethodc(const char* name);
	Method& getMethodError(std::string_view name);
	Method& getMethodErrorc(const char* name);
	Method* getMethodFromDescriptor(std::string_view descriptor);
	Method* getMethodFromDescriptorc(const char* descriptor);
	Method& getMethodFromDescriptorError(std::string_view descriptor);
	Method& getMethodFromDescriptorErrorc(const char* descriptor);
};<file_sep>#include "ClassRegistry.h"

#include <filesystem>
#include <iostream>

void debugPrintClass(Class& clazz) {
	std::cout << "Class '" << clazz.name << "'\n";
	std::cout << "\tAccess Flags: '" << clazz.accessFlags << "'\n";
	for (std::size_t i = 0; i < clazz.supers.size(); i++)
		std::cout << "\tSuper '" << clazz.supers[i]->name << "'\n";
	for (std::size_t i = 0; i < clazz.fields.size(); i++) {
		auto& field = clazz.fields[i];
		std::cout << "\tField '" << field.name << "'\n";
		std::cout << "\t\tDescriptor: '" << field.descriptor << "'\n";
		std::cout << "\t\tAccessFlags: '" << field.accessFlags << "'\n";
	}
	for (std::size_t i = 0; i < clazz.methods.size(); i++) {
		auto& method = clazz.methods[i];
		std::cout << "\tMethod '" << method.name << "'\n";
		std::cout << "\t\tDescriptor: '" << method.descriptor << "'\n";
		std::cout << "\t\tAccessFlags: '" << method.accessFlags << "'\n";
		std::cout << "\t\tCode:" << std::hex << std::uppercase << "\n\t\t\t";
		std::size_t column = 0;
		for (std::size_t j = 0; j < method.codeLength; j++) {
			if (column > 0) {
				if ((column % 8) == 0)
					std::cout << "  ";
				else
					std::cout << " ";
			}
			std::uint64_t byte = static_cast<uint64_t>(method.pCode[j]);
			if (byte <= 0xF) std::cout << '0';
			std::cout << byte;
			column++;
			if (column >= 16) {
				std::cout << "\n\t\t\t";
				column = 0;
			}
		}
		std::cout << std::dec << std::nouppercase << "\n";
	}
}

LAVA_MICROSOFT_CALL_ABI std::uint64_t returnFirstArg(std::uint64_t arg) {
	return arg + 6;
}

int main() {
	// Add current working directory to the class paths
	globalClassRegistry->addClassPath(".");
	globalClassRegistry->setPreloadRequiredClasses(true);

#if 0
	// Construct a new class before starting app
	auto otherClazz = globalClassRegistry->newClass("Other");
	Method otherClazzL;
	otherClazzL.name       = "L";
	otherClazzL.descriptor = "L";
	otherClazzL.setMethod(&returnFirstArg);
	otherClazz->methods.push_back(otherClazzL);
#endif

	// Load class "Test" from the "Test.lclass" file in the "Run" directory
	auto& clazz = globalClassRegistry->loadClassErrorc("Test");
	// Debug print class information
	debugPrintClass(clazz);
	// Invoke the method 'P' in the class
	auto& method = clazz.getMethodFromDescriptorErrorc("P");

	std::uint64_t result = method.invoke<int, std::uint64_t, std::uint64_t, std::uint64_t>(1, 2, 3);
	// Print the return value from the method
	std::cout << "Returned: " << std::hex << std::uppercase << result << std::dec << std::nouppercase << "\n";
}<file_sep># LavaTest
 A test repo for a Java and C combination language
<file_sep>#include "ByteBuffer.h"

#include <fstream>

void ByteBuffer::readFromFile(const std::filesystem::path& filename) {
	std::ifstream stream(filename, std::ios::binary | std::ios::ate);
	if (stream) {
		this->offset         = 0;
		std::size_t filesize = stream.tellg();
		this->bytes.resize(filesize);
		stream.seekg(0);
		stream.read(reinterpret_cast<char*>(this->bytes.data()), filesize);
		stream.close();
	}
}

std::size_t ByteBuffer::getUI1s(std::vector<std::uint8_t>& vec, std::size_t position, std::size_t length) const {
	if (position < this->bytes.size()) {
		length = std::min(this->bytes.size() - position, length);
		vec.resize(length);
		for (std::size_t i = 0; i < length; i++)
			vec[i] = getUI1(position + i);
		return length;
	}
	return 0;
}

std::size_t ByteBuffer::getUI2s(std::vector<std::uint16_t>& vec, std::size_t position, std::size_t length) const {
	if (position < this->bytes.size()) {
		length = std::min(this->bytes.size() - position, length * 2) / 2;
		vec.resize(length);
		for (std::size_t i = 0; i < length; i++)
			vec[i] = getUI1(position + (i * 2));
		return length;
	}
	return 0;
}

std::size_t ByteBuffer::getUI4s(std::vector<std::uint32_t>& vec, std::size_t position, std::size_t length) const {
	if (position < this->bytes.size()) {
		length = std::min(this->bytes.size() - position, length * 4) / 4;
		vec.resize(length);
		for (std::size_t i = 0; i < length; i++)
			vec[i] = getUI1(position + (i * 4));
		return length;
	}
	return 0;
}

std::size_t ByteBuffer::getUI8s(std::vector<std::uint64_t>& vec, std::size_t position, std::size_t length) const {
	if (position < this->bytes.size()) {
		length = std::min(this->bytes.size() - position, length * 8) / 8;
		vec.resize(length);
		for (std::size_t i = 0; i < length; i++)
			vec[i] = getUI1(position + (i * 8));
		return length;
	}
	return 0;
}

std::string_view ByteBuffer::getString(std::size_t position, std::size_t length) const {
	if (position < this->bytes.size()) {
		length = std::min(this->bytes.size() - position, length);
		return std::string_view(reinterpret_cast<const char*>(&this->bytes[position]), length);
	}
	return {};
}<file_sep>#include <filesystem>
#include <fstream>
#include <iostream>
#include <unordered_map>
#include <vector>

struct Field {
	std::uint16_t accessFlag = 0x0001;
	std::string name;
	std::string descriptor;
};

struct MethodRef {
	std::string className;
	std::string methodDescriptor;
	std::uint32_t codeOffset;
};

struct Method {
	std::uint16_t accessFlag = 0x0001;
	std::string name;
	std::string descriptor;
	std::vector<std::uint8_t> code;
	std::vector<MethodRef> methodRefs;
};

int main(int argc, const char** argv) {
	std::filesystem::path file;
	if (argc < 2) {
		std::cerr << "Missing output file argument, using default 'Test.lclass'" << std::endl;
		file = "Test.lclass";
	} else {
		file = argv[1];
	}

	std::ofstream lclassFile(file, std::ios::binary);
	if (lclassFile) {
		std::uint32_t magic       = 0x484F544C;
		std::uint16_t version     = 1;
		std::uint16_t accessFlags = 0x0001;
		std::string className;
		std::vector<std::string> superClassNames;
		std::vector<Field> fields;
		std::vector<Method> methods;

		char ws;
		std::cout << "Class name: ";
		std::cin >> className;
		std::cin.ignore(1000, '\n');
		while (true) {
			std::string superClass;
			std::cout << "Super class name: ";
			std::getline(std::cin, superClass);
			if (superClass.empty()) break;
			superClassNames.push_back(std::move(superClass.substr(0, superClass.find_first_of(' '))));
		}
		while (true) {
			Field field;
			std::cout << "Field name: ";
			std::getline(std::cin, field.name);
			if (field.name.empty()) break;
			field.name = field.name.substr(0, field.name.find_first_of(' '));
			std::cout << "Field descriptor: ";
			std::cin >> field.descriptor;
			std::cin.ignore(1000, '\n');
			fields.push_back(std::move(field));
		}
		while (true) {
			Method method;
			std::cout << "Method name: ";
			std::getline(std::cin, method.name);
			if (method.name.empty()) break;
			method.name = method.name.substr(0, method.name.find_first_of(' '));
			std::cout << "Method descriptor: ";
			std::cin >> method.descriptor;
			std::cin.ignore(1000, '\n');
			std::cout << "Method code: ";
			while (true) {
				std::string bytes;
				std::getline(std::cin, bytes);
				if (bytes.empty()) break;
				std::string_view bytesView = bytes;
				std::size_t offset         = 0;
				while (offset < bytes.size()) {
					std::size_t end           = bytesView.find_first_of(' ', offset);
					std::string_view byteView = bytesView.substr(offset, end - offset);
					if ((byteView.size() % 2) == 1) {
						std::cout << "Warning you passed an odd number of nibbles (4 bits), skipping '" << byteView << "'" << std::endl;
						offset = bytesView.find_first_not_of(' ', end);
						continue;
					}

					std::vector<std::uint8_t> code;
					bool skip = false;
					for (std::size_t i = 0; i < byteView.size(); i += 2) {
						char hn = byteView[i];
						char ln = byteView[i + 1];
						std::uint8_t hv;
						std::uint8_t lv;
						if (hn >= '0' && hn <= '9')
							hv = hn - '0';
						else if (hn >= 'a' && hn <= 'f')
							hv = 10 + hn - 'a';
						else if (hn >= 'A' && hn <= 'F')
							hv = 10 + hn - 'A';
						else {
							std::cout << "Warning Nibble " << i << " is not one of (0-9, a-f, A-F), skipping '" << byteView << "'" << std::endl;
							skip = true;
							break;
						}
						if (ln >= '0' && ln <= '9')
							lv = ln - '0';
						else if (ln >= 'a' && ln <= 'f')
							lv = 10 + ln - 'a';
						else if (ln >= 'A' && ln <= 'F')
							lv = 10 + ln - 'A';
						else {
							std::cout << "Warning Nibble " << (i + 1) << " is not one of (0-9, a-f, A-F), skipping '" << byteView << "'" << std::endl;
							skip = true;
							break;
						}
						std::uint8_t byte = hv << 4 | lv;
						code.push_back(byte);
					}

					if (skip) {
						offset = bytesView.find_first_not_of(' ', end);
						continue;
					}
					method.code.insert(method.code.end(), code.begin(), code.end());
					offset = bytesView.find_first_not_of(' ', end);
				}
			}

			while (true) {
				MethodRef methodRef;
				std::cout << "Method ref class name: ";
				std::getline(std::cin, methodRef.className);
				if (methodRef.className.empty()) break;
				std::cout << "Method ref method descriptor: ";
				std::cin >> methodRef.methodDescriptor;
				std::cout << "Method ref code offset: ";
				std::cin >> methodRef.codeOffset;
				std::cin.ignore(1000, '\n');
				method.methodRefs.push_back(std::move(methodRef));
			}

			methods.push_back(std::move(method));
		}

		lclassFile.write(reinterpret_cast<char*>(&magic), 4);
		lclassFile.write(reinterpret_cast<char*>(&version), 2);
		std::unordered_map<std::string, std::uint16_t> stringToConstantPoolIndex;
		std::unordered_map<std::string, std::uint16_t> classToConstantPoolIndex;
		classToConstantPoolIndex.insert({ className, 0 });
		stringToConstantPoolIndex.insert({ className, 0 });
		for (auto& superClass : superClassNames) {
			classToConstantPoolIndex.insert({ superClass, 0 });
			stringToConstantPoolIndex.insert({ superClass, 0 });
		}
		for (auto& field : fields) {
			stringToConstantPoolIndex.insert({ field.name, 0 });
			stringToConstantPoolIndex.insert({ field.descriptor, 0 });
		}
		for (auto& method : methods) {
			stringToConstantPoolIndex.insert({ method.name, 0 });
			stringToConstantPoolIndex.insert({ method.descriptor, 0 });
			if (!method.code.empty()) stringToConstantPoolIndex.insert({ "code", 0 });
			if (!method.methodRefs.empty()) stringToConstantPoolIndex.insert({ "methodref", 0 });
			for (auto& methodRef : method.methodRefs) {
				stringToConstantPoolIndex.insert({ methodRef.className, 0 });
				stringToConstantPoolIndex.insert({ methodRef.methodDescriptor, 0 });
			}
		}
		std::uint16_t constantPoolCount = stringToConstantPoolIndex.size() + classToConstantPoolIndex.size() + 1;
		lclassFile.write(reinterpret_cast<char*>(&constantPoolCount), 2);
		std::uint16_t currentConstantPoolIndex = 1;
		for (auto& stringConstant : stringToConstantPoolIndex) {
			std::uint8_t tag = 2;
			lclassFile.write(reinterpret_cast<char*>(&tag), 1);
			std::uint32_t stringLength = static_cast<std::uint32_t>(stringConstant.first.size());
			lclassFile.write(reinterpret_cast<char*>(&stringLength), 4);
			lclassFile.write(stringConstant.first.data(), stringLength);
			stringConstant.second = currentConstantPoolIndex++;
		}
		for (auto& classConstant : classToConstantPoolIndex) {
			std::uint8_t tag = 1;
			lclassFile.write(reinterpret_cast<char*>(&tag), 1);
			auto itr = stringToConstantPoolIndex.find(classConstant.first);
			if (itr == stringToConstantPoolIndex.end()) {
				std::cerr << "An unexpected error occured: Class name was not found in the constant pool, please try again." << std::endl;
				lclassFile.close();
				return EXIT_FAILURE;
			}
			std::uint16_t index = itr->second;
			lclassFile.write(reinterpret_cast<char*>(&index), 2);
			classConstant.second = currentConstantPoolIndex++;
		}
		lclassFile.write(reinterpret_cast<char*>(&accessFlags), 2);
		{
			auto itr = classToConstantPoolIndex.find(className);
			if (itr == classToConstantPoolIndex.end()) {
				std::cerr << "An unexpected error occured: Class name was not found in the constant pool, please try again." << std::endl;
				lclassFile.close();
				return EXIT_FAILURE;
			}
			std::uint16_t index = itr->second;
			lclassFile.write(reinterpret_cast<char*>(&index), 2);
		}
		std::uint16_t superCount = static_cast<std::uint16_t>(superClassNames.size());
		lclassFile.write(reinterpret_cast<char*>(&superCount), 2);
		for (auto& superClass : superClassNames) {
			auto itr = classToConstantPoolIndex.find(superClass);
			if (itr == classToConstantPoolIndex.end()) {
				std::cerr << "An unexpected error occured: Super class name was not found in the constant pool, please try again." << std::endl;
				lclassFile.close();
				return EXIT_FAILURE;
			}
			std::uint16_t index = itr->second;
			lclassFile.write(reinterpret_cast<char*>(&index), 2);
		}
		std::uint16_t fieldCount = static_cast<std::uint16_t>(fields.size());
		lclassFile.write(reinterpret_cast<char*>(&fieldCount), 2);
		for (auto& field : fields) {
			lclassFile.write(reinterpret_cast<char*>(&field.accessFlag), 2);
			{
				auto itr = stringToConstantPoolIndex.find(field.name);
				if (itr == stringToConstantPoolIndex.end()) {
					std::cerr << "An unexpected error occured: Field name was not found in the constant pool, please try again." << std::endl;
					lclassFile.close();
					return EXIT_FAILURE;
				}
				std::uint16_t index = itr->second;
				lclassFile.write(reinterpret_cast<char*>(&index), 2);
			}
			{
				auto itr = stringToConstantPoolIndex.find(field.descriptor);
				if (itr == stringToConstantPoolIndex.end()) {
					std::cerr << "An unexpected error occured: Field descriptor was not found in the constant pool, please try again." << std::endl;
					lclassFile.close();
					return EXIT_FAILURE;
				}
				std::uint16_t index = itr->second;
				lclassFile.write(reinterpret_cast<char*>(&index), 2);
			}
			std::uint16_t attributeCount = 0;
			lclassFile.write(reinterpret_cast<char*>(attributeCount), 2);
		}
		std::uint16_t methodCount = static_cast<std::uint16_t>(methods.size());
		lclassFile.write(reinterpret_cast<char*>(&methodCount), 2);
		for (auto& method : methods) {
			lclassFile.write(reinterpret_cast<char*>(&method.accessFlag), 2);
			{
				auto itr = stringToConstantPoolIndex.find(method.name);
				if (itr == stringToConstantPoolIndex.end()) {
					std::cerr << "An unexpected error occured: Method name was not found in the constant pool, please try again." << std::endl;
					lclassFile.close();
					return EXIT_FAILURE;
				}
				std::uint16_t index = itr->second;
				lclassFile.write(reinterpret_cast<char*>(&index), 2);
			}
			{
				auto itr = stringToConstantPoolIndex.find(method.descriptor);
				if (itr == stringToConstantPoolIndex.end()) {
					std::cerr << "An unexpected error occured: Method descriptor was not found in the constant pool, please try again." << std::endl;
					lclassFile.close();
					return EXIT_FAILURE;
				}
				std::uint16_t index = itr->second;
				lclassFile.write(reinterpret_cast<char*>(&index), 2);
			}
			std::uint16_t attributeCount = (!method.code.empty()) + (!method.methodRefs.empty());
			lclassFile.write(reinterpret_cast<char*>(&attributeCount), 2);
			if (!method.code.empty()) {
				{
					auto itr = stringToConstantPoolIndex.find("code");
					if (itr == stringToConstantPoolIndex.end()) {
						std::cerr << "An unexpected error occured: \"code\" was not found in the constant pool, please try again." << std::endl;
						lclassFile.close();
						return EXIT_FAILURE;
					}
					std::uint16_t index = itr->second;
					lclassFile.write(reinterpret_cast<char*>(&index), 2);
				}
				std::uint32_t codeLength = static_cast<std::uint32_t>(method.code.size());
				lclassFile.write(reinterpret_cast<char*>(&codeLength), 4);
				lclassFile.write(reinterpret_cast<char*>(method.code.data()), codeLength);
			}
			for (auto& methodRef : method.methodRefs) {
				{
					auto itr = stringToConstantPoolIndex.find("methodref");
					if (itr == stringToConstantPoolIndex.end()) {
						std::cerr << "An unexpected error occured: \"methodref\" was not found in the constant pool, please try again." << std::endl;
						lclassFile.close();
						return EXIT_FAILURE;
					}
					std::uint16_t index = itr->second;
					lclassFile.write(reinterpret_cast<char*>(&index), 2);
				}
				std::uint32_t attributeLength = 8;
				lclassFile.write(reinterpret_cast<char*>(&attributeLength), 4);
				{
					auto itr = stringToConstantPoolIndex.find(methodRef.className);
					if (itr == stringToConstantPoolIndex.end()) {
						std::cerr << "An unexpected error occured: Method ref class name was not found in the constant pool, please try again." << std::endl;
						lclassFile.close();
						return EXIT_FAILURE;
					}
					std::uint16_t index = itr->second;
					lclassFile.write(reinterpret_cast<char*>(&index), 2);
				}
				{
					auto itr = stringToConstantPoolIndex.find(methodRef.methodDescriptor);
					if (itr == stringToConstantPoolIndex.end()) {
						std::cerr << "An unexpected error occured: Method ref method descriptor was not found in the constant pool, please try again." << std::endl;
						lclassFile.close();
						return EXIT_FAILURE;
					}
					std::uint16_t index = itr->second;
					lclassFile.write(reinterpret_cast<char*>(&index), 2);
				}
				lclassFile.write(reinterpret_cast<char*>(&methodRef.codeOffset), 4);
			}
		}

		lclassFile.close();
		return EXIT_SUCCESS;
	}
	return EXIT_FAILURE;
}<file_sep>#pragma once

#include <cstdint>
#include <cstring>

#include <filesystem>
#include <string>
#include <string_view>
#include <vector>

struct ByteBuffer {
public:
	void readFromFile(const std::filesystem::path& filename);

	auto getOffset() const { return this->offset; }
	void setOffset(std::size_t offset) { this->offset = offset; }
	auto size() const { return this->bytes.size(); }
	auto data() { return this->bytes.data(); }
	auto data() const { return this->bytes.data(); }

	std::uint8_t getUI1(std::size_t position) const {
		if (position < this->bytes.size()) return this->bytes[position];
		return 0;
	}
	std::uint16_t getUI2(std::size_t position) const { return getUI1(position) | getUI1(position + 1) << 8; }
	std::uint32_t getUI4(std::size_t position) const { return getUI2(position) | getUI2(position + 2) << 16; }
	std::uint64_t getUI8(std::size_t position) const { return getUI4(position) | static_cast<std::uint64_t>(getUI4(position + 4)) << 32; }
	std::int8_t getI1(std::size_t position) const { return static_cast<std::int8_t>(getUI1(position)); }
	std::int16_t getI2(std::size_t position) const { return static_cast<std::int16_t>(getUI2(position)); }
	std::int32_t getI4(std::size_t position) const { return static_cast<std::int32_t>(getUI4(position)); }
	std::int64_t getI8(std::size_t position) const { return static_cast<std::int64_t>(getUI8(position)); }

	std::size_t getUI1s(std::vector<std::uint8_t>& vec, std::size_t position, std::size_t length) const;
	std::size_t getUI2s(std::vector<std::uint16_t>& vec, std::size_t position, std::size_t length) const;
	std::size_t getUI4s(std::vector<std::uint32_t>& vec, std::size_t position, std::size_t length) const;
	std::size_t getUI8s(std::vector<std::uint64_t>& vec, std::size_t position, std::size_t length) const;
	std::string_view getString(std::size_t position, std::size_t length) const;
	std::string_view getStringNT(std::size_t position) {
		std::size_t length = 0;
		while ((position + length) < this->bytes.size() && this->bytes[position + length] != 0)
			length++;
		return getString(position, length);
	}

	std::uint8_t getUI1() { return getUI1(this->offset++); }
	std::uint16_t getUI2() {
		std::uint16_t out = getUI2(this->offset);
		this->offset += 2;
		return out;
	}
	std::uint32_t getUI4() {
		std::uint32_t out = getUI4(this->offset);
		this->offset += 4;
		return out;
	}
	std::uint64_t getUI8() {
		std::uint64_t out = getUI8(this->offset);
		this->offset += 8;
		return out;
	}
	std::int8_t getI1() { return static_cast<std::int8_t>(getUI1()); }
	std::int16_t getI2() { return static_cast<std::int16_t>(getUI2()); }
	std::int32_t getI4() { return static_cast<std::int32_t>(getUI4()); }
	std::int64_t getI8() { return static_cast<std::int64_t>(getUI8()); }

	std::size_t getUI1s(std::vector<std::uint8_t>& vec, std::size_t length) {
		std::size_t read = getUI1s(vec, this->offset, length);
		this->offset += read;
		return read;
	}
	std::size_t getUI2s(std::vector<std::uint16_t>& vec, std::size_t length) {
		std::size_t read = getUI2s(vec, this->offset, length);
		this->offset += read * 2;
		return read;
	}
	std::size_t getUI4s(std::vector<std::uint32_t>& vec, std::size_t length) {
		std::size_t read = getUI4s(vec, this->offset, length);
		this->offset += read * 4;
		return read;
	}
	std::size_t getUI8s(std::vector<std::uint64_t>& vec, std::size_t length) {
		std::size_t read = getUI8s(vec, this->offset, length);
		this->offset += read * 8;
		return read;
	}
	std::string_view getString(std::size_t length) {
		std::string_view view = getString(this->offset, length);
		this->offset += view.size();
		return view;
	}
	std::string_view getStringNT() {
		std::string_view view = getStringNT(this->offset);
		this->offset += view.size();
		return view;
	}

	void addUI1(std::uint8_t value, std::size_t position) { bytes.insert(bytes.begin() + position, value); }
	void addUI2(std::uint16_t value, std::size_t position) {
		this->bytes.reserve(this->bytes.capacity() + 2);
		this->bytes.insert(this->bytes.begin() + position, { static_cast<std::uint8_t>(value & 0xFF), static_cast<std::uint8_t>((value >> 8) & 0xFF) });
	}
	void addUI4(std::uint32_t value, std::size_t position) {
		this->bytes.reserve(this->bytes.capacity() + 4);
		this->bytes.insert(this->bytes.begin() + position, { static_cast<std::uint8_t>(value & 0xFF), static_cast<std::uint8_t>((value >> 8) & 0xFF), static_cast<std::uint8_t>((value >> 16) & 0xFF), static_cast<std::uint8_t>((value >> 24) & 0xFF) });
	}
	void addUI8(std::uint64_t value, std::size_t position) {
		this->bytes.reserve(this->bytes.capacity() + 8);
		this->bytes.insert(this->bytes.begin() + position, { static_cast<std::uint8_t>(value & 0xFF), static_cast<std::uint8_t>((value >> 8) & 0xFF), static_cast<std::uint8_t>((value >> 16) & 0xFF), static_cast<std::uint8_t>((value >> 24) & 0xFF), static_cast<std::uint8_t>((value >> 32) & 0xFF), static_cast<std::uint8_t>((value >> 40) & 0xFF), static_cast<std::uint8_t>((value >> 48) & 0xFF), static_cast<std::uint8_t>((value >> 56) & 0xFF) });
	}
	void addI1(std::int8_t value, std::size_t position) { addUI1(static_cast<std::uint8_t>(value), position); }
	void addI2(std::int16_t value, std::size_t position) { addUI2(static_cast<std::uint16_t>(value), position); }
	void addI4(std::int32_t value, std::size_t position) { addUI4(static_cast<std::uint32_t>(value), position); }
	void addI8(std::int64_t value, std::size_t position) { addUI8(static_cast<std::uint64_t>(value), position); }
	void addUI1(std::uint8_t value) { addUI1(value, this->bytes.size()); }
	void addUI2(std::uint16_t value) { addUI2(value, this->bytes.size()); }
	void addUI4(std::uint32_t value) { addUI4(value, this->bytes.size()); }
	void addUI8(std::uint64_t value) { addUI8(value, this->bytes.size()); }
	void addI1(std::int8_t value) { addI1(value, this->bytes.size()); }
	void addI2(std::int16_t value) { addI2(value, this->bytes.size()); }
	void addI4(std::int32_t value) { addI4(value, this->bytes.size()); }
	void addI8(std::int64_t value) { addI8(value, this->bytes.size()); }

	void addUI1s(const std::vector<std::uint8_t>& bytes, std::size_t position) {
		this->bytes.reserve(this->bytes.capacity() + bytes.size() * sizeof(std::uint8_t));
		this->bytes.insert(this->bytes.begin() + position, reinterpret_cast<const std::uint8_t*>(bytes.data()), reinterpret_cast<const std::uint8_t*>(bytes.data() + bytes.size()));
	}
	void addUI2s(const std::vector<std::uint16_t>& bytes, std::size_t position) {
		this->bytes.reserve(this->bytes.capacity() + bytes.size() * sizeof(std::uint16_t));
		this->bytes.insert(this->bytes.begin() + position, reinterpret_cast<const std::uint8_t*>(bytes.data()), reinterpret_cast<const std::uint8_t*>(bytes.data() + bytes.size()));
	}
	void addUI4s(const std::vector<std::uint32_t>& bytes, std::size_t position) {
		this->bytes.reserve(this->bytes.capacity() + bytes.size() * sizeof(std::uint32_t));
		this->bytes.insert(this->bytes.begin() + position, reinterpret_cast<const std::uint8_t*>(bytes.data()), reinterpret_cast<const std::uint8_t*>(bytes.data() + bytes.size()));
	}
	void addUI8s(const std::vector<std::uint64_t>& bytes, std::size_t position) {
		this->bytes.reserve(this->bytes.capacity() + bytes.size() * sizeof(std::uint64_t));
		this->bytes.insert(this->bytes.begin() + position, reinterpret_cast<const std::uint8_t*>(bytes.data()), reinterpret_cast<const std::uint8_t*>(bytes.data() + bytes.size()));
	}
	void addI1s(const std::vector<std::int8_t>& bytes, std::size_t position) {
		this->bytes.reserve(this->bytes.capacity() + bytes.size() * sizeof(std::int8_t));
		this->bytes.insert(this->bytes.begin() + position, reinterpret_cast<const std::uint8_t*>(bytes.data()), reinterpret_cast<const std::uint8_t*>(bytes.data() + bytes.size()));
	}
	void addI2s(const std::vector<std::int16_t>& bytes, std::size_t position) {
		this->bytes.reserve(this->bytes.capacity() + bytes.size() * sizeof(std::int16_t));
		this->bytes.insert(this->bytes.begin() + position, reinterpret_cast<const std::uint8_t*>(bytes.data()), reinterpret_cast<const std::uint8_t*>(bytes.data() + bytes.size()));
	}
	void addI4s(const std::vector<std::int32_t>& bytes, std::size_t position) {
		this->bytes.reserve(this->bytes.capacity() + bytes.size() * sizeof(std::int32_t));
		this->bytes.insert(this->bytes.begin() + position, reinterpret_cast<const std::uint8_t*>(bytes.data()), reinterpret_cast<const std::uint8_t*>(bytes.data() + bytes.size()));
	}
	void addI8s(const std::vector<std::int64_t>& bytes, std::size_t position) {
		this->bytes.reserve(this->bytes.capacity() + bytes.size() * sizeof(std::int64_t));
		this->bytes.insert(this->bytes.begin() + position, reinterpret_cast<const std::uint8_t*>(bytes.data()), reinterpret_cast<const std::uint8_t*>(bytes.data() + bytes.size()));
	}
	void addString(std::string_view str, std::size_t position) {
		this->bytes.reserve(this->bytes.capacity() + str.size() * sizeof(std::string_view::value_type));
		this->bytes.insert(this->bytes.begin() + position, str.data(), str.data() + str.size());
	}
	void addStringNT(std::string_view str, std::size_t position) {
		this->bytes.reserve(this->bytes.capacity() + str.size() * sizeof(std::string_view::value_type));
		this->bytes.insert(this->bytes.begin() + position, str.data(), str.data() + str.size());
		this->bytes.insert(this->bytes.begin() + position + str.size() + 1, 0);
	}

	void addUI1s(const std::vector<uint8_t>& bytes) { addUI1s(bytes, this->bytes.size()); }
	void addString(std::string_view str) { addString(str, this->bytes.size()); }
	void addStringNT(std::string_view str) { addStringNT(str, this->bytes.size()); }

	void setUI1(std::uint8_t value, std::size_t position) {
		if (position < bytes.size()) bytes[position] = value;
	}
	void setUI2(std::uint16_t value, std::size_t position) {
		if (position + 1 < bytes.size()) {
			bytes[position]     = static_cast<std::uint8_t>(value & 0xFF);
			bytes[position + 1] = static_cast<std::uint8_t>((value >> 8) & 0xFF);
		}
	}
	void setUI4(std::uint32_t value, std::size_t position) {
		if (position + 3 < bytes.size()) {
			bytes[position]     = static_cast<std::uint8_t>(value & 0xFF);
			bytes[position + 1] = static_cast<std::uint8_t>((value >> 8) & 0xFF);
			bytes[position + 2] = static_cast<std::uint8_t>((value >> 16) & 0xFF);
			bytes[position + 3] = static_cast<std::uint8_t>((value >> 24) & 0xFF);
		}
	}
	void setUI8(std::uint64_t value, std::size_t position) {
		if (position + 7 < bytes.size()) {
			bytes[position]     = static_cast<std::uint8_t>(value & 0xFF);
			bytes[position + 1] = static_cast<std::uint8_t>((value >> 8) & 0xFF);
			bytes[position + 2] = static_cast<std::uint8_t>((value >> 16) & 0xFF);
			bytes[position + 3] = static_cast<std::uint8_t>((value >> 24) & 0xFF);
			bytes[position + 4] = static_cast<std::uint8_t>((value >> 32) & 0xFF);
			bytes[position + 5] = static_cast<std::uint8_t>((value >> 40) & 0xFF);
			bytes[position + 6] = static_cast<std::uint8_t>((value >> 48) & 0xFF);
			bytes[position + 7] = static_cast<std::uint8_t>((value >> 56) & 0xFF);
		}
	}
	void setUI1(std::uint8_t value) { setUI1(value, bytes.size()); }
	void setUI2(std::uint16_t value) { setUI2(value, bytes.size()); }
	void setUI4(std::uint32_t value) { setUI4(value, bytes.size()); }
	void setUI8(std::uint64_t value) { setUI8(value, bytes.size()); }

	void setUI1s(const std::vector<std::uint8_t>& bytes, std::size_t position) {
		if ((position + bytes.size() * sizeof(std::uint8_t)) < this->bytes.size())
			std::memcpy(this->bytes.data() + position, bytes.data(), bytes.size() * sizeof(std::uint8_t));
	}
	void setUI2s(const std::vector<std::uint16_t>& bytes, std::size_t position) {
		if ((position + bytes.size() * sizeof(std::uint16_t)) < this->bytes.size())
			std::memcpy(this->bytes.data() + position, bytes.data(), bytes.size() * sizeof(std::uint16_t));
	}
	void setUI4s(const std::vector<std::uint32_t>& bytes, std::size_t position) {
		if ((position + bytes.size() * sizeof(std::uint32_t)) < this->bytes.size())
			std::memcpy(this->bytes.data() + position, bytes.data(), bytes.size() * sizeof(std::uint32_t));
	}
	void setUI8s(const std::vector<std::uint64_t>& bytes, std::size_t position) {
		if ((position + bytes.size() * sizeof(std::uint64_t)) < this->bytes.size())
			std::memcpy(this->bytes.data() + position, bytes.data(), bytes.size() * sizeof(std::uint64_t));
	}
	void setString(std::string_view str, std::size_t position) {
		if ((position + str.size() * sizeof(std::string_view::value_type)) < bytes.size())
			std::memcpy(bytes.data() + position, str.data(), str.size() * sizeof(std::string_view::value_type));
	}
	void setStringNT(std::string_view str, std::size_t position) {
		if ((position + str.size() * sizeof(std::string_view::value_type) + 1) < bytes.size()) {
			std::memcpy(bytes.data() + position, str.data(), str.size() * sizeof(std::string_view::value_type));
			bytes[position + str.size() * sizeof(std::string_view::value_type)] = 0;
		}
	}

private:
	std::size_t offset = 0;
	std::vector<std::uint8_t> bytes;
};<file_sep>workspace("LavaTest")
	configurations({ "Debug", "Release", "Dist" })
	platforms({ "x64" })
	language("C++")
	cppdialect("C++20")
	rtti("Off")
	exceptionhandling("On")
	flags("MultiProcessorCompile")
	defines({ "LAVA_SYSTEM_%{cfg.system}", "LAVA_TOOLSET_%{cfg.toolset}" })

	filter("configurations:Debug")
		defines({ "_DEBUG", "NRELEASE" })
		optimize("Off")
		symbols("On")

	filter("configurations:Release")
		defines({ "_RELEASE", "NDEBUG" })
		optimize("Full")
		symbols("On")

	filter("configurations:Dist")
		defines({ "_RELEASE", "NDEBUG" })
		optimize("Full")
		symbols("Off")

	filter("system:windows")
		toolset("msc")
		defines({ "NOMINMAX", "_CRT_SECURE_NO_WARNINGS" })

	filter({ "toolset:gcc", "system:not windows" })
		buildoptions({ "-maccumulate-outgoing-args" })

	filter({})

	startproject("LavaTest")
	project("Lava")
		kind("ConsoleApp")
		location("Lava")
		targetdir("%{wks.location}/Bin/%{cfg.system}-%{cfg.platform}-%{cfg.buildcfg}")
		objdir("%{wks.location}/BinInt/%{cfg.system}-%{cfg.platform}-%{cfg.buildcfg}/Lava")
		debugdir("%{wks.location}/Run")

		files({ "%{prj.location}/**" })
		removefiles({ "**.vcxproj", "**.vcxproj.*", "**/Makefile", "**.make" })

	project("LavaCompiler")
		kind("ConsoleApp")
		location("LavaCompiler")
		targetdir("%{wks.location}/Bin/%{cfg.system}-%{cfg.platform}-%{cfg.buildcfg}")
		objdir("%{wks.location}/BinInt/%{cfg.system}-%{cfg.platform}-%{cfg.buildcfg}/LavaCompiler")
		debugdir("%{wks.location}/Run")

		files({ "%{prj.location}/**" })
		removefiles({ "**.vcxproj", "**.vcxproj.*", "**/Makefile", "**.make" })

	if _ACTION == "vs2019" then
	project("Run")
		kind("None")
		location("Run")

		files({ "%{prj.location}/**" })
		removefiles({ "**.vcxproj", "**.vcproj.*", "**/Makefile", "**.make" })
	end<file_sep>#include "ClassRegistry.h"
#include "ByteBuffer.h"

#include <cassert>
#include <cstring>

#include <set>

static Class* loadClassV1(ClassRegistry* registry, ByteBuffer& buffer, EClassLoadStatus* loadStatus);

std::ostream& operator<<(std::ostream& stream, EClassLoadStatus status) {
	switch (status) {
	case EClassLoadStatus::Success: return stream << "Success";
	case EClassLoadStatus::FileNotFound: return stream << "FileNotFound";
	case EClassLoadStatus::InvalidMagicNumber: return stream << "InvalidMagicNumber";
	case EClassLoadStatus::InvalidVersion: return stream << "InvalidVersion";
	case EClassLoadStatus::InvalidConstantPool: return stream << "InvalidConstantPool";
	case EClassLoadStatus::InvalidConstantPoolEntry: return stream << "InvalidConstantPoolEntry";
	case EClassLoadStatus::InvalidThisClassEntry: return stream << "InvalidThisClassEntry";
	case EClassLoadStatus::InvalidFieldName: return stream << "InvalidFieldName";
	case EClassLoadStatus::InvalidFieldDescriptor: return stream << "InvalidFieldDescriptor";
	case EClassLoadStatus::InvalidAttributeName: return stream << "InvalidAttributeName";
	case EClassLoadStatus::InvalidMethodName: return stream << "InvalidMethodName";
	case EClassLoadStatus::InvalidMethodDescriptor: return stream << "InvalidMethodDescriptor";
	case EClassLoadStatus::InvalidMethodRefClassName: return stream << "InvalidMethodRefClassName";
	case EClassLoadStatus::InvalidMethodRefMethodDescriptor: return stream << "InvalidMethodRefMethodDescriptor";
	}
	return stream;
}

ClassRegistry* globalClassRegistry = new ClassRegistry();

Class* ClassRegistry::newClass(const std::string& className) {
	auto itr = this->classes.find(className);
	if (itr != this->classes.end()) return nullptr;

	Class* clazz = new Class();
	clazz->name  = className;
	this->classes.insert({ className, clazz });
	return clazz;
}

void ClassRegistry::addClassPath(const std::filesystem::path classPath) {
	this->classPaths.push_back(classPath);
}

Class* ClassRegistry::getClass(const std::string& className) {
	auto itr = this->classes.find(className);
	if (itr != this->classes.end()) return itr->second;
	return nullptr;
}

Class* ClassRegistry::loadClass(const std::string& className, EClassLoadStatus* loadStatus) {
	// Look for the class in the registry
	Class* clazz = getClass(className);
	if (clazz) return clazz;

	std::filesystem::path filename = findClass(className);
	if (filename.empty()) {
		// .lclass file was not found
		if (loadStatus) *loadStatus = EClassLoadStatus::FileNotFound;
		return nullptr;
	}

	// Read .lclass file into a ByteBuffer
	ByteBuffer buffer;
	buffer.readFromFile(filename);

	// Read magic number and check that it is the string "HOTL"
	std::uint32_t magic = buffer.getUI4();
	if (magic != 0x484F544C) {
		// Magic number is not the string "HOTL"
		if (loadStatus) *loadStatus = EClassLoadStatus::InvalidMagicNumber;
		return nullptr;
	}

	// Read version and load class using that version
	std::uint16_t version = buffer.getUI2();
	switch (version) {
	case 1:
		clazz = loadClassV1(this, buffer, loadStatus);
		break;
	default:
		// Version is not one of the loadable versions
		if (loadStatus) *loadStatus = EClassLoadStatus::InvalidVersion;
		return nullptr;
	}
	if (clazz) this->classes.insert({ clazz->name, clazz });
	return clazz;
}

Class* ClassRegistry::loadClassc(const char* className, EClassLoadStatus* loadStatus) {
	return loadClass(std::string(className), loadStatus);
}

Class& ClassRegistry::loadClassError(const std::string& className) {
	EClassLoadStatus loadStatus;
	Class* clazz = loadClass(className, &loadStatus);
	if (!clazz) {
		std::ostringstream stream;
		stream << "Class could not be loaded: '" << loadStatus << "'";
		throw std::runtime_error(stream.str());
	}
	return *clazz;
}

Class& ClassRegistry::loadClassErrorc(const char* className) {
	return loadClassError(std::string(className));
}

Method& ClassRegistry::getMethodErrorc(const char* className, const char* methodName) {
	Class& clazz = loadClassErrorc(className);
	return clazz.getMethodErrorc(methodName);
}

Method& ClassRegistry::getMethodFromDescriptorErrorc(const char* className, const char* methodDescriptor) {
	Class& clazz = loadClassErrorc(className);
	return clazz.getMethodFromDescriptorErrorc(methodDescriptor);
}

std::vector<Class*> ClassRegistry::getLoadedClasses() const {
	std::vector<Class*> classes;
	classes.reserve(this->classes.size());
	for (auto& clazz : this->classes)
		classes.push_back(clazz.second);
	return classes;
}

std::filesystem::path ClassRegistry::findClass(std::string_view className) const {
	// Loop through every path in the class paths
	// And concatenate the class name and .lclass to it
	// Check if the file exists and return it if so
	for (auto& classPath : this->classPaths) {
		std::filesystem::path filename = classPath / className;
		filename.replace_extension("lclass");
		if (std::filesystem::exists(filename))
			return filename;
	}
	return {};
}

//-------------------
// LClass Version 1
//-------------------

static constexpr std::uint8_t ClassConstantClassEntryV1Tag = 1;
static constexpr std::uint8_t ClassConstantUTF8EntryV1Tag  = 2;

struct ClassConstantPoolEntryV1 {
public:
	ClassConstantPoolEntryV1(std::uint8_t tag) : tag(tag) { }

	std::uint8_t getTag() const { return this->tag; }

private:
	std::uint8_t tag;
};

struct ClassConstantClassEntryV1 : public ClassConstantPoolEntryV1 {
	ClassConstantClassEntryV1(std::uint16_t nameIndex) : ClassConstantPoolEntryV1(ClassConstantClassEntryV1Tag), nameIndex(nameIndex) { }

	std::uint16_t nameIndex;
};

struct ClassConstantUTF8EntryV1 : public ClassConstantPoolEntryV1 {
	ClassConstantUTF8EntryV1(std::string_view string) : ClassConstantPoolEntryV1(ClassConstantUTF8EntryV1Tag), string(string) { }

	std::string string;
};

struct ClassConstantPoolV1 {
public:
	void reserve(std::size_t size) { this->entries.reserve(size); }
	std::size_t size() const { return this->entries.size(); }
	void addEntry(ClassConstantPoolEntryV1* entry) { this->entries.push_back(entry); }
	ClassConstantPoolEntryV1* getEntry(std::size_t index) const {
		if (index == 0 || index > this->entries.size()) return nullptr;
		return this->entries[index - 1];
	}

	bool validate() const {
		// Loop through all entries in the constant pool
		// Check if it is a valid tag and if the entry is valid
		for (auto entry : this->entries) {
			switch (entry->getTag()) {
			case ClassConstantClassEntryV1Tag: {
				// Check if the Class tag is pointing to a UTF8 tag
				auto clazzEntry = reinterpret_cast<ClassConstantClassEntryV1*>(entry);
				auto nameEntry  = getEntry(clazzEntry->nameIndex);
				if (!nameEntry || nameEntry->getTag() != ClassConstantUTF8EntryV1Tag)
					return false;
				break;
			}
			case ClassConstantUTF8EntryV1Tag: break; // UTF8 tag's are always valid
			default: return false;
			}
		}
		return true;
	}

	auto begin() { return this->entries.begin(); }
	auto begin() const { return this->entries.begin(); }
	auto cbegin() const { return this->entries.cbegin(); }
	auto rbegin() { return this->entries.rbegin(); }
	auto rbegin() const { return this->entries.rbegin(); }
	auto crbegin() const { return this->entries.crbegin(); }
	auto end() { return this->entries.end(); }
	auto end() const { return this->entries.end(); }
	auto cend() const { return this->entries.cend(); }
	auto rend() { return this->entries.rend(); }
	auto rend() const { return this->entries.rend(); }
	auto crend() const { return this->entries.crend(); }

private:
	std::vector<ClassConstantPoolEntryV1*> entries;
};

ClassConstantPoolEntryV1* readConstantPoolEntryV1(ByteBuffer& buffer, EClassLoadStatus* loadStatus) {
	// Get tag and construct the specified entry
	std::uint8_t tag = buffer.getUI1();
	switch (tag) {
	case ClassConstantClassEntryV1Tag: return new ClassConstantClassEntryV1(buffer.getUI2());
	case ClassConstantUTF8EntryV1Tag: {
		std::uint32_t length = buffer.getUI4();
		return new ClassConstantUTF8EntryV1(buffer.getString(length));
	}
	default:
		if (loadStatus) *loadStatus = EClassLoadStatus::InvalidConstantPoolEntry;
		return nullptr;
	}
}

struct ClassAttributeV1 {
	ClassAttributeV1(std::string&& name) : name(std::move(name)) { }

	std::string name;
};

struct ClassAttributeUnknownV1 : public ClassAttributeV1 {
	ClassAttributeUnknownV1(std::string&& name, std::vector<std::uint8_t>&& info) : info(std::move(info)), ClassAttributeV1(std::move(name)) { }

	std::vector<std::uint8_t> info;
};

struct ClassAttributeMethodCodeV1 : public ClassAttributeV1 {
	ClassAttributeMethodCodeV1(std::vector<std::uint8_t>&& code) : code(std::move(code)), ClassAttributeV1("code") { }

	std::vector<std::uint8_t> code;
};

struct ClassAttributeMethodRefV1 : public ClassAttributeV1 {
	ClassAttributeMethodRefV1(std::uint16_t classNameIndex, std::uint16_t methodDescriptorIndex, std::uint32_t byteOffset) : classNameIndex(classNameIndex), methodDescriptorIndex(methodDescriptorIndex), byteOffset(byteOffset), ClassAttributeV1("methodref") { }

	std::uint16_t classNameIndex;
	std::uint16_t methodDescriptorIndex;
	std::uint32_t byteOffset;
};

struct ClassFieldEntryV1 {
	EAccessFlags accessFlags = 0;
	std::string name;
	std::string descriptor;
	std::vector<ClassAttributeV1*> attributes;
};

struct ClassMethodEntryV1 {
	EAccessFlags accessFlags = 0;
	std::string name;
	std::string descriptor;
	std::vector<ClassAttributeV1*> attributes;
};

struct ClassMethodRefV1 {
	std::string className;
	std::string methodDescriptor;
	std::uint32_t byteOffset = 0;
};

ClassAttributeV1* readAttributeEntryV1(ByteBuffer& buffer, ClassConstantPoolV1& constantPool, EClassLoadStatus* loadStatus) {
	// Get attribute name
	auto attributeNameEntry = constantPool.getEntry(buffer.getUI2());
	if (!attributeNameEntry || attributeNameEntry->getTag() != ClassConstantUTF8EntryV1Tag) {
		// Attribute name is either invalid or is not pointing to a UTF8 tag
		if (loadStatus) *loadStatus = EClassLoadStatus::InvalidAttributeName;
		return {};
	}
	auto attributeName = reinterpret_cast<ClassConstantUTF8EntryV1*>(attributeNameEntry);
	auto& name         = attributeName->string;

	// Read attribute info
	std::uint32_t attributeLength = buffer.getUI4();

	if (name == "code") {
		std::vector<std::uint8_t> code;
		buffer.getUI1s(code, attributeLength);
		return new ClassAttributeMethodCodeV1(std::move(code));
	} else if (name == "methodref") {
		std::uint16_t classNameIndex        = buffer.getUI2();
		std::uint16_t methodDescriptorIndex = buffer.getUI2();
		std::uint32_t byteOffset            = buffer.getUI4();
		return new ClassAttributeMethodRefV1(classNameIndex, methodDescriptorIndex, byteOffset);
	} else {
		std::vector<std::uint8_t> info;
		buffer.getUI1s(info, attributeLength);
		return new ClassAttributeUnknownV1(std::string(name), std::move(info));
	}
}

Class* loadClassV1(ClassRegistry* registry, ByteBuffer& buffer, EClassLoadStatus* loadStatus) {
	ClassConstantPoolV1 constantPool;
	// Allocate a constant pool of size 'constantPoolSize - 1'
	std::uint16_t constantPoolSize = buffer.getUI2();
	constantPool.reserve(constantPoolSize - 1);
	for (std::uint32_t i = 0; i < static_cast<std::uint32_t>(constantPoolSize) - 1; i++) {
		// Try to read a constant pool entry and add it to the constant pool
		auto entry = readConstantPoolEntryV1(buffer, loadStatus);
		if (!entry) return nullptr;
		constantPool.addEntry(entry);
	}

	// Validate the constant pool
	if (!constantPool.validate()) {
		// The constant pool is invalid
		if (loadStatus) *loadStatus = EClassLoadStatus::InvalidConstantPool;
		return nullptr;
	}

	// Read class access flags and an index into the constant pool pointing to a Class tag
	EAccessFlags accessFlags = buffer.getUI2();
	auto thisClassEntry      = constantPool.getEntry(buffer.getUI2());
	if (!thisClassEntry || thisClassEntry->getTag() != ClassConstantClassEntryV1Tag) {
		// The thisClass field is either invalid or is not pointing to a Class tag
		if (loadStatus) *loadStatus = EClassLoadStatus::InvalidThisClassEntry;
		return nullptr;
	}

	// Read super classes
	std::uint16_t superCount = buffer.getUI2();
	std::vector<std::uint16_t> supers;
	buffer.getUI2s(supers, superCount);
	// Validate the super classes
	for (auto super : supers) {
		auto superEntry = constantPool.getEntry(super);
		if (!superEntry || superEntry->getTag() != ClassConstantClassEntryV1Tag) {
			// The super class is either invalid or is not pointing to a Class tag
			if (loadStatus) *loadStatus = EClassLoadStatus::InvalidSuperClassEntry;
			return nullptr;
		}
	}

	// Read class fields
	std::uint16_t fieldCount = buffer.getUI2();
	std::vector<ClassFieldEntryV1> fields(fieldCount);
	for (std::size_t i = 0; i < fields.size(); i++) {
		auto& field = fields[i];
		// Read field access flags
		field.accessFlags = buffer.getUI2();

		// Read field name
		auto fieldNameEntry = constantPool.getEntry(buffer.getUI2());
		if (!fieldNameEntry || fieldNameEntry->getTag() != ClassConstantUTF8EntryV1Tag) {
			// Field name is either invalid or is not pointing to a UTF8 tag
			if (loadStatus) *loadStatus = EClassLoadStatus::InvalidFieldName;
			return nullptr;
		}
		field.name = reinterpret_cast<ClassConstantUTF8EntryV1*>(fieldNameEntry)->string;

		// Read field descriptor
		auto fieldDescriptorEntry = constantPool.getEntry(buffer.getUI2());
		if (!fieldDescriptorEntry || fieldDescriptorEntry->getTag() != ClassConstantUTF8EntryV1Tag) {
			// Field descriptor is either invalid or is not pointing to a UTF8 tag
			if (loadStatus) *loadStatus = EClassLoadStatus::InvalidFieldDescriptor;
			return nullptr;
		}
		field.descriptor = reinterpret_cast<ClassConstantUTF8EntryV1*>(fieldDescriptorEntry)->string;

		// Read field attributes
		std::uint16_t attributeCount = buffer.getUI2();
		field.attributes.resize(attributeCount);
		for (std::size_t j = 0; j < field.attributes.size(); j++) {
			// Try to read an attribute
			EClassLoadStatus loadStatus2 = EClassLoadStatus::Success;
			field.attributes[j]          = readAttributeEntryV1(buffer, constantPool, &loadStatus2);
			if (loadStatus2 != EClassLoadStatus::Success) {
				// Attribute was invalid
				if (loadStatus) *loadStatus = loadStatus2;
				return nullptr;
			}
		}
	}

	// Read class methods
	std::uint16_t methodCount = buffer.getUI2();
	std::vector<ClassMethodEntryV1> methods(methodCount);
	for (std::size_t i = 0; i < methods.size(); i++) {
		auto& method = methods[i];
		// Read method access flags
		method.accessFlags = buffer.getUI2();

		// Read method name
		auto methodNameEntry = constantPool.getEntry(buffer.getUI2());
		if (!methodNameEntry || methodNameEntry->getTag() != ClassConstantUTF8EntryV1Tag) {
			// Method name is either invalid or is not pointing to a UTF8 tag
			if (loadStatus) *loadStatus = EClassLoadStatus::InvalidMethodName;
			return nullptr;
		}
		method.name = reinterpret_cast<ClassConstantUTF8EntryV1*>(methodNameEntry)->string;

		// Read method descriptor
		auto methodDescriptorEntry = constantPool.getEntry(buffer.getUI2());
		if (!methodDescriptorEntry || methodDescriptorEntry->getTag() != ClassConstantUTF8EntryV1Tag) {
			// Method descriptor is either invalid or is not pointing to a UTF8 tag
			if (loadStatus) *loadStatus = EClassLoadStatus::InvalidMethodDescriptor;
			return nullptr;
		}
		method.descriptor = reinterpret_cast<ClassConstantUTF8EntryV1*>(methodDescriptorEntry)->string;

		// Read method attributes
		std::uint16_t attributeCount = buffer.getUI2();
		method.attributes.resize(attributeCount);
		for (std::size_t j = 0; j < method.attributes.size(); j++) {
			// Try to read an attribute
			EClassLoadStatus loadStatus2 = EClassLoadStatus::Success;
			method.attributes[j]         = readAttributeEntryV1(buffer, constantPool, &loadStatus2);
			if (loadStatus2 != EClassLoadStatus::Success) {
				// Attribute was invalid
				if (loadStatus) *loadStatus = loadStatus2;
				return nullptr;
			}
		}
	}

	// Read class attributes
	std::uint16_t attributeCount = buffer.getUI2();
	std::vector<ClassAttributeV1*> attributes(attributeCount);
	for (std::size_t i = 0; i < attributes.size(); i++) {
		// Try to read an attribute
		EClassLoadStatus loadStatus2 = EClassLoadStatus::Success;
		attributes[i]                = readAttributeEntryV1(buffer, constantPool, &loadStatus2);
		if (loadStatus2 != EClassLoadStatus::Success) {
			// Attribute was invalid
			if (loadStatus) *loadStatus = loadStatus2;
			return nullptr;
		}
	}

	// Construct a new class from the read data
	Class* clazz       = new Class();
	clazz->accessFlags = accessFlags;

	// Get class name string
	auto thisClass     = reinterpret_cast<ClassConstantClassEntryV1*>(thisClassEntry);
	auto thisClassName = reinterpret_cast<ClassConstantUTF8EntryV1*>(constantPool.getEntry(thisClass->nameIndex));
	clazz->name        = thisClassName->string;

	// Try to load super classes
	clazz->supers.resize(supers.size());
	for (std::size_t i = 0; i < supers.size(); i++) {
		auto super      = reinterpret_cast<ClassConstantClassEntryV1*>(constantPool.getEntry(supers[i]));
		auto superName  = reinterpret_cast<ClassConstantUTF8EntryV1*>(constantPool.getEntry(super->nameIndex));
		auto superClass = registry->loadClass(superName->string, loadStatus);
		if (!superClass) return nullptr;

		clazz->supers[i] = superClass;
	}

	clazz->fields.resize(fields.size());
	for (std::size_t i = 0; i < fields.size(); i++) {
		auto& field = clazz->fields[i];
		auto& entry = fields[i];
		// Get field information
		field.accessFlags = entry.accessFlags;
		field.name        = entry.name;
		field.descriptor  = entry.descriptor;
		// TODO: Apply field attributes
	}

	clazz->methods.resize(methods.size());
	for (std::size_t i = 0; i < methods.size(); i++) {
		auto& method = clazz->methods[i];
		auto& entry  = methods[i];
		// Get method information
		method.accessFlags = entry.accessFlags;
		method.name        = entry.name;
		method.descriptor  = entry.descriptor;
		std::vector<ClassMethodRefV1> methodRefs;
		std::vector<std::uint8_t> code;
		for (auto& attribute : entry.attributes) {
			if (attribute->name == "code") {
				code = reinterpret_cast<ClassAttributeMethodCodeV1*>(attribute)->code;
			} else if (attribute->name == "methodref") {
				auto ref = reinterpret_cast<ClassAttributeMethodRefV1*>(attribute);
				ClassMethodRefV1 methodRef;
				methodRef.byteOffset = ref->byteOffset;

				auto methodRefClassNameEntry = constantPool.getEntry(ref->classNameIndex);
				if (!methodRefClassNameEntry || methodRefClassNameEntry->getTag() != ClassConstantUTF8EntryV1Tag) {
					// Method-ref class is either invalid or is not pointing to a UTF8 tag
					if (loadStatus) *loadStatus = EClassLoadStatus::InvalidMethodRefClassName;
					return nullptr;
				}
				auto methodRefClassName = reinterpret_cast<ClassConstantUTF8EntryV1*>(methodRefClassNameEntry);
				methodRef.className     = methodRefClassName->string;

				auto methodRefMethodDescriptorEntry = constantPool.getEntry(ref->methodDescriptorIndex);
				if (!methodRefMethodDescriptorEntry || methodRefMethodDescriptorEntry->getTag() != ClassConstantUTF8EntryV1Tag) {
					// Method-ref method is either invalid or is not pointing to a UTF8 tag
					if (loadStatus) *loadStatus = EClassLoadStatus::InvalidMethodRefMethodDescriptor;
					return nullptr;
				}
				auto methodRefMethodDescriptor = reinterpret_cast<ClassConstantUTF8EntryV1*>(methodRefMethodDescriptorEntry);
				methodRef.methodDescriptor     = methodRefMethodDescriptor->string;

				methodRefs.push_back(methodRef);
			}
		}

		if (!code.empty()) {
			// Constants
			std::uintptr_t classRegistryAddr                 = reinterpret_cast<std::uintptr_t>(registry);
			std::uintptr_t getMethodFromDescriptorErrorcAddr = LavaUBCast<decltype(&ClassRegistry::getMethodFromDescriptorErrorc), std::uintptr_t>(&ClassRegistry::getMethodFromDescriptorErrorc).right;
			std::size_t pCodeOffset                          = offsetof(Method, pCode);
			bool requireLargeCall                            = pCodeOffset > 255;
			std::size_t codeLength                           = code.size();
			std::size_t getCallLength                        = requireLargeCall ? 71 : 68;
			std::size_t directCallLength                     = 6;
			std::size_t callLength                           = 0;
			std::size_t dataLength                           = 0;
			std::unordered_map<std::string, std::size_t> strings;
			std::unordered_map<std::uintptr_t, std::size_t> ptrs;
			std::set<std::string> loadedClasses;

			// Sort method refs based on their byte offset
			std::sort(methodRefs.begin(), methodRefs.end(), [](ClassMethodRefV1& lhs, ClassMethodRefV1& rhs) -> bool {
				return lhs.byteOffset < rhs.byteOffset;
			});

			// Check how much space each method ref requires to call
			for (auto& methodRef : methodRefs) {
				Class* methodRefClass = registry->getClass(methodRef.className);
				if (!methodRefClass && !registry->getPreloadRequiredClasses()) {
					strings.insert({ methodRef.className, 0 });
					strings.insert({ methodRef.methodDescriptor, 0 });
					ptrs.insert({ classRegistryAddr, 0 });
					ptrs.insert({ getMethodFromDescriptorErrorcAddr, 0 });
					callLength += getCallLength;
					continue;
				} else {
					methodRefClass = &registry->loadClassError(methodRef.className);
				}
				loadedClasses.insert(methodRef.className);
				Method* methodRefMethod = methodRefClass->getMethodFromDescriptor(methodRef.methodDescriptor);
				if (!methodRefMethod)
					throw std::runtime_error("Method wants to invoke a nonexistant method '" + methodRef.methodDescriptor + "' in class '" + methodRef.className + "'");
				callLength += directCallLength;
				ptrs.insert({ LavaUBCast<std::uint8_t*, std::uintptr_t>(methodRefMethod->pCode).right, 0 });
			}

			// Check how much space each string requires
			for (auto& string : strings) dataLength += string.first.size() + 1;
			dataLength += 8 * ptrs.size();

			std::size_t dataBegin = codeLength + callLength;
			// Resize the code to the new length
			code.resize(dataBegin + dataLength, 0);
			auto pCode = code.data();

			// Write the points into the code
			std::size_t dataOffset = 0;
			for (auto& ptr : ptrs) {
				std::memcpy(pCode + dataBegin + dataOffset, &ptr.first, 8);
				ptr.second = dataOffset;
				dataOffset += 8;
			}

			// Write the strings into the code
			for (auto& string : strings) {
				std::memcpy(pCode + dataBegin + dataOffset, string.first.data(), string.first.size());
				string.second = dataOffset;
				dataOffset += string.first.size() + 1;
			}

			// Write the method invocations into the code
			std::size_t offset = 0;
			for (auto& methodRef : methodRefs) {
				std::size_t callBegin = offset + methodRef.byteOffset;
				// If method refers to an already loaded class optimize the call to the direct call, else use the get call
				if (loadedClasses.find(methodRef.className) != loadedClasses.end()) {
					// Move bytes after call
					std::memmove(pCode + callBegin + directCallLength, pCode + callBegin + 1, codeLength - methodRef.byteOffset - 1);

					// Create the call in assembly
					Class* methodRefClass    = registry->getClass(methodRef.className);
					Method* methodRefMethod  = methodRefClass->getMethodFromDescriptor(methodRef.methodDescriptor);
					std::uintptr_t methodPtr = LavaUBCast<std::uint8_t*, std::uintptr_t>(methodRefMethod->pCode).right;

					std::int32_t addrOffset = static_cast<std::int32_t>((dataBegin + ptrs.find(methodPtr)->second) - (callBegin + 6));
					ByteBuffer call;
					call.addUI1s({ 0xFF, 0x15 }); // CALL [REL ??]
					call.addI4(addrOffset);       // Offset to address of method to call

					// Copy the call into the code
					std::memcpy(pCode + callBegin, call.data(), directCallLength);
					offset += directCallLength;
				} else { // Use the worse in every way get call :|
					// Get string offsets
					std::int32_t classRegistryOffset                 = static_cast<std::int32_t>((dataBegin + ptrs.find(classRegistryAddr)->second) - (callBegin + 26));
					std::int32_t getMethodFromDescriptorErrorcOffset = static_cast<std::int32_t>((dataBegin + ptrs.find(getMethodFromDescriptorErrorcAddr)->second) - (callBegin + 46));
					std::int32_t classNameOffset                     = static_cast<std::int32_t>((dataBegin + strings.find(methodRef.className)->second) - (callBegin + 33));
					std::int32_t methodDescriptorOffset              = static_cast<std::int32_t>((dataBegin + strings.find(methodRef.methodDescriptor)->second) - (callBegin + 40));

					// Move bytes after call
					std::memmove(pCode + callBegin + getCallLength, pCode + callBegin + 1, codeLength - methodRef.byteOffset - 1);

					// Create the call in assembly
					ByteBuffer call;
					call.addUI1s({ 0x48, 0x83, 0xEC, 0x38 });        // SUB RSP, 38h
					call.addUI1s({ 0x48, 0x89, 0x4C, 0x24, 0x20 });  // MOV [RSP + 20h], rcx
					call.addUI1s({ 0x48, 0x89, 0x54, 0x24, 0x28 });  // MOV [RSP + 28h], rdx
					call.addUI1s({ 0x4C, 0x89, 0x44, 0x24, 0x30 });  // MOV [RSP + 30h], r8
					call.addUI1s({ 0x48, 0x8B, 0x0D });              // MOV RCX, [REL ??]
					call.addI4(classRegistryOffset);                 // classRegistry offset
					call.addUI1s({ 0x48, 0x8D, 0x15 });              // LEA RDX, [REL ??]
					call.addI4(classNameOffset);                     // className offset
					call.addUI1s({ 0x4C, 0x8D, 0x05 });              // LEA R8, [REL ??]
					call.addI4(methodDescriptorOffset);              // methodDescriptor offset
					call.addUI1s({ 0xFF, 0x15 });                    // CALL [REL ??]
					call.addI4(getMethodFromDescriptorErrorcOffset); // getMethodFromDescriptorErrorc offset
					call.addUI1s({ 0x48, 0x8B, 0x4C, 0x24, 0x20 });  // MOV rcx, [RSP + 20h]
					call.addUI1s({ 0x48, 0x8B, 0x54, 0x24, 0x28 });  // MOV rdx, [RSP + 28h]
					call.addUI1s({ 0x4C, 0x8B, 0x44, 0x24, 0x30 });  // MOV r8,  [RSP + 30h]
					call.addUI1s({ 0x48, 0x83, 0xC4, 0x38 });        // ADD RSP, 38h
					if (requireLargeCall) {
						call.addUI1s({ 0xFF, 0x90 });                         // CALL [RAX + ??]
						call.addUI4(static_cast<std::uint32_t>(pCodeOffset)); // pCode offset
					} else {
						call.addUI1s({ 0xFF, 0x50 });                        // CALL [RAX + ??]
						call.addUI1(static_cast<std::uint8_t>(pCodeOffset)); // pCode offset
					}

					// Copy the call into the code
					std::memcpy(pCode + callBegin, call.data(), getCallLength);
					offset += getCallLength;
				}
			}

			method.allocateCode(code);
			method.makeCodeExecutable();
		}
	}

	// Return class
	if (loadStatus) *loadStatus = EClassLoadStatus::Success;
	return clazz;
}<file_sep>#include "Class.h"

#include <cstring>

#include <sstream>

#if LAVA_SYSTEM_windows
	#include <Windows.h>
#elif LAVA_SYSTEM_linux
	#include <sys/mman.h>
#else
	#error Requires executable memory allocation, which isnt supported by your system
#endif

static void* allocateReadWriteMemory(std::size_t bytes);
static void makeExecutableMemory(void* p, std::size_t bytes);
static void makeNonExecutableMemory(void* p, std::size_t bytes);
static void deallocateMemory(void* p, std::size_t bytes);

//-------------
// Class Flags
//-------------

std::ostream& operator<<(std::ostream& stream, EAccessFlags flags) {
	bool printed = false;
	if (flags & EAccessFlag::Public) {
		stream << "Public";
		printed = true;
	}
	if (flags & EAccessFlag::Private) {
		if (printed) stream << " | ";
		stream << "Private";
		printed = true;
	}
	if (flags & EAccessFlag::Protected) {
		if (printed) stream << " | ";
		stream << "Protected";
		printed = true;
	}
	if (flags & EAccessFlag::Static) {
		if (printed) stream << " | ";
		stream << "Static";
		printed = true;
	}
	if (flags & EAccessFlag::Final) {
		if (printed) stream << " | ";
		stream << "Final";
		printed = true;
	}
	if (flags & EAccessFlag::Super) {
		if (printed) stream << " | ";
		stream << "Super(Synchronized)";
		printed = true;
	}
	if (flags & EAccessFlag::Volatile) {
		if (printed) stream << " | ";
		stream << "Volatile(Bridge)";
		printed = true;
	}
	if (flags & EAccessFlag::Transient) {
		if (printed) stream << " | ";
		stream << "Transient(Varargs)";
		printed = true;
	}
	if (flags & EAccessFlag::Native) {
		if (printed) stream << " | ";
		stream << "Native";
		printed = true;
	}
	if (flags & EAccessFlag::Interface) {
		if (printed) stream << " | ";
		stream << "Interface";
		printed = true;
	}
	if (flags & EAccessFlag::Abstract) {
		if (printed) stream << " | ";
		stream << "Abstract";
		printed = true;
	}
	if (flags & EAccessFlag::Strict) {
		if (printed) stream << " | ";
		stream << "Strict";
		printed = true;
	}
	if (flags & EAccessFlag::Synthetic) {
		if (printed) stream << " | ";
		stream << "Synthetic";
		printed = true;
	}
	if (flags & EAccessFlag::Annotation) {
		if (printed) stream << " | ";
		stream << "Annotation";
		printed = true;
	}
	if (flags & EAccessFlag::Enum) {
		if (printed) stream << " | ";
		stream << "Enum";
		printed = true;
	}
	if (flags & EAccessFlag::Module) {
		if (printed) stream << " | ";
		stream << "Module";
		printed = true;
	}
	return stream;
}

//------------------
// Class structures
//------------------

Method::~Method() {
	if (this->allocated)
		deallocateMemory(this->pCode, this->codeLength);
}

void Method::allocateCode(std::vector<std::uint8_t>& code) {
	if (this->pCode) return;
	this->allocated  = true;
	this->codeLength = code.size();
	this->pCode      = reinterpret_cast<std::uint8_t*>(allocateReadWriteMemory(this->codeLength));
	std::memcpy(this->pCode, code.data(), this->codeLength);
}

void Method::makeCodeReadWrite() {
	makeNonExecutableMemory(this->pCode, this->codeLength);
}

void Method::makeCodeExecutable() {
	makeExecutableMemory(this->pCode, this->codeLength);
}

Method* Class::getMethod(std::string_view name) {
	for (auto& method : this->methods)
		if (method.name == name)
			return &method;
	return nullptr;
}

Method* Class::getMethodc(const char* name) {
	return getMethod(name);
}

Method& Class::getMethodError(std::string_view name) {
	for (auto& method : this->methods)
		if (method.name == name)
			return method;
	std::ostringstream stream;
	stream << "Method name '" << name << "' not found";
	throw std::runtime_error(stream.str());
}

Method& Class::getMethodErrorc(const char* name) {
	return getMethodError(name);
}

Method* Class::getMethodFromDescriptor(std::string_view descriptor) {
	for (auto& method : this->methods)
		if (method.descriptor == descriptor)
			return &method;
	return nullptr;
}

Method* Class::getMethodFromDescriptorc(const char* descriptor) {
	return getMethodFromDescriptor(descriptor);
}

Method& Class::getMethodFromDescriptorError(std::string_view descriptor) {
	for (auto& method : this->methods)
		if (method.descriptor == descriptor)
			return method;
	std::ostringstream stream;
	stream << "Method descriptor '" << descriptor << "' not found";
	throw std::runtime_error(stream.str());
}

Method& Class::getMethodFromDescriptorErrorc(const char* descriptor) {
	return getMethodFromDescriptorError(descriptor);
}

//----------------------------
// Windows execute allocation
//----------------------------

#if LAVA_SYSTEM_windows
void* allocateReadWriteMemory(std::size_t bytes) {
	return VirtualAlloc(nullptr, bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
}

void makeExecutableMemory(void* p, std::size_t bytes) {
	DWORD old;
	VirtualProtect(p, bytes, PAGE_EXECUTE_READ, &old);
}

void makeNonExecutableMemory(void* p, std::size_t bytes) {
	DWORD old;
	VirtualProtect(p, bytes, PAGE_READWRITE, &old);
}

void deallocateMemory(void* p, std::size_t bytes) {
	VirtualFree(p, 0, MEM_RELEASE);
}
#endif

//----------------------------
// Linux execute allocation
//----------------------------

#if LAVA_SYSTEM_linux
void* allocateReadWriteMemory(std::size_t bytes) {
	return mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}

void makeExecutableMemory(void* p, std::size_t bytes) {
	mprotect(p, bytes, PROT_EXEC | PROT_READ);
}

void makeNonExecutableMemory(void* p, std::size_t bytes) {
	mprotect(p, bytes, PROT_READ | PROT_WRITE);
}

void deallocateMemory(void* p, std::size_t bytes) {
	munmap(p, bytes);
}
#endif<file_sep>#pragma once

#include "Class.h"

#include <cstdint>

#include <filesystem>
#include <ostream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

enum class EClassLoadStatus : std::uint32_t {
	Success = 0,
	FileNotFound,
	InvalidMagicNumber,
	InvalidVersion,
	InvalidConstantPool,
	InvalidConstantPoolEntry,
	InvalidThisClassEntry,
	InvalidSuperClassEntry,
	InvalidFieldName,
	InvalidFieldDescriptor,
	InvalidAttributeName,
	InvalidMethodName,
	InvalidMethodDescriptor,
	InvalidMethodRefClassName,
	InvalidMethodRefMethodDescriptor,
};

std::ostream& operator<<(std::ostream& stream, EClassLoadStatus status);

class ClassRegistry {
public:
	Class* newClass(const std::string& className);
	void addClassPath(const std::filesystem::path classPath);
	Class* getClass(const std::string& className);
	Class* loadClass(const std::string& className, EClassLoadStatus* loadStatus = nullptr);
	Class* loadClassc(const char* className, EClassLoadStatus* loadStatus = nullptr);
	Class& loadClassError(const std::string& className);
	Class& loadClassErrorc(const char* className);
	Method& getMethodErrorc(const char* className, const char* methodName);
	LAVA_MICROSOFT_CALL_ABI Method& getMethodFromDescriptorErrorc(const char* className, const char* methodDescriptor);

	auto getPreloadRequiredClasses() const { return this->preloadRequiredClasses; }
	void setPreloadRequiredClasses(bool preloadRequiredClasses) { this->preloadRequiredClasses = preloadRequiredClasses; }
	auto& getClassPaths() const { return this->classPaths; }
	std::vector<Class*> getLoadedClasses() const;

private:
	std::filesystem::path findClass(std::string_view className) const;

private:
	bool preloadRequiredClasses = false;
	std::vector<std::filesystem::path> classPaths;
	std::unordered_map<std::string, Class*> classes;
};

extern ClassRegistry* globalClassRegistry;

languages: Markdown C++ Lua

repo_name: MarcasRealAccount/LavaTest


text:
<repo_name>AndrewK011/RobotsVsDinosaursProj<file_sep>/RobotsVsDinos/Dinosaur.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RobotsVsDinos
{
    public class Dinosaur
    {
        //member variables
        public string type;
        public int health;
        public int energy;
        public int attackPower;
        public string[] dinoAttacks = new string[3] {"light attack (x1)", "heavy attack (x1.5)", "special attack (x2)"};

        //constructor
        public Dinosaur(string type, int attackPower)
        {
            this.type = type;
            health = 100;
            energy = 50;
            this.attackPower = attackPower;
        }

        public void Attack(Robot enemy)
        {
            bool validInput = false;
            int userInput = 0;
            double attackMult;

            while (!validInput)
            {
                Console.WriteLine("Choose your type of attack: ");
                for (int i = 0; i < dinoAttacks.Length; i++)
                {
                    Console.WriteLine(i + ") " + dinoAttacks[i]);
                }

                if(int.TryParse(Console.ReadLine(), out userInput))
                {
                    validInput = true;
                }
                else
                {
                    Console.WriteLine("Incorrect input.");
                }

            }

            switch(userInput)
            {
                case 0:
                    attackMult = 1.0;
                    break;
                case 1:
                    attackMult = 1.5;
                    break;
                case 2:
                    attackMult = 2.0;
                    break;

                default:
                    attackMult = 1.0;
                    break;

            }

            attackPower *= (int)attackMult;

            if(energy >= (attackPower / 2))
            {
                enemy.health -= attackPower;
                energy -= attackPower / 2;
                Console.WriteLine(enemy.name + " takes " + attackPower + " damage from " + type + "'s " + dinoAttacks[userInput]);

                if (enemy.health <= 0)
                {
                    Console.WriteLine(enemy.name + " has been defeated.");
                }
                else
                {
                    Console.WriteLine(enemy.name + " now has " + enemy.health + " health remaining.");
                }

            }

            else
            {
                Console.WriteLine("Your dino is overexerted.");

            }

        }
    }
}
<file_sep>/RobotsVsDinos/Robot.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RobotsVsDinos
{
    public class Robot
    {
        //member variables
        public string name;
        public int health;
        public int powerLevel;
        public Weapon robotWeapon;

        public Robot(string name)
        {
            this.name = name;
            health = 100;
            powerLevel = 50;
        }

        public void Attack(Dinosaur enemy)
        {
            if(powerLevel >= (robotWeapon.attackPower / 2))
            {
                enemy.health -= robotWeapon.attackPower;
                powerLevel -= robotWeapon.attackPower / 2;

                Console.WriteLine(enemy.type + " takes " + robotWeapon.attackPower + " damage from " + name + "'s " + robotWeapon.type + ".");

                if (enemy.health <= 0)
                {
                    Console.WriteLine(enemy.type + " has been defeated.");

                }
                else
                {
                    Console.WriteLine(enemy.type + " now has " + enemy.health + " health remaining.");
                }

            }

            else
            {
                Console.WriteLine("Power cells depleted, need to recharge.");
            }

        }

    }
}
<file_sep>/RobotsVsDinos/Herd.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RobotsVsDinos
{
    public class Herd
    {

        public List<Dinosaur> dinos = new List<Dinosaur>();
        public Dinosaur raptor = new Dinosaur("Raptor", 20);
        public Dinosaur trex = new Dinosaur("T-Rex", 25);
        public Dinosaur pterodactyl = new Dinosaur("Pterodactyl", 20);

        public Herd()
        {
            dinos.Add(raptor);
            dinos.Add(trex);
            dinos.Add(pterodactyl);
        }

        public void CheckDinoRound()
        {

            if (raptor.health <= 0)
            {
                dinos.Remove(raptor);
            }
            if(trex.health <= 0)
            {
                dinos.Remove(trex);
            }
            if(pterodactyl.health <= 0)
            {
                dinos.Remove(pterodactyl);
            }

            if(raptor.energy <= 40)
            {
                raptor.energy += 10;

            }

            if(trex.energy <= 40)
            {
                trex.energy += 10;

            }

            if (pterodactyl.energy <= 40)
            {
                pterodactyl.energy += 10;

            }
        }

    }
}
<file_sep>/RobotsVsDinos/Fleet.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RobotsVsDinos
{
    public class Fleet
    {
        public List<Robot> robots;
        public Robot Optimus = new Robot("Optimus");
        public Robot Hal = new Robot("Hal");
        public Robot Phil = new Robot("Phil");
        public List<Weapon> weapons = new List<Weapon>();
        public Weapon laser = new Weapon("laser", 30);
        public Weapon rocketLauncher = new Weapon("rocket launcher", 50);
        public Weapon sword = new Weapon("sword", 35);
        public Weapon flameThrower = new Weapon("flame thrower", 25);

        public Fleet()
        {
            robots = new List<Robot>();
            robots.Add(Optimus);
            robots.Add(Hal);
            robots.Add(Phil);

        }

        public void CheckRoboRound()
        {
            if (Optimus.health <= 0)
            {
                robots.Remove(Optimus);
            }
            if (Hal.health <= 0)
            {
                robots.Remove(Hal);
            }
            if (Phil.health <= 0)
            {
                robots.Remove(Phil);
            }

            if (Optimus.powerLevel <= 40)
            {
                Optimus.powerLevel += 10;

            }

            if (Hal.powerLevel <= 40)
            {
                Hal.powerLevel += 10;

            }

            if (Phil.powerLevel <= 40)
            {
                Phil.powerLevel += 10;

            }
        }

        public void CreateWeaponList()
        {
            weapons.Add(laser);
            weapons.Add(rocketLauncher);
            weapons.Add(sword);
            weapons.Add(flameThrower);
        }

        public void ChooseWeapon(Robot robotLoadout)
        {
            bool validInput = false;
            int userChoice = 0;

            while (!validInput)
            {

                Console.WriteLine("Choose a weapon for " + robotLoadout.name + ": ");
                for (int i = 0; i < weapons.Count; i++)
                {
                Console.WriteLine(i + ") " + weapons[i].type + " (" + weapons[i].attackPower + ")");
                }

                if ((int.TryParse(Console.ReadLine(), out userChoice)))
                {
                    validInput = true;
                }
                else
                {
                    Console.WriteLine("Incorrect input, not an integer.");
                }

                if(userChoice < 0 || userChoice >= weapons.Count)
                {
                    Console.WriteLine("Incorrect input, not a valid choice.");
                    validInput = false;
                }

            }

            robotLoadout.robotWeapon = weapons[userChoice];
            weapons.RemoveAt(userChoice);
        }

    }
}
<file_sep>/RobotsVsDinos/Battlefield.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RobotsVsDinos
{
    public class Battlefield
    {
        public Herd dinoHerd;
        public Fleet robotFleet;
        public int enemyIndex;
        public int attackerIndex;
        public bool gameOver = false;
        public bool dinoWin = false;

        public bool playerTurn;
        public Random randomBool = new Random();

        public Battlefield()
        {
            dinoHerd = new Herd();
            robotFleet = new Fleet();

        }

        public void StartBattle()
        {
            robotFleet.CreateWeaponList();
            robotFleet.ChooseWeapon(robotFleet.Optimus);
            robotFleet.ChooseWeapon(robotFleet.Hal);
            robotFleet.ChooseWeapon(robotFleet.Phil);

            if(randomBool.Next(2) == 1)
            {
                playerTurn = true;
                Console.WriteLine();
                Console.WriteLine("Dinos go first!");
                Console.WriteLine();
            }
            else
            {
                Console.WriteLine();
                Console.WriteLine("Robots go first!");
                Console.WriteLine();
            }

            while (gameOver == false)
            {
                if (playerTurn)
                {
                    DinoAttack();
                    CheckRoundResult();
                    Console.WriteLine();
                    playerTurn = !playerTurn;
                }
                else
                {
                    RoboAttack();
                    Console.WriteLine();
                    CheckRoundResult();
                    playerTurn = !playerTurn;
                }
            }

            Console.WriteLine("Game Over");
            if(dinoWin)
            {
                Console.WriteLine("Dinosaurs win!");
            }
            else
            {
                Console.WriteLine("Robots win!");
            }

        }

        public void DinoAttack()
        {
            bool validInput = false;

            while (!validInput)
            {

            Console.WriteLine("Pick a dinosaur to attack with:");
            for(int i = 0; i < dinoHerd.dinos.Count; i++)
            {
                Console.Write(i + ") ");
                Console.WriteLine(dinoHerd.dinos[i].type + " (" + dinoHerd.dinos[i].energy + " energy)");
            }

                if (int.TryParse(Console.ReadLine(), out attackerIndex))
                {
                    validInput = true;
                }
                else
                {
                    Console.WriteLine("Incorrect input, must be an integer.");
                }

                if (attackerIndex < 0 || attackerIndex >= dinoHerd.dinos.Count)
                {
                    Console.WriteLine("Incorrect input, not a valid choice.");
                    validInput = false;
                }

            }

            validInput = false;

            while (!validInput)
            {

                Console.WriteLine("Choose your target: ");

                for (int i = 0; i < robotFleet.robots.Count; i++)
                {
                    Console.Write(i + ") ");
                    Console.WriteLine(robotFleet.robots[i].name);
                }

                if (int.TryParse(Console.ReadLine(), out enemyIndex))
                {
                    validInput = true;
                }
                else
                {
                    Console.WriteLine("Incorrect input, must be an integer.");
                }

                if (enemyIndex < 0 || enemyIndex >= robotFleet.robots.Count)
                {
                    Console.WriteLine("Incorrect input, not a valid choice.");
                    validInput = false;
                }

            }

            if (HitChance(dinoHerd.dinos[attackerIndex].attackPower))
            {

                dinoHerd.dinos[attackerIndex].Attack(robotFleet.robots[enemyIndex]);

            }

            else
            {
                Console.WriteLine("The attack missed!");
            }

        }

        public void RoboAttack()
        {

            bool validInput = false;

            while (!validInput)
            {
                Console.WriteLine("Pick a robot to attack with: ");
                for (int i = 0; i < robotFleet.robots.Count; i++)
                {
                    Console.Write(i + ") ");
                    Console.WriteLine(robotFleet.robots[i].name + " [" + robotFleet.robots[i].robotWeapon.type + "]" +
                        " (" + robotFleet.robots[i].robotWeapon.attackPower + ") (" + robotFleet.robots[i].powerLevel + " energy)");

                }

                if (int.TryParse(Console.ReadLine(), out attackerIndex))
                {
                    validInput = true;
                }
                else
                {
                    Console.WriteLine("Incorrect input, must be an integer.");
                }

                if (attackerIndex < 0 || attackerIndex >= robotFleet.robots.Count)
                {
                    Console.WriteLine("Incorrect input, not a valid choice.");
                    validInput = false;
                }
            }

            validInput = false;

            while (!validInput)
            {

                Console.WriteLine("Choose your target: ");
                for (int i = 0; i < dinoHerd.dinos.Count; i++)
                {
                    Console.Write(i + ") ");
                    Console.WriteLine(dinoHerd.dinos[i].type);
                }

                if (int.TryParse(Console.ReadLine(), out enemyIndex))
                {
                    validInput = true;
                }
                else
                {
                    Console.WriteLine("Incorrect input, must be an integer.");
                }

                if (enemyIndex < 0 || enemyIndex >= dinoHerd.dinos.Count)
                {
                    Console.WriteLine("Incorrect input, not a valid choice.");
                    validInput = false;
                }
            }

            if (HitChance(robotFleet.robots[attackerIndex].robotWeapon.attackPower))
            {
                robotFleet.robots[attackerIndex].Attack(dinoHerd.dinos[enemyIndex]);

            }

            else
            {
                Console.WriteLine("The attack missed!");
            }

        }

        public bool CheckRoundResult()
        {
            dinoHerd.CheckDinoRound();
            robotFleet.CheckRoboRound();
            if(dinoHerd.dinos.Count == 0)
            {
                gameOver = true;

            }
            else if(robotFleet.robots.Count == 0)
            {
                gameOver = true;
                dinoWin = true;
            }
            return gameOver;
        }

        public bool HitChance(int attackPower)
        {
            if (randomBool.Next(200) > attackPower )
            {
                return true;
            }
            else
            {
                return false;
            }
        }

    }
}

branch_name: refs/heads/master

repo_language: C#