diff options
Diffstat (limited to 'meshmc/libraries/classparser')
| -rw-r--r-- | meshmc/libraries/classparser/CMakeLists.txt | 41 | ||||
| -rw-r--r-- | meshmc/libraries/classparser/include/classparser.h | 49 | ||||
| -rw-r--r-- | meshmc/libraries/classparser/include/classparser_config.h | 45 | ||||
| -rw-r--r-- | meshmc/libraries/classparser/src/annotations.cpp | 105 | ||||
| -rw-r--r-- | meshmc/libraries/classparser/src/annotations.h | 296 | ||||
| -rw-r--r-- | meshmc/libraries/classparser/src/classfile.h | 169 | ||||
| -rw-r--r-- | meshmc/libraries/classparser/src/classparser.cpp | 116 | ||||
| -rw-r--r-- | meshmc/libraries/classparser/src/constants.h | 241 | ||||
| -rw-r--r-- | meshmc/libraries/classparser/src/errors.h | 29 | ||||
| -rw-r--r-- | meshmc/libraries/classparser/src/javaendian.h | 84 | ||||
| -rw-r--r-- | meshmc/libraries/classparser/src/membuffer.h | 84 |
11 files changed, 1259 insertions, 0 deletions
diff --git a/meshmc/libraries/classparser/CMakeLists.txt b/meshmc/libraries/classparser/CMakeLists.txt new file mode 100644 index 0000000000..f4776902cf --- /dev/null +++ b/meshmc/libraries/classparser/CMakeLists.txt @@ -0,0 +1,41 @@ +project(classparser) + +set(CMAKE_AUTOMOC ON) + +######## Check endianness ######## +include(TestBigEndian) +test_big_endian(BIGENDIAN) +if(${BIGENDIAN}) + add_definitions(-DMULTIMC_BIG_ENDIAN) +endif(${BIGENDIAN}) + +# Find Qt +find_package(Qt6Core REQUIRED) + +# Include Qt headers. +include_directories(${Qt6Base_INCLUDE_DIRS}) + +set(CLASSPARSER_HEADERS +# Public headers +include/classparser_config.h +include/classparser.h + +# Private headers +src/annotations.h +src/classfile.h +src/constants.h +src/errors.h +src/javaendian.h +src/membuffer.h +) + +set(CLASSPARSER_SOURCES +src/classparser.cpp +src/annotations.cpp +) + +add_definitions(-DCLASSPARSER_LIBRARY) + +add_library(MeshMC_classparser STATIC ${CLASSPARSER_SOURCES} ${CLASSPARSER_HEADERS}) +target_include_directories(MeshMC_classparser PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(MeshMC_classparser PRIVATE LibArchive::LibArchive Qt6::Core) diff --git a/meshmc/libraries/classparser/include/classparser.h b/meshmc/libraries/classparser/include/classparser.h new file mode 100644 index 0000000000..c39e4e800e --- /dev/null +++ b/meshmc/libraries/classparser/include/classparser.h @@ -0,0 +1,49 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once +#include <QString> +#include "classparser_config.h" + +namespace classparser +{ + /** + * @brief Get the version from a minecraft.jar by parsing its class files. + * Expensive! + */ + QString GetMinecraftJarVersion(QString jar); +} // namespace classparser diff --git a/meshmc/libraries/classparser/include/classparser_config.h b/meshmc/libraries/classparser/include/classparser_config.h new file mode 100644 index 0000000000..644c392143 --- /dev/null +++ b/meshmc/libraries/classparser/include/classparser_config.h @@ -0,0 +1,45 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <QtCore/QtGlobal> + +#ifdef CLASSPARSER_LIBRARY +#define CLASSPARSER_EXPORT Q_DECL_EXPORT +#else +#define CLASSPARSER_EXPORT Q_DECL_IMPORT +#endif diff --git a/meshmc/libraries/classparser/src/annotations.cpp b/meshmc/libraries/classparser/src/annotations.cpp new file mode 100644 index 0000000000..93288a8f0b --- /dev/null +++ b/meshmc/libraries/classparser/src/annotations.cpp @@ -0,0 +1,105 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "classfile.h" +#include "annotations.h" +#include <sstream> + +namespace java +{ + std::string annotation::toString() + { + std::ostringstream ss; + ss << "Annotation type : " << type_index << " - " + << pool[type_index].str_data << std::endl; + ss << "Contains " << name_val_pairs.size() << " pairs:" << std::endl; + for (unsigned i = 0; i < name_val_pairs.size(); i++) { + std::pair<uint16_t, element_value*>& val = name_val_pairs[i]; + auto name_idx = val.first; + ss << pool[name_idx].str_data << "(" << name_idx << ")" + << " = " << val.second->toString() << std::endl; + } + return ss.str(); + } + + annotation* annotation::read(util::membuffer& input, constant_pool& pool) + { + uint16_t type_index = 0; + input.read_be(type_index); + annotation* ann = new annotation(type_index, pool); + + uint16_t num_pairs = 0; + input.read_be(num_pairs); + while (num_pairs) { + uint16_t name_idx = 0; + // read name index + input.read_be(name_idx); + auto elem = element_value::readElementValue(input, pool); + // read value + ann->add_pair(name_idx, elem); + num_pairs--; + } + return ann; + } + + element_value* element_value::readElementValue(util::membuffer& input, + java::constant_pool& pool) + { + element_value_type type = INVALID; + input.read(type); + uint16_t index = 0; + uint16_t index2 = 0; + std::vector<element_value*> vals; + switch (type) { + case PRIMITIVE_BYTE: + case PRIMITIVE_CHAR: + case PRIMITIVE_DOUBLE: + case PRIMITIVE_FLOAT: + case PRIMITIVE_INT: + case PRIMITIVE_LONG: + case PRIMITIVE_SHORT: + case PRIMITIVE_BOOLEAN: + case STRING: + input.read_be(index); + return new element_value_simple(type, index, pool); + case ENUM_CONSTANT: + input.read_be(index); + input.read_be(index2); + return new element_value_enum(type, index, index2, pool); + case CLASS: // Class + input.read_be(index); + return new element_value_class(type, index, pool); + case ANNOTATION: // Annotation + // FIXME: runtime visibility info needs to be passed from parent + return new element_value_annotation( + ANNOTATION, annotation::read(input, pool), pool); + case ARRAY: // Array + input.read_be(index); + for (int i = 0; i < index; i++) { + vals.push_back( + element_value::readElementValue(input, pool)); + } + return new element_value_array(ARRAY, vals, pool); + default: + throw new java::classfile_exception(); + } + } +} // namespace java
\ No newline at end of file diff --git a/meshmc/libraries/classparser/src/annotations.h b/meshmc/libraries/classparser/src/annotations.h new file mode 100644 index 0000000000..644d28c0d4 --- /dev/null +++ b/meshmc/libraries/classparser/src/annotations.h @@ -0,0 +1,296 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once +#include "classfile.h" +#include <map> +#include <vector> + +namespace java +{ + enum element_value_type : uint8_t { + INVALID = 0, + STRING = 's', + ENUM_CONSTANT = 'e', + CLASS = 'c', + ANNOTATION = '@', + ARRAY = '[', // one array dimension + PRIMITIVE_INT = 'I', // integer + PRIMITIVE_BYTE = 'B', // signed byte + PRIMITIVE_CHAR = 'C', // Unicode character code point in the Basic + // Multilingual Plane, encoded with UTF-16 + PRIMITIVE_DOUBLE = 'D', // double-precision floating-point value + PRIMITIVE_FLOAT = 'F', // single-precision floating-point value + PRIMITIVE_LONG = 'J', // long integer + PRIMITIVE_SHORT = 'S', // signed short + PRIMITIVE_BOOLEAN = 'Z' // true or false + }; + /** + * The element_value structure is a discriminated union representing the + * value of an element-value pair. It is used to represent element values in + * all attributes that describe annotations + * - RuntimeVisibleAnnotations + * - RuntimeInvisibleAnnotations + * - RuntimeVisibleParameterAnnotations + * - RuntimeInvisibleParameterAnnotations). + * + * The element_value structure has the following format: + */ + class element_value + { + protected: + element_value_type type; + constant_pool& pool; + + public: + element_value(element_value_type type, constant_pool& pool) + : type(type), pool(pool) {}; + virtual ~element_value() {} + + element_value_type getElementValueType() + { + return type; + } + + virtual std::string toString() = 0; + + static element_value* readElementValue(util::membuffer& input, + constant_pool& pool); + }; + + /** + * Each value of the annotations table represents a single runtime-visible + * annotation on a program element. The annotation structure has the + * following format: + */ + class annotation + { + public: + typedef std::vector<std::pair<uint16_t, element_value*>> value_list; + + protected: + /** + * The value of the type_index item must be a valid index into the + * constant_pool table. The constant_pool entry at that index must be a + * CONSTANT_Utf8_info (§4.4.7) structure representing a field descriptor + * representing the annotation type corresponding to the annotation + * represented by this annotation structure. + */ + uint16_t type_index; + /** + * map between element_name_index and value. + * + * The value of the element_name_index item must be a valid index into + * the constant_pool table. The constant_pool entry at that index must + * be a CONSTANT_Utf8_info (§4.4.7) structure representing a valid field + * descriptor (§4.3.2) that denotes the name of the annotation type + * element represented by this element_value_pairs entry. + */ + value_list name_val_pairs; + /** + * Reference to the parent constant pool + */ + constant_pool& pool; + + public: + annotation(uint16_t type_index, constant_pool& pool) + : type_index(type_index), pool(pool) {}; + ~annotation() + { + for (unsigned i = 0; i < name_val_pairs.size(); i++) { + delete name_val_pairs[i].second; + } + } + void add_pair(uint16_t key, element_value* value) + { + name_val_pairs.push_back(std::make_pair(key, value)); + }; + value_list::const_iterator begin() + { + return name_val_pairs.cbegin(); + } + value_list::const_iterator end() + { + return name_val_pairs.cend(); + } + std::string toString(); + static annotation* read(util::membuffer& input, constant_pool& pool); + }; + typedef std::vector<annotation*> annotation_table; + + /// type for simple value annotation elements + class element_value_simple : public element_value + { + protected: + /// index of the constant in the constant pool + uint16_t index; + + public: + element_value_simple(element_value_type type, uint16_t index, + constant_pool& pool) + : element_value(type, pool), index(index) { + // TODO: verify consistency + }; + uint16_t getIndex() + { + return index; + } + virtual std::string toString() + { + return pool[index].toString(); + }; + }; + /// The enum_const_value item is used if the tag item is 'e'. + class element_value_enum : public element_value + { + protected: + /** + * The value of the type_name_index item must be a valid index into the + * constant_pool table. The constant_pool entry at that index must be a + * CONSTANT_Utf8_info (§4.4.7) structure representing a valid field + * descriptor (§4.3.2) that denotes the internal form of the binary name + * (§4.2.1) of the type of the enum constant represented by this + * element_value structure. + */ + uint16_t typeIndex; + /** + * The value of the const_name_index item must be a valid index into the + * constant_pool table. The constant_pool entry at that index must be a + * CONSTANT_Utf8_info (§4.4.7) structure representing the simple name of + * the enum constant represented by this element_value structure. + */ + uint16_t valueIndex; + + public: + element_value_enum(element_value_type type, uint16_t typeIndex, + uint16_t valueIndex, constant_pool& pool) + : element_value(type, pool), typeIndex(typeIndex), + valueIndex(valueIndex) + { + // TODO: verify consistency + } + uint16_t getValueIndex() + { + return valueIndex; + } + uint16_t getTypeIndex() + { + return typeIndex; + } + virtual std::string toString() + { + return "enum value"; + }; + }; + + class element_value_class : public element_value + { + protected: + /** + * The class_info_index item must be a valid index into the + * constant_pool table. The constant_pool entry at that index must be a + * CONSTANT_Utf8_info (§4.4.7) structure representing the return + * descriptor (§4.3.3) of the type that is reified by the class + * represented by this element_value structure. + * + * For example, 'V' for Void.class, 'Ljava/lang/Object;' for Object, + * etc. + * + * Or in plain english, you can store type information in annotations. + * Yay. + */ + uint16_t classIndex; + + public: + element_value_class(element_value_type type, uint16_t classIndex, + constant_pool& pool) + : element_value(type, pool), classIndex(classIndex) + { + // TODO: verify consistency + } + uint16_t getIndex() + { + return classIndex; + } + virtual std::string toString() + { + return "class"; + }; + }; + + /// nested annotations... yay + class element_value_annotation : public element_value + { + private: + annotation* nestedAnnotation; + + public: + element_value_annotation(element_value_type type, + annotation* nestedAnnotation, + constant_pool& pool) + : element_value(type, pool), nestedAnnotation(nestedAnnotation) {}; + ~element_value_annotation() + { + if (nestedAnnotation) { + delete nestedAnnotation; + nestedAnnotation = nullptr; + } + } + virtual std::string toString() + { + return "nested annotation"; + }; + }; + + /// and arrays! + class element_value_array : public element_value + { + public: + typedef std::vector<element_value*> elem_vec; + + protected: + elem_vec values; + + public: + element_value_array(element_value_type type, + std::vector<element_value*>& values, + constant_pool& pool) + : element_value(type, pool), values(values) {}; + ~element_value_array() + { + for (unsigned i = 0; i < values.size(); i++) { + delete values[i]; + } + }; + elem_vec::const_iterator begin() + { + return values.cbegin(); + } + elem_vec::const_iterator end() + { + return values.cend(); + } + virtual std::string toString() + { + return "array"; + }; + }; +} // namespace java
\ No newline at end of file diff --git a/meshmc/libraries/classparser/src/classfile.h b/meshmc/libraries/classparser/src/classfile.h new file mode 100644 index 0000000000..0832c8039d --- /dev/null +++ b/meshmc/libraries/classparser/src/classfile.h @@ -0,0 +1,169 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once +#include "membuffer.h" +#include "constants.h" +#include "annotations.h" +#include <map> +namespace java +{ + /** + * Class representing a Java .class file + */ + class classfile : public util::membuffer + { + public: + classfile(char* data, std::size_t size) : membuffer(data, size) + { + valid = false; + is_synthetic = false; + read_be(magic); + if (magic != 0xCAFEBABE) + throw new classfile_exception(); + read_be(minor_version); + read_be(major_version); + constants.load(*this); + read_be(access_flags); + read_be(this_class); + read_be(super_class); + + // Interfaces + uint16_t iface_count = 0; + read_be(iface_count); + while (iface_count) { + uint16_t iface; + read_be(iface); + interfaces.push_back(iface); + iface_count--; + } + + // Fields + // read fields (and attributes from inside fields) (and possible + // inner classes. yay for recursion!) for now though, we will ignore + // all attributes + /* + * field_info + * { + * u2 access_flags; + * u2 name_index; + * u2 descriptor_index; + * u2 attributes_count; + * attribute_info attributes[attributes_count]; + * } + */ + uint16_t field_count = 0; + read_be(field_count); + while (field_count) { + // skip field stuff + skip(6); + // and skip field attributes + uint16_t attr_count = 0; + read_be(attr_count); + while (attr_count) { + skip(2); + uint32_t attr_length = 0; + read_be(attr_length); + skip(attr_length); + attr_count--; + } + field_count--; + } + + // class methods + /* + * method_info + * { + * u2 access_flags; + * u2 name_index; + * u2 descriptor_index; + * u2 attributes_count; + * attribute_info attributes[attributes_count]; + * } + */ + uint16_t method_count = 0; + read_be(method_count); + while (method_count) { + skip(6); + // and skip method attributes + uint16_t attr_count = 0; + read_be(attr_count); + while (attr_count) { + skip(2); + uint32_t attr_length = 0; + read_be(attr_length); + skip(attr_length); + attr_count--; + } + method_count--; + } + + // class attributes + // there are many kinds of attributes. this is just the generic + // wrapper structure. type is decided by attribute name. extensions + // to the standard are *possible* class annotations are one kind of + // a attribute (one per class) + /* + * attribute_info + * { + * u2 attribute_name_index; + * u4 attribute_length; + * u1 info[attribute_length]; + * } + */ + uint16_t class_attr_count = 0; + read_be(class_attr_count); + while (class_attr_count) { + uint16_t name_idx = 0; + read_be(name_idx); + uint32_t attr_length = 0; + read_be(attr_length); + + auto name = constants[name_idx]; + if (name.str_data == "RuntimeVisibleAnnotations") { + uint16_t num_annotations = 0; + read_be(num_annotations); + while (num_annotations) { + visible_class_annotations.push_back( + annotation::read(*this, constants)); + num_annotations--; + } + } else + skip(attr_length); + class_attr_count--; + } + valid = true; + }; + bool valid; + bool is_synthetic; + uint32_t magic; + uint16_t minor_version; + uint16_t major_version; + constant_pool constants; + uint16_t access_flags; + uint16_t this_class; + uint16_t super_class; + // interfaces this class implements ? must be. investigate. + std::vector<uint16_t> interfaces; + // FIXME: doesn't free up memory on delete + java::annotation_table visible_class_annotations; + }; +} // namespace java
\ No newline at end of file diff --git a/meshmc/libraries/classparser/src/classparser.cpp b/meshmc/libraries/classparser/src/classparser.cpp new file mode 100644 index 0000000000..2dd4bdfba2 --- /dev/null +++ b/meshmc/libraries/classparser/src/classparser.cpp @@ -0,0 +1,116 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "classfile.h" +#include "classparser.h" + +#include <QFile> +#include <archive.h> +#include <archive_entry.h> +#include <QDebug> + +namespace classparser +{ + + QString GetMinecraftJarVersion(QString jarName) + { + QString version; + + // check if minecraft.jar exists + QFile jar(jarName); + if (!jar.exists()) + return version; + + // open jar with libarchive + struct archive* a = archive_read_new(); + archive_read_support_format_zip(a); + if (archive_read_open_filename(a, jarName.toUtf8().constData(), + 10240) != ARCHIVE_OK) { + archive_read_free(a); + return version; + } + + // find and read Minecraft.class + QByteArray classData; + struct archive_entry* entry; + while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { + QString name = QString::fromUtf8(archive_entry_pathname(entry)); + if (name == "net/minecraft/client/Minecraft.class") { + la_int64_t sz = archive_entry_size(entry); + if (sz > 0) { + classData.resize(sz); + archive_read_data(a, classData.data(), sz); + } else { + char buf[8192]; + la_ssize_t r; + while ((r = archive_read_data(a, buf, sizeof(buf))) > 0) + classData.append(buf, r); + } + break; + } + archive_read_data_skip(a); + } + archive_read_free(a); + + if (classData.isEmpty()) + return version; + + // parse Minecraft.class + try { + char* temp = classData.data(); + qint64 size = classData.size(); + java::classfile MinecraftClass(temp, size); + java::constant_pool constants = MinecraftClass.constants; + for (java::constant_pool::container_type::const_iterator iter = + constants.begin(); + iter != constants.end(); iter++) { + const java::constant& constant = *iter; + if (constant.type != java::constant_type_t::j_string_data) + continue; + const std::string& str = constant.str_data; + qDebug() << QString::fromStdString(str); + if (str.compare(0, 20, "Minecraft Minecraft ") == 0) { + version = str.substr(20).data(); + break; + } + } + } catch (const java::classfile_exception&) { + } + + return version; + } +} // namespace classparser diff --git a/meshmc/libraries/classparser/src/constants.h b/meshmc/libraries/classparser/src/constants.h new file mode 100644 index 0000000000..251026fcfc --- /dev/null +++ b/meshmc/libraries/classparser/src/constants.h @@ -0,0 +1,241 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once +#include "errors.h" +#include <sstream> + +namespace java +{ + enum class constant_type_t : uint8_t { + j_hole = 0, // HACK: this is a hole in the array, because java is crazy + j_string_data = 1, + j_int = 3, + j_float = 4, + j_long = 5, + j_double = 6, + j_class = 7, + j_string = 8, + j_fieldref = 9, + j_methodref = 10, + j_interface_methodref = 11, + j_nameandtype = 12 + // FIXME: missing some constant types, see + // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4 + }; + + struct ref_type_t { + /** + * Class reference: + * an index within the constant pool to a UTF-8 string containing + * the fully qualified class name (in internal format) + * Used for j_class, j_fieldref, j_methodref and j_interface_methodref + */ + uint16_t class_idx; + // used for j_fieldref, j_methodref and j_interface_methodref + uint16_t name_and_type_idx; + }; + + struct name_and_type_t { + uint16_t name_index; + uint16_t descriptor_index; + }; + + class constant + { + public: + constant_type_t type = constant_type_t::j_hole; + + constant(util::membuffer& buf) + { + buf.read(type); + + // load data depending on type + switch (type) { + case constant_type_t::j_float: + buf.read_be(data.int_data); + break; + case constant_type_t::j_int: + buf.read_be(data.int_data); // same as float data really + break; + case constant_type_t::j_double: + buf.read_be(data.long_data); + break; + case constant_type_t::j_long: + buf.read_be(data.long_data); // same as double + break; + case constant_type_t::j_class: + buf.read_be(data.ref_type.class_idx); + break; + case constant_type_t::j_fieldref: + case constant_type_t::j_methodref: + case constant_type_t::j_interface_methodref: + buf.read_be(data.ref_type.class_idx); + buf.read_be(data.ref_type.name_and_type_idx); + break; + case constant_type_t::j_string: + buf.read_be(data.index); + break; + case constant_type_t::j_string_data: + // HACK HACK: for now, we call these UTF-8 and do no further + // processing. Later, we should do some decoding. It's + // really modified UTF-8 + // * U+0000 is represented as 0xC0,0x80 invalid character + // * any single zero byte ends the string + // * characters above U+10000 are encoded like in CESU-8 + buf.read_jstr(str_data); + break; + case constant_type_t::j_nameandtype: + buf.read_be(data.name_and_type.name_index); + buf.read_be(data.name_and_type.descriptor_index); + break; + default: + // invalid constant type! + throw new classfile_exception(); + } + } + constant(int) {} + + std::string toString() + { + std::ostringstream ss; + switch (type) { + case constant_type_t::j_hole: + ss << "Fake legacy entry"; + break; + case constant_type_t::j_float: + ss << "Float: " << data.float_data; + break; + case constant_type_t::j_double: + ss << "Double: " << data.double_data; + break; + case constant_type_t::j_int: + ss << "Int: " << data.int_data; + break; + case constant_type_t::j_long: + ss << "Long: " << data.long_data; + break; + case constant_type_t::j_string_data: + ss << "StrData: " << str_data; + break; + case constant_type_t::j_string: + ss << "Str: " << data.index; + break; + case constant_type_t::j_fieldref: + ss << "FieldRef: " << data.ref_type.class_idx << " " + << data.ref_type.name_and_type_idx; + break; + case constant_type_t::j_methodref: + ss << "MethodRef: " << data.ref_type.class_idx << " " + << data.ref_type.name_and_type_idx; + break; + case constant_type_t::j_interface_methodref: + ss << "IfMethodRef: " << data.ref_type.class_idx << " " + << data.ref_type.name_and_type_idx; + break; + case constant_type_t::j_class: + ss << "Class: " << data.ref_type.class_idx; + break; + case constant_type_t::j_nameandtype: + ss << "NameAndType: " << data.name_and_type.name_index + << " " << data.name_and_type.descriptor_index; + break; + default: + ss << "Invalid entry (" << int(type) << ")"; + break; + } + return ss.str(); + } + + std::string str_data; /** String data in 'modified utf-8'.*/ + + // store everything here. + union { + int32_t int_data; + int64_t long_data; + float float_data; + double double_data; + uint16_t index; + ref_type_t ref_type; + name_and_type_t name_and_type; + } data = {0}; + }; + + /** + * A helper class that represents the custom container used in Java class + * file for storage of constants + */ + class constant_pool + { + public: + /** + * Create a pool of constants + */ + constant_pool() {} + /** + * Load a java constant pool + */ + void load(util::membuffer& buf) + { + // FIXME: @SANITY this should check for the end of buffer. + uint16_t length = 0; + buf.read_be(length); + length--; + const constant* last_constant = nullptr; + while (length) { + const constant& cnst = constant(buf); + constants.push_back(cnst); + last_constant = &constants[constants.size() - 1]; + if (last_constant->type == constant_type_t::j_double || + last_constant->type == constant_type_t::j_long) { + // push in a fake constant to preserve indexing + constants.push_back(constant(0)); + length -= 2; + } else { + length--; + } + } + } + typedef std::vector<java::constant> container_type; + /** + * Access constants based on jar file index numbers (index of the first + * element is 1) + */ + java::constant& operator[](std::size_t constant_index) + { + if (constant_index == 0 || constant_index > constants.size()) { + throw new classfile_exception(); + } + return constants[constant_index - 1]; + }; + container_type::const_iterator begin() const + { + return constants.begin(); + }; + container_type::const_iterator end() const + { + return constants.end(); + } + + private: + container_type constants; + }; +} // namespace java diff --git a/meshmc/libraries/classparser/src/errors.h b/meshmc/libraries/classparser/src/errors.h new file mode 100644 index 0000000000..95a6aee575 --- /dev/null +++ b/meshmc/libraries/classparser/src/errors.h @@ -0,0 +1,29 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once +#include <exception> +namespace java +{ + class classfile_exception : public std::exception + { + }; +} // namespace java diff --git a/meshmc/libraries/classparser/src/javaendian.h b/meshmc/libraries/classparser/src/javaendian.h new file mode 100644 index 0000000000..693df89964 --- /dev/null +++ b/meshmc/libraries/classparser/src/javaendian.h @@ -0,0 +1,84 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stdint.h> + +/** + * Swap bytes between big endian and local number representation + */ +namespace util +{ +#ifdef MULTIMC_BIG_ENDIAN + inline uint64_t bigswap(uint64_t x) + { + return x; + } + + inline uint32_t bigswap(uint32_t x) + { + return x; + } + + inline uint16_t bigswap(uint16_t x) + { + return x; + } + +#else + inline uint64_t bigswap(uint64_t x) + { + return (x >> 56) | ((x << 40) & 0x00FF000000000000) | + ((x << 24) & 0x0000FF0000000000) | + ((x << 8) & 0x000000FF00000000) | + ((x >> 8) & 0x00000000FF000000) | + ((x >> 24) & 0x0000000000FF0000) | + ((x >> 40) & 0x000000000000FF00) | (x << 56); + } + + inline uint32_t bigswap(uint32_t x) + { + return (x >> 24) | ((x << 8) & 0x00FF0000) | ((x >> 8) & 0x0000FF00) | + (x << 24); + } + + inline uint16_t bigswap(uint16_t x) + { + return (x >> 8) | (x << 8); + } + +#endif + + inline int64_t bigswap(int64_t x) + { + return static_cast<int64_t>(bigswap(static_cast<uint64_t>(x))); + } + + inline int32_t bigswap(int32_t x) + { + return static_cast<int32_t>(bigswap(static_cast<uint32_t>(x))); + } + + inline int16_t bigswap(int16_t x) + { + return static_cast<int16_t>(bigswap(static_cast<uint16_t>(x))); + } +} // namespace util diff --git a/meshmc/libraries/classparser/src/membuffer.h b/meshmc/libraries/classparser/src/membuffer.h new file mode 100644 index 0000000000..bbfc1c3984 --- /dev/null +++ b/meshmc/libraries/classparser/src/membuffer.h @@ -0,0 +1,84 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stdint.h> +#include <string> +#include <vector> +#include <exception> +#include "javaendian.h" + +namespace util +{ + class membuffer + { + public: + membuffer(char* buffer, std::size_t size) + { + current = start = buffer; + end = start + size; + } + ~membuffer() + { + // maybe? possibly? left out to avoid confusion. for now. + // delete start; + } + /** + * Read some value. That's all ;) + */ + template <class T> void read(T& val) + { + val = *(T*)current; + current += sizeof(T); + } + /** + * Read a big-endian number + * valid for 2-byte, 4-byte and 8-byte variables + */ + template <class T> void read_be(T& val) + { + val = util::bigswap(*(T*)current); + current += sizeof(T); + } + /** + * Read a string in the format: + * 2B length (big endian, unsigned) + * length bytes data + */ + void read_jstr(std::string& str) + { + uint16_t length = 0; + read_be(length); + str.append(current, length); + current += length; + } + /** + * Skip N bytes + */ + void skip(std::size_t N) + { + current += N; + } + + private: + char *start, *end, *current; + }; +} // namespace util |
