diff options
Diffstat (limited to 'depends')
126 files changed, 29015 insertions, 0 deletions
diff --git a/depends/classparser/CMakeLists.txt b/depends/classparser/CMakeLists.txt new file mode 100644 index 00000000..5a48e002 --- /dev/null +++ b/depends/classparser/CMakeLists.txt @@ -0,0 +1,41 @@ +project(classparser) + +set(CMAKE_AUTOMOC ON) + +# Find Qt +find_package(Qt5Core REQUIRED) + +# Include Qt headers. +include_directories(${Qt5Base_INCLUDE_DIRS}) + +SET(CLASSPARSER_HEADERS +include/classparser_config.h + +# Public headers +include/javautils.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/javautils.cpp +src/annotations.cpp +) + +# Set the include dir path. +SET(LIBGROUPVIEW_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE) + +# Include self. +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) +include_directories(${CMAKE_BINARY_DIR}/include) + +add_definitions(-DCLASSPARSER_LIBRARY) + +add_library(classparser SHARED ${CLASSPARSER_SOURCES} ${CLASSPARSER_HEADERS}) +qt5_use_modules(classparser Core) diff --git a/depends/classparser/include/classparser_config.h b/depends/classparser/include/classparser_config.h new file mode 100644 index 00000000..a043824a --- /dev/null +++ b/depends/classparser/include/classparser_config.h @@ -0,0 +1,22 @@ +/* Copyright 2013 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/depends/classparser/include/javautils.h b/depends/classparser/include/javautils.h new file mode 100644 index 00000000..90042261 --- /dev/null +++ b/depends/classparser/include/javautils.h @@ -0,0 +1,29 @@ +/* Copyright 2013 MultiMC Contributors + * + * Authors: Orochimarufan <orochimarufan.x3@gmail.com> + * + * 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" + +#define MCVer_Unknown "Unknown" + +namespace javautils +{ +/** + * @brief Get the version from a minecraft.jar by parsing its class files. Expensive! + */ +QString GetMinecraftJarVersion(QString jar); +} diff --git a/depends/classparser/src/annotations.cpp b/depends/classparser/src/annotations.cpp new file mode 100644 index 00000000..d1a7c046 --- /dev/null +++ b/depends/classparser/src/annotations.cpp @@ -0,0 +1,85 @@ +#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(); + } +} +}
\ No newline at end of file diff --git a/depends/classparser/src/annotations.h b/depends/classparser/src/annotations.h new file mode 100644 index 00000000..aa25d241 --- /dev/null +++ b/depends/classparser/src/annotations.h @@ -0,0 +1,277 @@ +#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) {}; + + 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"; + } + ; +}; +}
\ No newline at end of file diff --git a/depends/classparser/src/classfile.h b/depends/classparser/src/classfile.h new file mode 100644 index 00000000..a5e7ee50 --- /dev/null +++ b/depends/classparser/src/classfile.h @@ -0,0 +1,156 @@ +#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; +}; +}
\ No newline at end of file diff --git a/depends/classparser/src/constants.h b/depends/classparser/src/constants.h new file mode 100644 index 00000000..242b943e --- /dev/null +++ b/depends/classparser/src/constants.h @@ -0,0 +1,220 @@ +#pragma once +#include "errors.h" +#include <sstream> + +namespace java +{ +class constant +{ +public: + enum 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 + } type; + + constant(util::membuffer &buf) + { + buf.read(type); + // invalid constant type! + if (type > j_nameandtype || type == (type_t)0 || type == (type_t)2) + throw new classfile_exception(); + + // load data depending on type + switch (type) + { + case j_float: + case j_int: + buf.read_be(int_data); // same as float data really + break; + case j_double: + case j_long: + buf.read_be(long_data); // same as double + break; + case j_class: + buf.read_be(ref_type.class_idx); + break; + case j_fieldref: + case j_methodref: + case j_interface_methodref: + buf.read_be(ref_type.class_idx); + buf.read_be(ref_type.name_and_type_idx); + break; + case j_string: + buf.read_be(index); + break; + case 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 j_nameandtype: + buf.read_be(name_and_type.name_index); + buf.read_be(name_and_type.descriptor_index); + break; + } + } + + constant(int fake) + { + type = j_hole; + } + + std::string toString() + { + std::ostringstream ss; + switch (type) + { + case j_hole: + ss << "Fake legacy entry"; + break; + case j_float: + ss << "Float: " << float_data; + break; + case j_double: + ss << "Double: " << double_data; + break; + case j_int: + ss << "Int: " << int_data; + break; + case j_long: + ss << "Long: " << long_data; + break; + case j_string_data: + ss << "StrData: " << str_data; + break; + case j_string: + ss << "Str: " << index; + break; + case j_fieldref: + ss << "FieldRef: " << ref_type.class_idx << " " << ref_type.name_and_type_idx; + break; + case j_methodref: + ss << "MethodRef: " << ref_type.class_idx << " " << ref_type.name_and_type_idx; + break; + case j_interface_methodref: + ss << "IfMethodRef: " << ref_type.class_idx << " " << ref_type.name_and_type_idx; + break; + case j_class: + ss << "Class: " << ref_type.class_idx; + break; + case j_nameandtype: + ss << "NameAndType: " << name_and_type.name_index << " " + << name_and_type.descriptor_index; + 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; + struct + { + /** + * 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; + } ref_type; + struct + { + uint16_t name_index; + uint16_t descriptor_index; + } name_and_type; + }; +}; + +/** + * 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) + { + uint16_t length = 0; + buf.read_be(length); + length--; + uint16_t index = 1; + 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::j_double || + last_constant->type == constant::j_long) + { + // push in a fake constant to preserve indexing + constants.push_back(constant(0)); + length -= 2; + index += 2; + } + else + { + length--; + index++; + } + } + } + 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; +}; +} diff --git a/depends/classparser/src/errors.h b/depends/classparser/src/errors.h new file mode 100644 index 00000000..ddbbd828 --- /dev/null +++ b/depends/classparser/src/errors.h @@ -0,0 +1,8 @@ +#pragma once +#include <exception> +namespace java +{ +class classfile_exception : public std::exception +{ +}; +} diff --git a/depends/classparser/src/javaendian.h b/depends/classparser/src/javaendian.h new file mode 100644 index 00000000..d488b382 --- /dev/null +++ b/depends/classparser/src/javaendian.h @@ -0,0 +1,76 @@ +#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; +} +; +inline int64_t bigswap(int64_t x) +{ + return x; +} +; +inline int32_t bigswap(int32_t x) +{ + return x; +} +; +inline int16_t bigswap(int16_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); +} +; +inline int64_t bigswap(int64_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 int32_t bigswap(int32_t x) +{ + return (x >> 24) | ((x << 8) & 0x00FF0000) | ((x >> 8) & 0x0000FF00) | (x << 24); +} +; +inline int16_t bigswap(int16_t x) +{ + return (x >> 8) | (x << 8); +} +; +#endif +} diff --git a/depends/classparser/src/javautils.cpp b/depends/classparser/src/javautils.cpp new file mode 100644 index 00000000..9f9ac263 --- /dev/null +++ b/depends/classparser/src/javautils.cpp @@ -0,0 +1,83 @@ +/* Copyright 2013 MultiMC Contributors + * + * Authors: Orochimarufan <orochimarufan.x3@gmail.com> + * + * 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 "javautils.h" + +#include <QFile> +#include <quazipfile.h> + +namespace javautils +{ + +QString GetMinecraftJarVersion(QString jarName) +{ + QString version = MCVer_Unknown; + + // check if minecraft.jar exists + QFile jar(jarName); + if (!jar.exists()) + return version; + + // open minecraft.jar + QuaZip zip(&jar); + if (!zip.open(QuaZip::mdUnzip)) + return version; + + // open Minecraft.class + zip.setCurrentFile("net/minecraft/client/Minecraft.class", QuaZip::csSensitive); + QuaZipFile Minecraft(&zip); + if (!Minecraft.open(QuaZipFile::ReadOnly)) + return version; + + // read Minecraft.class + qint64 size = Minecraft.size(); + char *classfile = new char[size]; + Minecraft.read(classfile, size); + + // parse Minecraft.class + try + { + char *temp = classfile; + 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::j_string_data) + continue; + const std::string &str = constant.str_data; + if (str.compare(0, 20, "Minecraft Minecraft ") == 0) + { + version = str.substr(20).data(); + break; + } + } + } + catch (java::classfile_exception &) + { + } + + // clean up + delete[] classfile; + Minecraft.close(); + zip.close(); + jar.close(); + + return version; +} +} diff --git a/depends/classparser/src/membuffer.h b/depends/classparser/src/membuffer.h new file mode 100644 index 00000000..ab83412a --- /dev/null +++ b/depends/classparser/src/membuffer.h @@ -0,0 +1,63 @@ +#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; +}; +} diff --git a/depends/groupview/.clang-format b/depends/groupview/.clang-format new file mode 100644 index 00000000..167a8fa7 --- /dev/null +++ b/depends/groupview/.clang-format @@ -0,0 +1,24 @@ +UseTab: true +IndentWidth: 4 +TabWidth: 4 +ConstructorInitializerIndentWidth: 4 +AccessModifierOffset: -4 +IndentCaseLabels: false +IndentFunctionDeclarationAfterType: false +NamespaceIndentation: None + +BreakBeforeBraces: Allman +AllowShortIfStatementsOnASingleLine: false +ColumnLimit: 96 +MaxEmptyLinesToKeep: 1 + +Standard: Cpp11 +Cpp11BracedListStyle: true + +SpacesInParentheses: false +SpaceInEmptyParentheses: false +SpacesInCStyleCastParentheses: false +SpaceAfterControlStatementKeyword: true + +AlignTrailingComments: true +SpacesBeforeTrailingComments: 1 diff --git a/depends/groupview/.gitignore b/depends/groupview/.gitignore new file mode 100644 index 00000000..a5d18fa3 --- /dev/null +++ b/depends/groupview/.gitignore @@ -0,0 +1,2 @@ +build/ +*.user* diff --git a/depends/groupview/CMakeLists.txt b/depends/groupview/CMakeLists.txt new file mode 100644 index 00000000..e2a85950 --- /dev/null +++ b/depends/groupview/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 2.8.9) + +project(GroupView) + +set(CMAKE_AUTOMOC ON) + +IF(APPLE) + message(STATUS "Using APPLE CMAKE_CXX_FLAGS") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall") +ELSEIF(UNIX) + # assume GCC, add C++0x/C++11 stuff + MESSAGE(STATUS "Using UNIX CMAKE_CXX_FLAGS") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall") +ELSEIF(MINGW) + MESSAGE(STATUS "Using MINGW CMAKE_CXX_FLAGS") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -Wall") +ENDIF() + +find_package(Qt5Core REQUIRED) +find_package(Qt5Gui REQUIRED) +find_package(Qt5Widgets REQUIRED) + +include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS}) + +set(SOURCES + main.cpp + main.h + + GroupView.h + GroupView.cpp + Group.h + Group.cpp + GroupedProxyModel.h + GroupedProxyModel.cpp + InstanceDelegate.h + InstanceDelegate.cpp +) + +add_executable(GroupView ${SOURCES}) +qt5_use_modules(GroupView Core Gui Widgets) diff --git a/depends/groupview/Group.cpp b/depends/groupview/Group.cpp new file mode 100644 index 00000000..f216cc6e --- /dev/null +++ b/depends/groupview/Group.cpp @@ -0,0 +1,169 @@ +#include "Group.h" + +#include <QModelIndex> +#include <QPainter> +#include <QtMath> + +#include "GroupView.h" + +Group::Group(const QString &text, GroupView *view) : view(view), text(text), collapsed(false) +{ +} + +Group::Group(const Group *other) + : view(other->view), text(other->text), collapsed(other->collapsed) +{ +} + +void Group::update() +{ + firstItemIndex = firstItem().row(); + + rowHeights = QVector<int>(numRows()); + for (int i = 0; i < numRows(); ++i) + { + rowHeights[i] = view->categoryRowHeight( + view->model()->index(i * view->itemsPerRow() + firstItemIndex, 0)); + } +} + +Group::HitResults Group::hitScan(const QPoint &pos) const +{ + Group::HitResults results = Group::NoHit; + int y_start = verticalPosition(); + int body_start = y_start + headerHeight(); + int body_end = body_start + contentHeight() + 5; // FIXME: wtf is this 5? + int y = pos.y(); + // int x = pos.x(); + if(y < y_start) + { + results = Group::NoHit; + } + else if(y < body_start) + { + results = Group::HeaderHit; + int collapseSize = headerHeight() - 4; + + // the icon + QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize); + if(iconRect.contains(pos)) + { + results |= Group::CheckboxHit; + } + } + else if (y < body_end) + { + results |= Group::BodyHit; + } + return results; +} + +void Group::drawHeader(QPainter *painter, const int y) +{ + painter->save(); + + int height = headerHeight() - 4; + int collapseSize = height; + + // the icon + QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y, collapseSize, collapseSize); + painter->setPen(QPen(Qt::black, 1)); + painter->drawRect(iconRect); + static const int margin = 2; + QRect iconSubrect = iconRect.adjusted(margin, margin, -margin, -margin); + int midX = iconSubrect.center().x(); + int midY = iconSubrect.center().y(); + if (collapsed) + { + painter->drawLine(midX, iconSubrect.top(), midX, iconSubrect.bottom()); + } + painter->drawLine(iconSubrect.left(), midY, iconSubrect.right(), midY); + + // the text + int textWidth = painter->fontMetrics().width(text); + QRect textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); + painter->setBrush(view->viewOptions().palette.text()); + view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter, + view->viewport()->palette(), true, text); + + // the line + painter->drawLine(textRect.right() + 4, y + headerHeight() / 2, + view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2); + + painter->restore(); +} + +int Group::totalHeight() const +{ + return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'? +} + +int Group::headerHeight() const +{ + return view->viewport()->fontMetrics().height() + 4; +} + +int Group::contentHeight() const +{ + if (collapsed) + { + return 0; + } + int result = 0; + for (int i = 0; i < rowHeights.size(); ++i) + { + result += rowHeights[i]; + } + return result; +} + +int Group::numRows() const +{ + return qMax(1, qCeil((qreal)numItems() / (qreal)view->itemsPerRow())); +} + +int Group::verticalPosition() const +{ + int res = 0; + const QList<Group *> cats = view->m_groups; + for (int i = 0; i < cats.size(); ++i) + { + if (cats.at(i) == this) + { + break; + } + res += cats.at(i)->totalHeight() + view->m_categoryMargin; + } + return res; +} + +QList<QModelIndex> Group::items() const +{ + QList<QModelIndex> indices; + for (int i = 0; i < view->model()->rowCount(); ++i) + { + const QModelIndex index = view->model()->index(i, 0); + if (index.data(GroupViewRoles::GroupRole).toString() == text) + { + indices.append(index); + } + } + return indices; +} + +int Group::numItems() const +{ + return items().size(); +} + +QModelIndex Group::firstItem() const +{ + QList<QModelIndex> indices = items(); + return indices.isEmpty() ? QModelIndex() : indices.first(); +} + +QModelIndex Group::lastItem() const +{ + QList<QModelIndex> indices = items(); + return indices.isEmpty() ? QModelIndex() : indices.last(); +} diff --git a/depends/groupview/Group.h b/depends/groupview/Group.h new file mode 100644 index 00000000..455ee1a8 --- /dev/null +++ b/depends/groupview/Group.h @@ -0,0 +1,67 @@ +#pragma once + +#include <QString> +#include <QRect> +#include <QVector> + +class GroupView; +class QPainter; +class QModelIndex; + +struct Group +{ +/* constructors */ + Group(const QString &text, GroupView *view); + Group(const Group *other); + +/* data */ + GroupView *view; + QString text; + bool collapsed; + QVector<int> rowHeights; + int firstItemIndex; + +/* logic */ + /// do stuff. and things. TODO: redo. + void update(); + + /// draw the header at y-position. + void drawHeader(QPainter *painter, const int y); + + /// height of the group, in total. includes a small bit of padding. + int totalHeight() const; + + /// height of the group header, in pixels + int headerHeight() const; + + /// height of the group content, in pixels + int contentHeight() const; + + /// the number of visual rows this group has + int numRows() const; + + /// the height at which this group starts, in pixels + int verticalPosition() const; + + enum HitResult + { + NoHit = 0x0, + TextHit = 0x1, + CheckboxHit = 0x2, + HeaderHit = 0x4, + BodyHit = 0x8 + }; + Q_DECLARE_FLAGS(HitResults, HitResult) + + /// shoot! BANG! what did we hit? + HitResults hitScan (const QPoint &pos) const; + + /// super derpy thing. + QList<QModelIndex> items() const; + /// I don't even + int numItems() const; + QModelIndex firstItem() const; + QModelIndex lastItem() const; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(Group::HitResults) diff --git a/depends/groupview/GroupView.cpp b/depends/groupview/GroupView.cpp new file mode 100644 index 00000000..89e3e223 --- /dev/null +++ b/depends/groupview/GroupView.cpp @@ -0,0 +1,908 @@ +#include "GroupView.h" + +#include <QPainter> +#include <QApplication> +#include <QtMath> +#include <QDebug> +#include <QMouseEvent> +#include <QListView> +#include <QPersistentModelIndex> +#include <QDrag> +#include <QMimeData> +#include <QScrollBar> + +#include "Group.h" + +template <typename T> bool listsIntersect(const QList<T> &l1, const QList<T> t2) +{ + for (auto &item : l1) + { + if (t2.contains(item)) + { + return true; + } + } + return false; +} + +GroupView::GroupView(QWidget *parent) + : QAbstractItemView(parent), m_leftMargin(5), m_rightMargin(5), m_bottomMargin(5), + m_categoryMargin(5) //, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0) +{ + // setViewMode(IconMode); + // setMovement(Snap); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + // setWordWrap(true); + // setDragDropMode(QListView::InternalMove); + setAcceptDrops(true); + m_spacing = 5; +} + +GroupView::~GroupView() +{ + qDeleteAll(m_groups); + m_groups.clear(); +} + +void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector<int> &roles) +{ + if (roles.contains(GroupViewRoles::GroupRole) || roles.contains(Qt::DisplayRole)) + { + updateGeometries(); + } + viewport()->update(); +} +void GroupView::rowsInserted(const QModelIndex &parent, int start, int end) +{ + updateGeometries(); + viewport()->update(); +} + +void GroupView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + updateGeometries(); + viewport()->update(); +} + +void GroupView::updateGeometries() +{ + int previousScroll = verticalScrollBar()->value(); + + QMap<QString, Group *> cats; + + for (int i = 0; i < model()->rowCount(); ++i) + { + const QString groupName = + model()->index(i, 0).data(GroupViewRoles::GroupRole).toString(); + if (!cats.contains(groupName)) + { + Group *old = this->category(groupName); + if (old) + { + cats.insert(groupName, new Group(old)); + } + else + { + cats.insert(groupName, new Group(groupName, this)); + } + } + } + + /*if (m_editedCategory) + { + m_editedCategory = cats[m_editedCategory->text]; + }*/ + + qDeleteAll(m_groups); + m_groups = cats.values(); + + for (auto cat : m_groups) + { + cat->update(); + } + + if (m_groups.isEmpty()) + { + verticalScrollBar()->setRange(0, 0); + } + else + { + int totalHeight = 0; + for (auto category : m_groups) + { + totalHeight += category->totalHeight() + m_categoryMargin; + } + // remove the last margin (we don't want it) + totalHeight -= m_categoryMargin; + totalHeight += m_bottomMargin; + verticalScrollBar()->setRange(0, totalHeight - height()); + } + + verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum())); + + viewport()->update(); +} + +bool GroupView::isIndexHidden(const QModelIndex &index) const +{ + Group *cat = category(index); + if (cat) + { + return cat->collapsed; + } + else + { + return false; + } +} + +Group *GroupView::category(const QModelIndex &index) const +{ + return category(index.data(GroupViewRoles::GroupRole).toString()); +} + +Group *GroupView::category(const QString &cat) const +{ + for (auto group : m_groups) + { + if (group->text == cat) + { + return group; + } + } + return nullptr; +} + +Group *GroupView::categoryAt(const QPoint &pos) const +{ + for (auto group : m_groups) + { + if(group->hitScan(pos) & Group::CheckboxHit) + { + return group; + } + } + return nullptr; +} + +int GroupView::itemsPerRow() const +{ + return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + m_spacing)); +} + +int GroupView::contentWidth() const +{ + return width() - m_leftMargin - m_rightMargin; +} + +int GroupView::itemWidth() const +{ + return itemDelegate() + ->sizeHint(viewOptions(), model()->index(model()->rowCount() - 1, 0)) + .width(); +} + +int GroupView::categoryRowHeight(const QModelIndex &index) const +{ + QModelIndexList indices; + int internalRow = categoryInternalPosition(index).second; + for (auto &i : category(index)->items()) + { + if (categoryInternalPosition(i).second == internalRow) + { + indices.append(i); + } + } + + int largestHeight = 0; + for (auto &i : indices) + { + largestHeight = + qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height()); + } + return largestHeight; +} + +QPair<int, int> GroupView::categoryInternalPosition(const QModelIndex &index) const +{ + QList<QModelIndex> indices = category(index)->items(); + int x = 0; + int y = 0; + const int perRow = itemsPerRow(); + for (int i = 0; i < indices.size(); ++i) + { + if (indices.at(i) == index) + { + break; + } + ++x; + if (x == perRow) + { + x = 0; + ++y; + } + } + return qMakePair(x, y); +} + +int GroupView::categoryInternalRowTop(const QModelIndex &index) const +{ + Group *cat = category(index); + int categoryInternalRow = categoryInternalPosition(index).second; + int result = 0; + for (int i = 0; i < categoryInternalRow; ++i) + { + result += cat->rowHeights.at(i); + } + return result; +} + +int GroupView::itemHeightForCategoryRow(const Group *category, const int internalRow) const +{ + for (auto &i : category->items()) + { + QPair<int, int> pos = categoryInternalPosition(i); + if (pos.second == internalRow) + { + return categoryRowHeight(i); + } + } + return -1; +} + +void GroupView::mousePressEvent(QMouseEvent *event) +{ + // endCategoryEditor(); + + QPoint pos = event->pos() + offset(); + QPersistentModelIndex index = indexAt(pos); + + m_pressedIndex = index; + m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); + QItemSelectionModel::SelectionFlags selection_flags = selectionCommand(index, event); + m_pressedPosition = pos; + + m_pressedCategory = categoryAt(m_pressedPosition); + if (m_pressedCategory) + { + setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState); + event->accept(); + return; + } + + if (index.isValid() && (index.flags() & Qt::ItemIsEnabled)) + { + // we disable scrollTo for mouse press so the item doesn't change position + // when the user is interacting with it (ie. clicking on it) + bool autoScroll = hasAutoScroll(); + setAutoScroll(false); + selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + setAutoScroll(autoScroll); + QRect rect(m_pressedPosition, pos); + setSelection(rect, QItemSelectionModel::ClearAndSelect); + + // signal handlers may change the model + emit pressed(index); + } + else + { + // Forces a finalize() even if mouse is pressed, but not on a item + selectionModel()->select(QModelIndex(), QItemSelectionModel::Select); + } +} + +void GroupView::mouseMoveEvent(QMouseEvent *event) +{ + QPoint topLeft; + QPoint pos = event->pos() + offset(); + + if (state() == ExpandingState || state() == CollapsingState) + { + return; + } + + if (state() == DraggingState) + { + topLeft = m_pressedPosition - offset(); + if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance()) + { + m_pressedIndex = QModelIndex(); + startDrag(model()->supportedDragActions()); + setState(NoState); + stopAutoScroll(); + } + return; + } + + if (selectionMode() != SingleSelection) + { + topLeft = m_pressedPosition - offset(); + } + else + { + topLeft = pos; + } + + if (m_pressedIndex.isValid() && (state() != DragSelectingState) && + (event->buttons() != Qt::NoButton) && !selectedIndexes().isEmpty()) + { + setState(DraggingState); + return; + } + + if ((event->buttons() & Qt::LeftButton) && selectionModel()) + { + setState(DragSelectingState); + + setSelection(QRect(pos, pos), QItemSelectionModel::ClearAndSelect); + QModelIndex index = indexAt(pos); + + // set at the end because it might scroll the view + if (index.isValid() && (index != selectionModel()->currentIndex()) && + (index.flags() & Qt::ItemIsEnabled)) + { + selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + } + } +} + +void GroupView::mouseReleaseEvent(QMouseEvent *event) +{ + QPoint pos = event->pos() + offset(); + QPersistentModelIndex index = indexAt(pos); + + bool click = (index == m_pressedIndex && index.isValid()) || + (m_pressedCategory && m_pressedCategory == categoryAt(pos)); + + if (click && m_pressedCategory) + { + if (state() == ExpandingState) + { + m_pressedCategory->collapsed = false; + updateGeometries(); + viewport()->update(); + event->accept(); + return; + } + else if (state() == CollapsingState) + { + m_pressedCategory->collapsed = true; + updateGeometries(); + viewport()->update(); + event->accept(); + return; + } + } + + m_ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate; + + setState(NoState); + + if (click) + { + if (event->button() == Qt::LeftButton) + { + emit clicked(index); + } + QStyleOptionViewItem option = viewOptions(); + if (m_pressedAlreadySelected) + { + option.state |= QStyle::State_Selected; + } + if ((model()->flags(index) & Qt::ItemIsEnabled) && + style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) + { + emit activated(index); + } + } +} + +void GroupView::mouseDoubleClickEvent(QMouseEvent *event) +{ + QModelIndex index = indexAt(event->pos()); + if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index)) + { + QMouseEvent me(QEvent::MouseButtonPress, event->localPos(), event->windowPos(), + event->screenPos(), event->button(), event->buttons(), + event->modifiers()); + mousePressEvent(&me); + return; + } + // signal handlers may change the model + QPersistentModelIndex persistent = index; + emit doubleClicked(persistent); +} + +void GroupView::paintEvent(QPaintEvent *event) +{ + QPainter painter(this->viewport()); + + int y = -verticalOffset(); + for (int i = 0; i < m_groups.size(); ++i) + { + Group *category = m_groups.at(i); + category->drawHeader(&painter, y); + y += category->totalHeight() + m_categoryMargin; + } + + for (int i = 0; i < model()->rowCount(); ++i) + { + const QModelIndex index = model()->index(i, 0); + if (isIndexHidden(index)) + { + continue; + } + Qt::ItemFlags flags = index.flags(); + QStyleOptionViewItemV4 option(viewOptions()); + option.rect = visualRect(index); + option.widget = this; + option.features |= + QStyleOptionViewItemV2::WrapText; // FIXME: what is the meaning of this anyway? + if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index)) + { + option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected + : QStyle::State_None; + } + else + { + option.state &= ~QStyle::State_Selected; + } + option.state |= (index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None; + if (!(flags & Qt::ItemIsEnabled)) + { + option.state &= ~QStyle::State_Enabled; + } + itemDelegate()->paint(&painter, option, index); + } + + /* + * Drop indicators for manual reordering... + */ +#if 0 + if (!m_lastDragPosition.isNull()) + { + QPair<Group *, int> pair = rowDropPos(m_lastDragPosition); + Group *category = pair.first; + int row = pair.second; + if (category) + { + int internalRow = row - category->firstItemIndex; + QLine line; + if (internalRow >= category->numItems()) + { + QRect toTheRightOfRect = visualRect(category->lastItem()); + line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight()); + } + else + { + QRect toTheLeftOfRect = visualRect(model()->index(row, 0)); + line = QLine(toTheLeftOfRect.topLeft(), toTheLeftOfRect.bottomLeft()); + } + painter.save(); + painter.setPen(QPen(Qt::black, 3)); + painter.drawLine(line); + painter.restore(); + } + } +#endif +} + +void GroupView::resizeEvent(QResizeEvent *event) +{ + // QListView::resizeEvent(event); + + // if (m_categoryEditor) + // { + // m_categoryEditor->resize(qMax(contentWidth() / 2, + // m_editedCategory->textRect.width()), + // m_categoryEditor->height()); + // } + + updateGeometries(); +} + +void GroupView::dragEnterEvent(QDragEnterEvent *event) +{ + if (!isDragEventAccepted(event)) + { + return; + } + m_lastDragPosition = event->pos() + offset(); + viewport()->update(); + event->accept(); +} + +void GroupView::dragMoveEvent(QDragMoveEvent *event) +{ + if (!isDragEventAccepted(event)) + { + return; + } + m_lastDragPosition = event->pos() + offset(); + viewport()->update(); + event->accept(); +} + +void GroupView::dragLeaveEvent(QDragLeaveEvent *event) +{ + m_lastDragPosition = QPoint(); + viewport()->update(); +} + +void GroupView::dropEvent(QDropEvent *event) +{ + m_lastDragPosition = QPoint(); + + stopAutoScroll(); + setState(NoState); + + if (event->source() != this || !(event->possibleActions() & Qt::MoveAction)) + { + return; + } + + QPair<Group *, int> dropPos = rowDropPos(event->pos() + offset()); + const Group *category = dropPos.first; + const int row = dropPos.second; + + if (row == -1) + { + viewport()->update(); + return; + } + + const QString categoryText = category->text; + if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) + { + model()->setData(model()->index(row, 0), categoryText, + GroupViewRoles::GroupRole); + event->setDropAction(Qt::MoveAction); + event->accept(); + } + updateGeometries(); + viewport()->update(); +} + +void GroupView::startDrag(Qt::DropActions supportedActions) +{ + QModelIndexList indexes = selectionModel()->selectedIndexes(); + if (indexes.count() > 0) + { + QMimeData *data = model()->mimeData(indexes); + if (!data) + { + return; + } + QRect rect; + QPixmap pixmap = renderToPixmap(indexes, &rect); + //rect.translate(offset()); + // rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); + QDrag *drag = new QDrag(this); + drag->setPixmap(pixmap); + drag->setMimeData(data); + drag->setHotSpot(m_pressedPosition - rect.topLeft()); + Qt::DropAction defaultDropAction = Qt::IgnoreAction; + if (this->defaultDropAction() != Qt::IgnoreAction && + (supportedActions & this->defaultDropAction())) + { + defaultDropAction = this->defaultDropAction(); + } + if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction) + { + const QItemSelection selection = selectionModel()->selection(); + + for (auto it = selection.constBegin(); it != selection.constEnd(); ++it) + { + QModelIndex parent = (*it).parent(); + if ((*it).left() != 0) + { + continue; + } + if ((*it).right() != (model()->columnCount(parent) - 1)) + { + continue; + } + int count = (*it).bottom() - (*it).top() + 1; + model()->removeRows((*it).top(), count, parent); + } + } + } +} + +QRect GroupView::visualRect(const QModelIndex &index) const +{ + return geometryRect(index).translated(-offset()); +} + +QRect GroupView::geometryRect(const QModelIndex &index) const +{ + if (!index.isValid() || isIndexHidden(index) || index.column() > 0) + { + return QRect(); + } + + const Group *cat = category(index); + QPair<int, int> pos = categoryInternalPosition(index); + int x = pos.first; + // int y = pos.second; + + QRect out; + out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + categoryInternalRowTop(index)); + out.setLeft(m_spacing + x * (itemWidth() + m_spacing)); + out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); + + return out; +} + +/* +void CategorizedView::startCategoryEditor(Category *category) +{ + if (m_categoryEditor != 0) + { + return; + } + m_editedCategory = category; + m_categoryEditor = new QLineEdit(m_editedCategory->text, this); + QRect rect = m_editedCategory->textRect; + rect.setWidth(qMax(contentWidth() / 2, rect.width())); + m_categoryEditor->setGeometry(rect); + m_categoryEditor->show(); + m_categoryEditor->setFocus(); + connect(m_categoryEditor, &QLineEdit::returnPressed, this, +&CategorizedView::endCategoryEditor); +} + +void CategorizedView::endCategoryEditor() +{ + if (m_categoryEditor == 0) + { + return; + } + m_editedCategory->text = m_categoryEditor->text(); + m_updatesDisabled = true; + foreach (const QModelIndex &index, itemsForCategory(m_editedCategory)) + { + const_cast<QAbstractItemModel *>(index.model())->setData(index, +m_categoryEditor->text(), CategoryRole); + } + m_updatesDisabled = false; + delete m_categoryEditor; + m_categoryEditor = 0; + m_editedCategory = 0; + updateGeometries(); +} +*/ + +QModelIndex GroupView::indexAt(const QPoint &point) const +{ + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + if (geometryRect(index).contains(point)) + { + return index; + } + } + return QModelIndex(); +} + +void GroupView::setSelection(const QRect &rect, + const QItemSelectionModel::SelectionFlags commands) +{ + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + QRect itemRect = geometryRect(index); + if (itemRect.intersects(rect)) + { + selectionModel()->select(index, commands); + update(itemRect.translated(-offset())); + } + } + +} + +QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) const +{ + Q_ASSERT(r); + auto paintPairs = draggablePaintPairs(indices, r); + if (paintPairs.isEmpty()) + { + return QPixmap(); + } + QPixmap pixmap(r->size()); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + QStyleOptionViewItem option = viewOptions(); + option.state |= QStyle::State_Selected; + for (int j = 0; j < paintPairs.count(); ++j) + { + option.rect = paintPairs.at(j).first.translated(-r->topLeft()); + const QModelIndex ¤t = paintPairs.at(j).second; + itemDelegate()->paint(&painter, option, current); + } + return pixmap; +} + +QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices, + QRect *r) const +{ + Q_ASSERT(r); + QRect &rect = *r; + QList<QPair<QRect, QModelIndex>> ret; + for (int i = 0; i < indices.count(); ++i) + { + const QModelIndex &index = indices.at(i); + const QRect current = geometryRect(index); + ret += qMakePair(current, index); + rect |= current; + } + return ret; +} + +bool GroupView::isDragEventAccepted(QDropEvent *event) +{ + if (event->source() != this) + { + return false; + } + if (!listsIntersect(event->mimeData()->formats(), model()->mimeTypes())) + { + return false; + } + if (!model()->canDropMimeData(event->mimeData(), event->dropAction(), + rowDropPos(event->pos()).second, 0, QModelIndex())) + { + return false; + } + return true; +} + +QPair<Group *, int> GroupView::rowDropPos(const QPoint &pos) +{ + // check that we aren't on a category header and calculate which category we're in + Group *category = 0; + { + int y = 0; + for (auto cat : m_groups) + { + if (pos.y() > y && pos.y() < (y + cat->headerHeight())) + { + return qMakePair(nullptr, -1); + } + y += cat->totalHeight() + m_categoryMargin; + if (pos.y() < y) + { + category = cat; + break; + } + } + if (category == 0) + { + return qMakePair(nullptr, -1); + } + } + + QList<QModelIndex> indices = category->items(); + + // calculate the internal column + int internalColumn = -1; + { + const int itemWidth = this->itemWidth(); + if (pos.x() >= (itemWidth * itemsPerRow())) + { + internalColumn = itemsPerRow(); + } + else + { + for (int i = 0, c = 0; i < contentWidth(); i += itemWidth + 10 /*spacing()*/, ++c) + { + if (pos.x() > (i - itemWidth / 2) && pos.x() <= (i + itemWidth / 2)) + { + internalColumn = c; + break; + } + } + } + if (internalColumn == -1) + { + return qMakePair(nullptr, -1); + } + } + + // calculate the internal row + int internalRow = -1; + { + // FIXME rework the drag and drop code + const int top = category->verticalPosition(); + for (int r = 0, h = top; r < category->numRows(); + h += itemHeightForCategoryRow(category, r), ++r) + { + if (pos.y() > h && pos.y() < (h + itemHeightForCategoryRow(category, r))) + { + internalRow = r; + break; + } + } + if (internalRow == -1) + { + return qMakePair(nullptr, -1); + } + // this happens if we're in the margin between a one category and another + // categories header + if (internalRow > (indices.size() / itemsPerRow())) + { + return qMakePair(nullptr, -1); + } + } + + // flaten the internalColumn/internalRow to one row + int categoryRow = internalRow * itemsPerRow() + internalColumn; + + // this is used if we're past the last item + if (categoryRow >= indices.size()) + { + return qMakePair(category, indices.last().row() + 1); + } + + return qMakePair(category, indices.at(categoryRow).row()); +} + +QPoint GroupView::offset() const +{ + return QPoint(horizontalOffset(), verticalOffset()); +} + +QRegion GroupView::visualRegionForSelection(const QItemSelection &selection) const +{ + QRegion region; + for (auto &range : selection) + { + int start_row = range.top(); + int end_row = range.bottom(); + for (int row = start_row; row <= end_row; ++row) + { + int start_column = range.left(); + int end_column = range.right(); + for (int column = start_column; column <= end_column; ++column) + { + QModelIndex index = model()->index(row, column, rootIndex()); + region += visualRect(index); // OK + } + } + } + return region; +} +QModelIndex GroupView::moveCursor(QAbstractItemView::CursorAction cursorAction, + Qt::KeyboardModifiers modifiers) +{ + auto current = currentIndex(); + if(!current.isValid()) + { + qDebug() << "model row: invalid"; + return current; + } + qDebug() << "model row: " << current.row(); + auto cat = category(current); + int i = m_groups.indexOf(cat); + if(i >= 0) + { + // this is a pile of something foul + auto real_group = m_groups[i]; + int beginning_row = 0; + for(auto group: m_groups) + { + if(group == real_group) + break; + beginning_row += group->numRows(); + } + qDebug() << "category: " << real_group->text; + QPair<int, int> pos = categoryInternalPosition(current); + int row = beginning_row + pos.second; + qDebug() << "row: " << row; + qDebug() << "column: " << pos.first; + } + return current; +} diff --git a/depends/groupview/GroupView.h b/depends/groupview/GroupView.h new file mode 100644 index 00000000..329a3503 --- /dev/null +++ b/depends/groupview/GroupView.h @@ -0,0 +1,139 @@ +#pragma once + +#include <QListView> +#include <QLineEdit> +#include <QScrollBar> + +struct GroupViewRoles +{ + enum + { + GroupRole = Qt::UserRole, + ProgressValueRole, + ProgressMaximumRole + }; +}; + +struct Group; + +class GroupView : public QAbstractItemView +{ + Q_OBJECT + +public: + GroupView(QWidget *parent = 0); + ~GroupView(); + + QRect geometryRect(const QModelIndex &index) const; + virtual QRect visualRect(const QModelIndex &index) const override; + QModelIndex indexAt(const QPoint &point) const; + void setSelection(const QRect &rect, + const QItemSelectionModel::SelectionFlags commands) override; + + virtual int horizontalOffset() const override + { + return horizontalScrollBar()->value(); + } + + virtual int verticalOffset() const override + { + return verticalScrollBar()->value(); + } + + virtual void scrollContentsBy(int dx, int dy) override + { + scrollDirtyRegion(dx, dy); + viewport()->scroll(dx, dy); + } + + /* + * TODO! + */ + virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override + { + return; + } + + virtual QModelIndex moveCursor(CursorAction cursorAction, + Qt::KeyboardModifiers modifiers) override; + + virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override; + +protected +slots: + virtual void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector<int> &roles) override; + virtual void rowsInserted(const QModelIndex &parent, int start, int end) override; + virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override; + virtual void updateGeometries() override; + +protected: + virtual bool isIndexHidden(const QModelIndex &index) const override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; + + void startDrag(Qt::DropActions supportedActions) override; + +private: + friend struct Group; + + QList<Group *> m_groups; + + int m_leftMargin; + int m_rightMargin; + int m_bottomMargin; + int m_categoryMargin; + + // bool m_updatesDisabled; + + Group *category(const QModelIndex &index) const; + Group *category(const QString &cat) const; + Group *categoryAt(const QPoint &pos) const; + + int itemsPerRow() const; + int contentWidth() const; + +private: + int itemWidth() const; + int categoryRowHeight(const QModelIndex &index) const; + + /*QLineEdit *m_categoryEditor; + Category *m_editedCategory; + void startCategoryEditor(Category *category); + +private slots: + void endCategoryEditor();*/ + +private: /* variables */ + QPoint m_pressedPosition; + QPersistentModelIndex m_pressedIndex; + bool m_pressedAlreadySelected; + Group *m_pressedCategory; + QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; + QPoint m_lastDragPosition; + int m_spacing = 5; + +private: /* methods */ + QPair<int, int> categoryInternalPosition(const QModelIndex &index) const; + int categoryInternalRowTop(const QModelIndex &index) const; + int itemHeightForCategoryRow(const Group *category, const int internalRow) const; + + QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; + QList<QPair<QRect, QModelIndex>> draggablePaintPairs(const QModelIndexList &indices, + QRect *r) const; + + bool isDragEventAccepted(QDropEvent *event); + + QPair<Group *, int> rowDropPos(const QPoint &pos); + + QPoint offset() const; +}; diff --git a/depends/groupview/GroupedProxyModel.cpp b/depends/groupview/GroupedProxyModel.cpp new file mode 100644 index 00000000..57d7ff5c --- /dev/null +++ b/depends/groupview/GroupedProxyModel.cpp @@ -0,0 +1,21 @@ +#include "GroupedProxyModel.h" + +#include "GroupView.h" + +GroupedProxyModel::GroupedProxyModel(QObject *parent) : QSortFilterProxyModel(parent) +{ +} + +bool GroupedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + const QString leftCategory = left.data(GroupViewRoles::GroupRole).toString(); + const QString rightCategory = right.data(GroupViewRoles::GroupRole).toString(); + if (leftCategory == rightCategory) + { + return left.row() < right.row(); + } + else + { + return leftCategory < rightCategory; + } +} diff --git a/depends/groupview/GroupedProxyModel.h b/depends/groupview/GroupedProxyModel.h new file mode 100644 index 00000000..cae87ecd --- /dev/null +++ b/depends/groupview/GroupedProxyModel.h @@ -0,0 +1,14 @@ +#pragma once + +#include <QSortFilterProxyModel> + +class GroupedProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + GroupedProxyModel(QObject *parent = 0); + +protected: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; +}; diff --git a/depends/groupview/InstanceDelegate.cpp b/depends/groupview/InstanceDelegate.cpp new file mode 100644 index 00000000..8527e2bc --- /dev/null +++ b/depends/groupview/InstanceDelegate.cpp @@ -0,0 +1,281 @@ +/* Copyright 2013 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 "InstanceDelegate.h" +#include <QPainter> +#include <QTextOption> +#include <QTextLayout> +#include <QApplication> +#include <QtCore/qmath.h> + +#include "GroupView.h" + +// Origin: Qt +static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, + qreal &widthUsed) +{ + height = 0; + widthUsed = 0; + textLayout.beginLayout(); + QString str = textLayout.text(); + while (true) + { + QTextLine line = textLayout.createLine(); + if (!line.isValid()) + break; + if (line.textLength() == 0) + break; + line.setLineWidth(lineWidth); + line.setPosition(QPointF(0, height)); + height += line.height(); + widthUsed = qMax(widthUsed, line.naturalTextWidth()); + } + textLayout.endLayout(); +} + +#define QFIXED_MAX (INT_MAX / 256) + +ListViewDelegate::ListViewDelegate(QObject *parent) : QStyledItemDelegate(parent) +{ +} + +void drawSelectionRect(QPainter *painter, const QStyleOptionViewItemV4 &option, + const QRect &rect) +{ + if ((option.state & QStyle::State_Selected)) + painter->fillRect(rect, option.palette.brush(QPalette::Highlight)); + else + { + QColor backgroundColor = option.palette.color(QPalette::Background); + backgroundColor.setAlpha(160); + painter->fillRect(rect, QBrush(backgroundColor)); + } +} + +void drawFocusRect(QPainter *painter, const QStyleOptionViewItemV4 &option, const QRect &rect) +{ + if (!(option.state & QStyle::State_HasFocus)) + return; + QStyleOptionFocusRect opt; + opt.direction = option.direction; + opt.fontMetrics = option.fontMetrics; + opt.palette = option.palette; + opt.rect = rect; + // opt.state = option.state | QStyle::State_KeyboardFocusChange | + // QStyle::State_Item; + auto col = option.state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base; + opt.backgroundColor = option.palette.color(col); + // Apparently some widget styles expect this hint to not be set + painter->setRenderHint(QPainter::Antialiasing, false); + + QStyle *style = option.widget ? option.widget->style() : QApplication::style(); + + style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter, option.widget); + + painter->setRenderHint(QPainter::Antialiasing); +} + +// TODO this can be made a lot prettier +void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItemV4 &option, + const int value, const int maximum) +{ + if (maximum == 0 || value == maximum) + { + return; + } + + painter->save(); + + qreal percent = (qreal)value / (qreal)maximum; + QColor color = option.palette.color(QPalette::Dark); + color.setAlphaF(0.70f); + painter->setBrush(color); + painter->setPen(QPen(QBrush(), 0)); + painter->drawPie(option.rect, 90 * 16, -percent * 360 * 16); + + painter->restore(); +} + +static QSize viewItemTextSize(const QStyleOptionViewItemV4 *option) +{ + QStyle *style = option->widget ? option->widget->style() : QApplication::style(); + QTextOption textOption; + textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + QTextLayout textLayout; + textLayout.setTextOption(textOption); + textLayout.setFont(option->font); + textLayout.setText(option->text); + const int textMargin = + style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1; + QRect bounds(0, 0, 100 - 2 * textMargin, 600); + qreal height = 0, widthUsed = 0; + viewItemTextLayout(textLayout, bounds.width(), height, widthUsed); + const QSize size(qCeil(widthUsed), qCeil(height)); + return QSize(size.width() + 2 * textMargin, size.height()); +} + +void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + painter->save(); + painter->setClipRect(opt.rect); + + opt.features |= QStyleOptionViewItem::WrapText; + opt.text = index.data().toString(); + opt.textElideMode = Qt::ElideRight; + opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; + + QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); + + // const int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize); + const int iconSize = 48; + QRect iconbox = opt.rect; + const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, opt.widget) + 1; + QRect textRect = opt.rect; + QRect textHighlightRect = textRect; + // clip the decoration on top, remove width padding + textRect.adjust(textMargin, iconSize + textMargin + 5, -textMargin, 0); + + textHighlightRect.adjust(0, iconSize + 5, 0, 0); + + // draw background + { + // FIXME: unused + // QSize textSize = viewItemTextSize ( &opt ); + QPalette::ColorGroup cg; + QStyleOptionViewItemV4 opt2(opt); + + if ((opt.widget && opt.widget->isEnabled()) || (opt.state & QStyle::State_Enabled)) + { + if (!(opt.state & QStyle::State_Active)) + cg = QPalette::Inactive; + else + cg = QPalette::Normal; + } + else + { + cg = QPalette::Disabled; + } + opt2.palette.setCurrentColorGroup(cg); + + // fill in background, if any + if (opt.backgroundBrush.style() != Qt::NoBrush) + { + QPointF oldBO = painter->brushOrigin(); + painter->setBrushOrigin(opt.rect.topLeft()); + painter->fillRect(opt.rect, opt.backgroundBrush); + painter->setBrushOrigin(oldBO); + } + + if (opt.showDecorationSelected) + { + drawSelectionRect(painter, opt2, opt.rect); + drawFocusRect(painter, opt2, opt.rect); + // painter->fillRect ( opt.rect, opt.palette.brush ( cg, QPalette::Highlight ) ); + } + else + { + + // if ( opt.state & QStyle::State_Selected ) + { + // QRect textRect = subElementRect ( QStyle::SE_ItemViewItemText, opt, + // opt.widget ); + // painter->fillRect ( textHighlightRect, opt.palette.brush ( cg, + // QPalette::Highlight ) ); + drawSelectionRect(painter, opt2, textHighlightRect); + drawFocusRect(painter, opt2, textHighlightRect); + } + } + } + + // draw the icon + { + QIcon::Mode mode = QIcon::Normal; + if (!(opt.state & QStyle::State_Enabled)) + mode = QIcon::Disabled; + else if (opt.state & QStyle::State_Selected) + mode = QIcon::Selected; + QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off; + + iconbox.setHeight(iconSize); + opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); + } + // set the text colors + QPalette::ColorGroup cg = + opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) + cg = QPalette::Inactive; + if (opt.state & QStyle::State_Selected) + { + painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); + } + else + { + painter->setPen(opt.palette.color(cg, QPalette::Text)); + } + + // draw the text + QTextOption textOption; + textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + textOption.setTextDirection(opt.direction); + textOption.setAlignment(QStyle::visualAlignment(opt.direction, opt.displayAlignment)); + QTextLayout textLayout; + textLayout.setTextOption(textOption); + textLayout.setFont(opt.font); + textLayout.setText(opt.text); + + qreal width, height; + viewItemTextLayout(textLayout, textRect.width(), height, width); + + const int lineCount = textLayout.lineCount(); + + const QRect layoutRect = QStyle::alignedRect( + opt.direction, opt.displayAlignment, QSize(textRect.width(), int(height)), textRect); + const QPointF position = layoutRect.topLeft(); + for (int i = 0; i < lineCount; ++i) + { + const QTextLine line = textLayout.lineAt(i); + line.draw(painter, position); + } + + drawProgressOverlay(painter, opt, + index.data(GroupViewRoles::ProgressValueRole).toInt(), + index.data(GroupViewRoles::ProgressMaximumRole).toInt()); + + painter->restore(); +} + +QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + opt.features |= QStyleOptionViewItem::WrapText; + opt.text = index.data().toString(); + opt.textElideMode = Qt::ElideRight; + opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; + + QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); + const int textMargin = + style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1; + int height = 48 + textMargin * 2 + 5; // TODO: turn constants into variables + QSize szz = viewItemTextSize(&opt); + height += szz.height(); + // FIXME: maybe the icon items could scale and keep proportions? + QSize sz(100, height); + return sz; +} diff --git a/depends/groupview/InstanceDelegate.h b/depends/groupview/InstanceDelegate.h new file mode 100644 index 00000000..de2f429b --- /dev/null +++ b/depends/groupview/InstanceDelegate.h @@ -0,0 +1,29 @@ +/* Copyright 2013 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 <QStyledItemDelegate> + +class ListViewDelegate : public QStyledItemDelegate +{ +public: + explicit ListViewDelegate(QObject *parent = 0); + +protected: + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; +}; diff --git a/depends/groupview/main.cpp b/depends/groupview/main.cpp new file mode 100644 index 00000000..df14e3bd --- /dev/null +++ b/depends/groupview/main.cpp @@ -0,0 +1,98 @@ +#include "main.h" + +#include <QApplication> +#include <QStandardItemModel> +#include <QPainter> +#include <QTime> + +#include "GroupView.h" +#include "GroupedProxyModel.h" +#include "InstanceDelegate.h" + +Progresser *progresser; + +QPixmap icon(const Qt::GlobalColor color) +{ + QPixmap p = QPixmap(32, 32); + p.fill(QColor(color)); + return p; +} +QPixmap icon(const int number) +{ + QPixmap p = icon(Qt::white); + QPainter painter(&p); + QFont font = painter.font(); + font.setBold(true); + font.setPixelSize(28); + painter.setFont(font); + painter.drawText(QRect(QPoint(0, 0), p.size()), Qt::AlignVCenter | Qt::AlignHCenter, + QString::number(number)); + painter.end(); + return p; +} +QStandardItem *createItem(const Qt::GlobalColor color, const QString &text, + const QString &category) +{ + QStandardItem *item = new QStandardItem; + item->setText(text); + item->setData(icon(color), Qt::DecorationRole); + item->setData(category, GroupViewRoles::GroupRole); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + // progresser->addTrackedIndex(item); + return item; +} +QStandardItem *createItem(const int index, const QString &category) +{ + QStandardItem *item = new QStandardItem; + item->setText(QString("Item #%1").arg(index)); + item->setData(icon(index), Qt::DecorationRole); + item->setData(category, GroupViewRoles::GroupRole); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + // progresser->addTrackedIndex(item); + return item; +} + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + qsrand(QTime::currentTime().msec()); + + progresser = new Progresser(); + + QStandardItemModel model; + model.setRowCount(10); + model.setColumnCount(1); + + model.setItem( + 0, createItem(Qt::red, + "Red is a color. Some more text. I'm out of ideas. 42. What's your name?", + "Colorful")); + model.setItem(1, createItem(Qt::blue, "Blue", "Colorful")); + model.setItem(2, createItem(Qt::yellow, "Yellow", "Colorful")); + + model.setItem(3, createItem(Qt::black, "Black", "Not Colorful")); + model.setItem(4, createItem(Qt::darkGray, "Dark Gray", "Not Colorful")); + model.setItem(5, createItem(Qt::gray, "Gray", "Not Colorful")); + model.setItem(6, createItem(Qt::lightGray, "Light Gray", "Not Colorful")); + model.setItem(7, createItem(Qt::white, "White", "Not Colorful")); + + model.setItem(8, createItem(Qt::darkGreen, "Dark Green", "")); + model.setItem(9, progresser->addTrackedIndex(createItem(Qt::green, "Green", ""))); + + for (int i = 0; i < 20; ++i) + { + model.setItem(i + 10, createItem(i + 1, "Items 1-20")); + } + + GroupedProxyModel pModel; + pModel.setSourceModel(&model); + + GroupView w; + w.setItemDelegate(new ListViewDelegate); + w.setModel(&pModel); + w.resize(640, 480); + w.show(); + + return a.exec(); +} diff --git a/depends/groupview/main.h b/depends/groupview/main.h new file mode 100644 index 00000000..2a358329 --- /dev/null +++ b/depends/groupview/main.h @@ -0,0 +1,54 @@ +#pragma once + +#include <QObject> +#include <QTimer> +#include <QList> +#include <QStandardItem> +#include <QDebug> + +#include "GroupView.h" + +class Progresser : public QObject +{ + Q_OBJECT +public: + explicit Progresser(QObject *parent = 0) : QObject(parent) + { + QTimer *timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); + timer->start(50); + } + + QStandardItem *addTrackedIndex(QStandardItem *item) + { + item->setData(1000, GroupViewRoles::ProgressMaximumRole); + m_items.append(item); + return item; + } + +public +slots: + void timeout() + { + QList<QStandardItem *> toRemove; + for (auto item : m_items) + { + int maximum = item->data(GroupViewRoles::ProgressMaximumRole).toInt(); + int value = item->data(GroupViewRoles::ProgressValueRole).toInt(); + int newvalue = std::min(value + 3, maximum); + item->setData(newvalue, GroupViewRoles::ProgressValueRole); + + if(newvalue >= maximum) + { + toRemove.append(item); + } + } + for(auto remove : toRemove) + { + m_items.removeAll(remove); + } + } + +private: + QList<QStandardItem *> m_items; +}; diff --git a/depends/javacheck/.gitignore b/depends/javacheck/.gitignore new file mode 100644 index 00000000..cc1c52bf --- /dev/null +++ b/depends/javacheck/.gitignore @@ -0,0 +1,6 @@ +.idea +*.iml +out +.classpath +.idea +.project diff --git a/depends/javacheck/CMakeLists.txt b/depends/javacheck/CMakeLists.txt new file mode 100644 index 00000000..10b9a716 --- /dev/null +++ b/depends/javacheck/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 2.8.6) +project(launcher Java) +find_package(Java 1.6 REQUIRED COMPONENTS Development) + +include(UseJava) +set(CMAKE_JAVA_JAR_ENTRY_POINT JavaCheck) +set(CMAKE_JAVA_COMPILE_FLAGS -target 1.6 -source 1.6 -Xlint:deprecation -Xlint:unchecked) + +set(SRC + JavaCheck.java +) + +add_jar(JavaCheck ${SRC}) + +INSTALL_JAR(JavaCheck "${BINARY_DEST_DIR}/jars") diff --git a/depends/javacheck/JavaCheck.java b/depends/javacheck/JavaCheck.java new file mode 100644 index 00000000..11420b86 --- /dev/null +++ b/depends/javacheck/JavaCheck.java @@ -0,0 +1,24 @@ +import java.lang.Integer; + +public class JavaCheck +{ + private static final String[] keys = {"os.arch", "java.version"}; + public static void main (String [] args) + { + int ret = 0; + for(String key : keys) + { + String property = System.getProperty(key); + if(property != null) + { + System.out.println(key + "=" + property); + } + else + { + ret = 1; + } + } + + System.exit(ret); + } +} diff --git a/depends/launcher/.gitignore b/depends/launcher/.gitignore new file mode 100644 index 00000000..cc1c52bf --- /dev/null +++ b/depends/launcher/.gitignore @@ -0,0 +1,6 @@ +.idea +*.iml +out +.classpath +.idea +.project diff --git a/depends/launcher/CMakeLists.txt b/depends/launcher/CMakeLists.txt new file mode 100644 index 00000000..6af5f738 --- /dev/null +++ b/depends/launcher/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 2.8.6) +project(launcher Java) +find_package(Java 1.6 REQUIRED COMPONENTS Development) + +include(UseJava) +set(CMAKE_JAVA_JAR_ENTRY_POINT org.multimc.EntryPoint) +set(CMAKE_JAVA_COMPILE_FLAGS -target 1.6 -source 1.6 -Xlint:deprecation -Xlint:unchecked) + +set(SRC + # OSX things + org/simplericity/macify/eawt/Application.java + org/simplericity/macify/eawt/ApplicationAdapter.java + org/simplericity/macify/eawt/ApplicationEvent.java + org/simplericity/macify/eawt/ApplicationListener.java + org/simplericity/macify/eawt/DefaultApplication.java + + # legacy applet wrapper thing. + # The launcher has to be there for silly FML/Forge relauncher. + net/minecraft/Launcher.java + org/multimc/legacy/LegacyLauncher.java + org/multimc/legacy/LegacyFrame.java + + # onesix launcher + org/multimc/onesix/OneSixLauncher.java + + # generic launcher + org/multimc/EntryPoint.java + org/multimc/Launcher.java + org/multimc/ParseException.java + org/multimc/Utils.java + org/multimc/IconLoader.java +) +add_jar(NewLaunch ${SRC}) + +INSTALL_JAR(NewLaunch "${BINARY_DEST_DIR}/jars") diff --git a/depends/launcher/net/minecraft/Launcher.java b/depends/launcher/net/minecraft/Launcher.java new file mode 100644 index 00000000..c9b137e1 --- /dev/null +++ b/depends/launcher/net/minecraft/Launcher.java @@ -0,0 +1,154 @@ +/* + * Copyright 2012-2014 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. + */ + +package net.minecraft; + +import java.util.TreeMap; +import java.util.Map; +import java.net.URL; +import java.awt.Dimension; +import java.awt.BorderLayout; +import java.awt.Graphics; +import java.applet.Applet; +import java.applet.AppletStub; + +public class Launcher extends Applet implements AppletStub +{ + private Applet wrappedApplet; + private URL documentBase; + private boolean active = false; + private final Map<String, String> params; + + public Launcher(Applet applet, URL documentBase) + { + params = new TreeMap<String, String>(); + + this.setLayout(new BorderLayout()); + this.add(applet, "Center"); + this.wrappedApplet = applet; + this.documentBase = documentBase; + } + + public void setParameter(String name, String value) + { + params.put(name, value); + } + + public void replace(Applet applet) + { + this.wrappedApplet = applet; + + applet.setStub(this); + applet.setSize(getWidth(), getHeight()); + + this.setLayout(new BorderLayout()); + this.add(applet, "Center"); + + applet.init(); + active = true; + applet.start(); + validate(); + } + + @Override + public String getParameter(String name) + { + String param = params.get(name); + if (param != null) + return param; + try + { + return super.getParameter(name); + } catch (Exception ignore){} + return null; + } + + @Override + public boolean isActive() + { + return active; + } + + @Override + public void appletResize(int width, int height) + { + wrappedApplet.resize(width, height); + } + + @Override + public void resize(int width, int height) + { + wrappedApplet.resize(width, height); + } + + @Override + public void resize(Dimension d) + { + wrappedApplet.resize(d); + } + + @Override + public void init() + { + if (wrappedApplet != null) + { + wrappedApplet.init(); + } + } + + @Override + public void start() + { + wrappedApplet.start(); + active = true; + } + + @Override + public void stop() + { + wrappedApplet.stop(); + active = false; + } + + public void destroy() + { + wrappedApplet.destroy(); + } + + @Override + public URL getCodeBase() { + return wrappedApplet.getCodeBase(); + } + + @Override + public URL getDocumentBase() + { + return documentBase; + } + + @Override + public void setVisible(boolean b) + { + super.setVisible(b); + wrappedApplet.setVisible(b); + } + public void update(Graphics paramGraphics) + { + } + public void paint(Graphics paramGraphics) + { + } +}
\ No newline at end of file diff --git a/depends/launcher/org/multimc/EntryPoint.java b/depends/launcher/org/multimc/EntryPoint.java new file mode 100644 index 00000000..e2721ffa --- /dev/null +++ b/depends/launcher/org/multimc/EntryPoint.java @@ -0,0 +1,147 @@ +package org.multimc;/* + * Copyright 2012-2014 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. + */ + +import org.multimc.legacy.LegacyLauncher; +import org.multimc.onesix.OneSixLauncher; +import org.simplericity.macify.eawt.Application; +import org.simplericity.macify.eawt.DefaultApplication; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.*; +import java.nio.charset.Charset; + +public class EntryPoint +{ + private enum Action + { + Proceed, + Launch + } + + public static void main(String[] args) + { + // Set the OSX application icon first, if we are on OSX. + Application application = new DefaultApplication(); + if(application.isMac()) + { + try + { + BufferedImage image = ImageIO.read(new File("icon.png")); + application.setApplicationIconImage(image); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + EntryPoint listener = new EntryPoint(); + int retCode = listener.listen(); + if (retCode != 0) + { + System.out.println("Exiting with " + retCode); + System.exit(retCode); + } + } + + private Action parseLine(String inData) throws ParseException + { + String[] pair = inData.split(" ", 2); + if(pair.length != 2) + throw new ParseException(); + + String command = pair[0]; + String param = pair[1]; + + if(command.equals("launch")) + { + if(param.equals("legacy")) + { + m_launcher = new LegacyLauncher(); + Utils.log("Using legacy launcher."); + Utils.log(); + return Action.Launch; + } + if(param.equals("onesix")) + { + m_launcher = new OneSixLauncher(); + Utils.log("Using onesix launcher."); + Utils.log(); + return Action.Launch; + } + else + throw new ParseException(); + } + + m_params.add(command, param); + //System.out.println(command + " : " + param); + return Action.Proceed; + } + + public int listen() + { + BufferedReader buffer; + try + { + buffer = new BufferedReader(new InputStreamReader(System.in, "UTF-8")); + } catch (UnsupportedEncodingException e) + { + System.err.println("For some reason, your java does not support UTF-8. Consider living in the current century."); + e.printStackTrace(); + return 1; + } + boolean isListening = true; + // Main loop + while (isListening) + { + String inData; + try + { + // Read from the pipe one line at a time + inData = buffer.readLine(); + if (inData != null) + { + if(parseLine(inData) == Action.Launch) + { + isListening = false; + } + } + } + catch (IOException e) + { + System.err.println("Launcher ABORT due to IO exception:"); + e.printStackTrace(); + return 1; + } + catch (ParseException e) + { + System.err.println("Launcher ABORT due to PARSE exception:"); + e.printStackTrace(); + return 1; + } + } + if(m_launcher != null) + { + return m_launcher.launch(m_params); + } + System.err.println("No valid launcher implementation specified."); + return 1; + } + + private ParamBucket m_params = new ParamBucket(); + private org.multimc.Launcher m_launcher; +} diff --git a/depends/launcher/org/multimc/IconLoader.java b/depends/launcher/org/multimc/IconLoader.java new file mode 100644 index 00000000..f1638f3a --- /dev/null +++ b/depends/launcher/org/multimc/IconLoader.java @@ -0,0 +1,132 @@ +package org.multimc; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; + +/***************************************************************************** + * A convenience class for loading icons from images. + * + * Icons loaded from this class are formatted to fit within the required + * dimension (16x16, 32x32, or 128x128). If the source image is larger than the + * target dimension, it is shrunk down to the minimum size that will fit. If it + * is smaller, then it is only scaled up if the new scale can be a per-pixel + * linear scale (i.e., x2, x3, x4, etc). In both cases, the image's width/height + * ratio is kept the same as the source image. + * + * @author Chris Molini + *****************************************************************************/ +public class IconLoader +{ + /************************************************************************* + * Loads an icon in ByteBuffer form. + * + * @param filepath + * The location of the Image to use as an icon. + * + * @return An array of ByteBuffers containing the pixel data for the icon in + * various sizes (as recommended by the OS). + *************************************************************************/ + public static ByteBuffer[] load(String filepath) + { + BufferedImage image; + try { + image = ImageIO.read ( new File( filepath ) ); + } catch ( IOException e ) { + e.printStackTrace(); + return new ByteBuffer[0]; + } + ByteBuffer[] buffers; + buffers = new ByteBuffer[1]; + buffers[0] = loadInstance(image, 128); + return buffers; + } + + /************************************************************************* + * Copies the supplied image into a square icon at the indicated size. + * + * @param image + * The image to place onto the icon. + * @param dimension + * The desired size of the icon. + * + * @return A ByteBuffer of pixel data at the indicated size. + *************************************************************************/ + private static ByteBuffer loadInstance(BufferedImage image, int dimension) + { + BufferedImage scaledIcon = new BufferedImage(dimension, dimension, + BufferedImage.TYPE_INT_ARGB_PRE); + Graphics2D g = scaledIcon.createGraphics(); + double ratio = getIconRatio(image, scaledIcon); + double width = image.getWidth() * ratio; + double height = image.getHeight() * ratio; + g.drawImage(image, (int) ((scaledIcon.getWidth() - width) / 2), + (int) ((scaledIcon.getHeight() - height) / 2), (int) (width), + (int) (height), null); + g.dispose(); + + return convertToByteBuffer(scaledIcon); + } + + /************************************************************************* + * Gets the width/height ratio of the icon. This is meant to simplify + * scaling the icon to a new dimension. + * + * @param src + * The base image that will be placed onto the icon. + * @param icon + * The icon that will have the image placed on it. + * + * @return The amount to scale the source image to fit it onto the icon + * appropriately. + *************************************************************************/ + private static double getIconRatio(BufferedImage src, BufferedImage icon) + { + double ratio = 1; + if (src.getWidth() > icon.getWidth()) + ratio = (double) (icon.getWidth()) / src.getWidth(); + else + ratio = (int) (icon.getWidth() / src.getWidth()); + if (src.getHeight() > icon.getHeight()) + { + double r2 = (double) (icon.getHeight()) / src.getHeight(); + if (r2 < ratio) + ratio = r2; + } + else + { + double r2 = (int) (icon.getHeight() / src.getHeight()); + if (r2 < ratio) + ratio = r2; + } + return ratio; + } + + /************************************************************************* + * Converts a BufferedImage into a ByteBuffer of pixel data. + * + * @param image + * The image to convert. + * + * @return A ByteBuffer that contains the pixel data of the supplied image. + *************************************************************************/ + public static ByteBuffer convertToByteBuffer(BufferedImage image) + { + byte[] buffer = new byte[image.getWidth() * image.getHeight() * 4]; + int counter = 0; + for (int i = 0; i < image.getHeight(); i++) + for (int j = 0; j < image.getWidth(); j++) + { + int colorSpace = image.getRGB(j, i); + buffer[counter + 0] = (byte) ((colorSpace << 8) >> 24); + buffer[counter + 1] = (byte) ((colorSpace << 16) >> 24); + buffer[counter + 2] = (byte) ((colorSpace << 24) >> 24); + buffer[counter + 3] = (byte) (colorSpace >> 24); + counter += 4; + } + return ByteBuffer.wrap(buffer); + } +}
\ No newline at end of file diff --git a/depends/launcher/org/multimc/Launcher.java b/depends/launcher/org/multimc/Launcher.java new file mode 100644 index 00000000..1aa2b21f --- /dev/null +++ b/depends/launcher/org/multimc/Launcher.java @@ -0,0 +1,22 @@ +/* + * Copyright 2012-2014 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. + */ + +package org.multimc; + +public interface Launcher +{ + abstract int launch(ParamBucket params); +} diff --git a/depends/launcher/org/multimc/NotFoundException.java b/depends/launcher/org/multimc/NotFoundException.java new file mode 100644 index 00000000..fe154a2f --- /dev/null +++ b/depends/launcher/org/multimc/NotFoundException.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2014 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. + */ + +package org.multimc; + +public class NotFoundException extends Exception +{ +} diff --git a/depends/launcher/org/multimc/ParamBucket.java b/depends/launcher/org/multimc/ParamBucket.java new file mode 100644 index 00000000..2e197d9f --- /dev/null +++ b/depends/launcher/org/multimc/ParamBucket.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012-2014 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. + */ + +package org.multimc; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class ParamBucket +{ + public void add(String key, String value) + { + List<String> coll = null; + if(!m_params.containsKey(key)) + { + coll = new ArrayList<String>(); + m_params.put(key, coll); + } + else + { + coll = m_params.get(key); + } + coll.add(value); + } + + public List<String> all(String key) throws NotFoundException + { + if(!m_params.containsKey(key)) + throw new NotFoundException(); + return m_params.get(key); + } + + public List<String> allSafe(String key, List<String> def) + { + if(!m_params.containsKey(key) || m_params.get(key).size() < 1) + { + return def; + } + return m_params.get(key); + } + + public List<String> allSafe(String key) + { + return allSafe(key, new ArrayList<String>()); + } + + public String first(String key) throws NotFoundException + { + List<String> list = all(key); + if(list.size() < 1) + { + throw new NotFoundException(); + } + return list.get(0); + } + + public String firstSafe(String key, String def) + { + if(!m_params.containsKey(key) || m_params.get(key).size() < 1) + { + return def; + } + return m_params.get(key).get(0); + } + + public String firstSafe(String key) + { + return firstSafe(key, ""); + } + + private HashMap<String, List<String>> m_params = new HashMap<String, List<String>>(); +} diff --git a/depends/launcher/org/multimc/ParseException.java b/depends/launcher/org/multimc/ParseException.java new file mode 100644 index 00000000..d9e8e53e --- /dev/null +++ b/depends/launcher/org/multimc/ParseException.java @@ -0,0 +1,22 @@ +/* + * Copyright 2012-2014 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. + */ + +package org.multimc; + +public class ParseException extends java.lang.Exception +{ + +} diff --git a/depends/launcher/org/multimc/Utils.java b/depends/launcher/org/multimc/Utils.java new file mode 100644 index 00000000..f3bac91d --- /dev/null +++ b/depends/launcher/org/multimc/Utils.java @@ -0,0 +1,242 @@ +/* + * Copyright 2012-2014 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. + */ + +package org.multimc; + +import java.io.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class Utils +{ + /** + * Combine two parts of a path. + * + * @param path1 + * @param path2 + * @return the paths, combined + */ + public static String combine(String path1, String path2) + { + File file1 = new File(path1); + File file2 = new File(file1, path2); + return file2.getPath(); + } + + /** + * Join a list of strings into a string using a separator! + * + * @param strings the string list to join + * @param separator the glue + * @return the result. + */ + public static String join(List<String> strings, String separator) + { + StringBuilder sb = new StringBuilder(); + String sep = ""; + for (String s : strings) + { + sb.append(sep).append(s); + sep = separator; + } + return sb.toString(); + } + + /** + * Adds the specified library to the classpath + * + * @param s the path to add + * @throws Exception + */ + public static void addToClassPath(String s) throws Exception + { + File f = new File(s); + URL u = f.toURI().toURL(); + URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); + Class urlClass = URLClassLoader.class; + Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class}); + method.setAccessible(true); + method.invoke(urlClassLoader, new Object[]{u}); + } + + /** + * Adds many libraries to the classpath + * + * @param jars the paths to add + */ + public static boolean addToClassPath(List<String> jars) + { + boolean pure = true; + // initialize the class path + for (String jar : jars) + { + try + { + Utils.addToClassPath(jar); + } catch (Exception e) + { + System.err.println("Unable to load: " + jar); + e.printStackTrace(System.err); + pure = false; + } + } + return pure; + } + + /** + * Adds the specified path to the java library path + * + * @param pathToAdd the path to add + * @throws Exception + */ + @Deprecated + public static void addLibraryPath(String pathToAdd) throws Exception + { + final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths"); + usrPathsField.setAccessible(true); + + //get array of paths + final String[] paths = (String[]) usrPathsField.get(null); + + //check if the path to add is already present + for (String path : paths) + { + if (path.equals(pathToAdd)) + { + return; + } + } + + //add the new path + final String[] newPaths = Arrays.copyOf(paths, paths.length + 1); + newPaths[newPaths.length - 1] = pathToAdd; + usrPathsField.set(null, newPaths); + } + + /** + * Finds a field that looks like a Minecraft base folder in a supplied class + * + * @param mc the class to scan + */ + public static Field getMCPathField(Class<?> mc) + { + Field[] fields = mc.getDeclaredFields(); + + for (Field f : fields) + { + if (f.getType() != File.class) + { + // Has to be File + continue; + } + if (f.getModifiers() != (Modifier.PRIVATE + Modifier.STATIC)) + { + // And Private Static. + continue; + } + return f; + } + return null; + } + + /** + * Log to the MultiMC console + * + * @param message A String containing the message + * @param level A String containing the level name. See MinecraftProcess::getLevel() + */ + public static void log(String message, String level) + { + // Kinda dirty + String tag = "!![" + level + "]!"; + System.out.println(tag + message.replace("\n", "\n" + tag)); + } + + public static void log(String message) + { + log(message, "MultiMC"); + } + + public static void log() + { + System.out.println(); + } + + /** + * Pushes bytes from in to out. Closes both streams no matter what. + * @param in the input stream + * @param out the output stream + * @throws IOException + */ + private static void copyStream(InputStream in, OutputStream out) throws IOException + { + try + { + byte[] buffer = new byte[4096]; + int len; + + while((len = in.read(buffer)) >= 0) + out.write(buffer, 0, len); + } finally + { + in.close(); + out.close(); + } + } + + /** + * Unzip zip file 'source' into the folder 'targetFolder' + * @param source + * @param targetFolder + * @throws IOException + */ + public static void unzip(File source, File targetFolder) throws IOException + { + ZipFile zip = new ZipFile(source); + try + { + Enumeration entries = zip.entries(); + + while (entries.hasMoreElements()) + { + ZipEntry entry = (ZipEntry) entries.nextElement(); + + File targetFile = new File(targetFolder, entry.getName()); + if (targetFile.getParentFile() != null) + { + targetFile.getParentFile().mkdirs(); + } + + if (entry.isDirectory()) + continue; + + copyStream(zip.getInputStream(entry), new BufferedOutputStream(new FileOutputStream(targetFile))); + } + } finally + { + zip.close(); + } + } +} + diff --git a/depends/launcher/org/multimc/legacy/LegacyFrame.java b/depends/launcher/org/multimc/legacy/LegacyFrame.java new file mode 100644 index 00000000..c3c0cafc --- /dev/null +++ b/depends/launcher/org/multimc/legacy/LegacyFrame.java @@ -0,0 +1,112 @@ +package org.multimc.legacy;/* + * Copyright 2012-2014 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. + */ + +import net.minecraft.Launcher; + +import javax.imageio.ImageIO; +import java.applet.Applet; +import java.awt.*; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +public class LegacyFrame extends Frame implements WindowListener +{ + private Launcher appletWrap = null; + public LegacyFrame(String title) + { + super ( title ); + BufferedImage image; + try { + image = ImageIO.read ( new File ( "icon.png" ) ); + setIconImage ( image ); + } catch ( IOException e ) { + e.printStackTrace(); + } + this.addWindowListener ( this ); + } + + public void start ( Applet mcApplet, String user, String session, Dimension winSize, boolean maximize ) + { + try { + appletWrap = new Launcher( mcApplet, new URL ( "http://www.minecraft.net/game" ) ); + } catch ( MalformedURLException ignored ) {} + appletWrap.setParameter ( "username", user ); + appletWrap.setParameter ( "sessionid", session ); + appletWrap.setParameter ( "stand-alone", "true" ); // Show the quit button. + appletWrap.setParameter ( "demo", "false" ); + appletWrap.setParameter("fullscreen", "false"); + mcApplet.setStub(appletWrap); + this.add ( appletWrap ); + appletWrap.setPreferredSize ( winSize ); + this.pack(); + this.setLocationRelativeTo ( null ); + this.setResizable ( true ); + if ( maximize ) { + this.setExtendedState ( MAXIMIZED_BOTH ); + } + validate(); + appletWrap.init(); + appletWrap.start(); + setVisible ( true ); + } + + @Override + public void windowActivated ( WindowEvent e ) {} + + @Override + public void windowClosed ( WindowEvent e ) {} + + @Override + public void windowClosing ( WindowEvent e ) + { + new Thread() { + public void run() { + try { + Thread.sleep ( 30000L ); + } catch ( InterruptedException localInterruptedException ) { + localInterruptedException.printStackTrace(); + } + System.out.println ( "FORCING EXIT!" ); + System.exit ( 0 ); + } + } + .start(); + + if ( appletWrap != null ) { + appletWrap.stop(); + appletWrap.destroy(); + } + // old minecraft versions can hang without this >_< + System.exit ( 0 ); + } + + @Override + public void windowDeactivated ( WindowEvent e ) {} + + @Override + public void windowDeiconified ( WindowEvent e ) {} + + @Override + public void windowIconified ( WindowEvent e ) {} + + @Override + public void windowOpened ( WindowEvent e ) {} +} diff --git a/depends/launcher/org/multimc/legacy/LegacyLauncher.java b/depends/launcher/org/multimc/legacy/LegacyLauncher.java new file mode 100644 index 00000000..1ca37c4a --- /dev/null +++ b/depends/launcher/org/multimc/legacy/LegacyLauncher.java @@ -0,0 +1,178 @@ +package org.multimc.legacy;/* + * Copyright 2012-2014 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. + */ + +import org.multimc.Launcher; +import org.multimc.NotFoundException; +import org.multimc.ParamBucket; +import org.multimc.Utils; + +import java.applet.Applet; +import java.awt.*; +import java.io.File; +import java.lang.reflect.Field; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; + +public class LegacyLauncher implements Launcher +{ + @Override + public int launch(ParamBucket params) + { + String userName, sessionId, windowTitle, windowParams, lwjgl; + String mainClass = "net.minecraft.client.Minecraft"; + try + { + userName = params.first("userName"); + sessionId = params.first("sessionId"); + windowTitle = params.first("windowTitle"); + windowParams = params.first("windowParams"); + lwjgl = params.first("lwjgl"); + } catch (NotFoundException e) + { + System.err.println("Not enough arguments."); + return -1; + } + + String cwd = System.getProperty("user.dir"); + Dimension winSize = new Dimension(854, 480); + boolean maximize = false; + + String[] dimStrings = windowParams.split("x"); + + if (windowParams.equalsIgnoreCase("max")) + { + maximize = true; + } + else if (dimStrings.length == 2) + { + try + { + winSize = new Dimension(Integer.parseInt(dimStrings[0]), Integer.parseInt(dimStrings[1])); + } catch (NumberFormatException ignored) {} + } + + File binDir = new File(cwd, "bin"); + File lwjglDir; + if (lwjgl.equalsIgnoreCase("Mojang")) + { + lwjglDir = binDir; + } + else + { + lwjglDir = new File(lwjgl); + } + + URL[] classpath; + { + try + { + classpath = new URL[] + { + new File(binDir, "minecraft.jar").toURI().toURL(), + new File(lwjglDir, "lwjgl.jar").toURI().toURL(), + new File(lwjglDir, "lwjgl_util.jar").toURI().toURL(), + new File(lwjglDir, "jinput.jar").toURI().toURL(), + }; + } catch (MalformedURLException e) + { + System.err.println("Class path entry is badly formed:"); + e.printStackTrace(System.err); + return -1; + } + } + + String nativesDir = new File(lwjglDir, "natives").toString(); + + System.setProperty("org.lwjgl.librarypath", nativesDir); + System.setProperty("net.java.games.input.librarypath", nativesDir); + + // print the pretty things + { + Utils.log("Main Class:"); + Utils.log(" " + mainClass); + Utils.log(); + + Utils.log("Class Path:"); + for (URL s : classpath) + { + Utils.log(" " + s); + } + Utils.log(); + + Utils.log("Native Path:"); + Utils.log(" " + nativesDir); + Utils.log(); + } + + URLClassLoader cl = new URLClassLoader(classpath, LegacyLauncher.class.getClassLoader()); + + // Get the Minecraft Class and set the base folder + Class<?> mc; + try + { + mc = cl.loadClass(mainClass); + + Field f = Utils.getMCPathField(mc); + + if (f == null) + { + System.err.println("Could not find Minecraft path field. Launch failed."); + return -1; + } + + f.setAccessible(true); + f.set(null, new File(cwd)); + } catch (Exception e) + { + System.err.println("Could not set base folder. Failed to find/access Minecraft main class:"); + e.printStackTrace(System.err); + return -1; + } + + System.setProperty("minecraft.applet.TargetDirectory", cwd); + + String[] mcArgs = new String[2]; + mcArgs[0] = userName; + mcArgs[1] = sessionId; + + Utils.log("Launching with applet wrapper..."); + try + { + Class<?> MCAppletClass = cl.loadClass("net.minecraft.client.MinecraftApplet"); + Applet mcappl = (Applet) MCAppletClass.newInstance(); + LegacyFrame mcWindow = new LegacyFrame(windowTitle); + mcWindow.start(mcappl, userName, sessionId, winSize, maximize); + } catch (Exception e) + { + Utils.log("Applet wrapper failed:", "Error"); + e.printStackTrace(System.err); + Utils.log(); + Utils.log("Falling back to compatibility mode."); + try + { + mc.getMethod("main", String[].class).invoke(null, (Object) mcArgs); + } catch (Exception e1) + { + Utils.log("Failed to invoke the Minecraft main class:", "Fatal"); + e1.printStackTrace(System.err); + return -1; + } + } + + return 0; + } +} diff --git a/depends/launcher/org/multimc/onesix/OneSixLauncher.java b/depends/launcher/org/multimc/onesix/OneSixLauncher.java new file mode 100644 index 00000000..28f8e6ee --- /dev/null +++ b/depends/launcher/org/multimc/onesix/OneSixLauncher.java @@ -0,0 +1,223 @@ +/* Copyright 2012-2014 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. + */ + +package org.multimc.onesix; + +import org.multimc.*; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; + +public class OneSixLauncher implements Launcher +{ + @Override + public int launch(ParamBucket params) + { + // get and process the launch script params + List<String> libraries; + List<String> extlibs; + List<String> mcparams; + List<String> mods; + String mainClass; + String natives; + final String windowTitle; + String windowParams; + try + { + libraries = params.all("cp"); + extlibs = params.all("ext"); + mcparams = params.all("param"); + mainClass = params.first("mainClass"); + mods = params.allSafe("mods", new ArrayList<String>()); + natives = params.first("natives"); + + windowTitle = params.first("windowTitle"); + // windowParams = params.first("windowParams"); + } catch (NotFoundException e) + { + System.err.println("Not enough arguments."); + e.printStackTrace(System.err); + return -1; + } + + List<String> allJars = new ArrayList<String>(); + allJars.addAll(mods); + allJars.addAll(libraries); + + if(!Utils.addToClassPath(allJars)) + { + System.err.println("Halting launch due to previous errors."); + return -1; + } + + // print the pretty things + { + Utils.log("Main Class:"); + Utils.log(" " + mainClass); + Utils.log(); + + Utils.log("Native path:"); + Utils.log(" " + natives); + Utils.log(); + + Utils.log("Libraries:"); + for (String s : libraries) + { + Utils.log(" " + s); + } + Utils.log(); + + if(mods.size() > 0) + { + Utils.log("Class Path Mods:"); + for (String s : mods) + { + Utils.log(" " + s); + } + Utils.log(); + } + + Utils.log("Params:"); + Utils.log(" " + mcparams.toString()); + Utils.log(); + } + + // set up the natives path(s). + Utils.log("Preparing native libraries..."); + String property = System.getProperty("os.arch"); + boolean is_64 = property.equalsIgnoreCase("x86_64") || property.equalsIgnoreCase("amd64"); + for(String extlib: extlibs) + { + try + { + String cleanlib = extlib.replace("${arch}", is_64 ? "64" : "32"); + File cleanlibf = new File(cleanlib); + Utils.log("Extracting " + cleanlibf.getName()); + Utils.unzip(cleanlibf, new File(natives)); + } catch (IOException e) + { + System.err.println("Failed to extract native library:"); + e.printStackTrace(System.err); + return -1; + } + } + Utils.log(); + + System.setProperty("java.library.path", natives); + Field fieldSysPath; + try + { + fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths"); + fieldSysPath.setAccessible( true ); + fieldSysPath.set( null, null ); + } catch (Exception e) + { + System.err.println("Failed to set the native library path:"); + e.printStackTrace(System.err); + return -1; + } + + // Get the Minecraft Class. + final ClassLoader cl = ClassLoader.getSystemClassLoader(); + Class<?> mc; + try + { + mc = cl.loadClass(mainClass); + } catch (ClassNotFoundException e) + { + System.err.println("Failed to find Minecraft main class:"); + e.printStackTrace(System.err); + return -1; + } + + // get the main method. + Method meth; + try + { + meth = mc.getMethod("main", String[].class); + } catch (NoSuchMethodException e) + { + System.err.println("Failed to acquire the main method:"); + e.printStackTrace(System.err); + return -1; + } + + // FIXME: works only on linux, we need a better solution +/* + final java.nio.ByteBuffer[] icons = IconLoader.load("icon.png"); + new Thread() { + public void run() { + ClassLoader cl = ClassLoader.getSystemClassLoader(); + try + { + Class<?> Display; + Method isCreated; + Method setTitle; + Method setIcon; + + Display = cl.loadClass("org.lwjgl.opengl.Display"); + isCreated = Display.getMethod("isCreated"); + setTitle = Display.getMethod("setTitle", String.class); + setIcon = Display.getMethod("setIcon", java.nio.ByteBuffer[].class); + + // set the window title? Maybe? + while(!(Boolean) isCreated.invoke(null)) + { + try + { + Thread.sleep(150); + } catch (InterruptedException ignored) {} + } + // Give it a bit more time ;) + Thread.sleep(150); + // set the title + setTitle.invoke(null,windowTitle); + // only set icon when there's actually something to set... + if(icons.length > 0) + { + setIcon.invoke(null,(Object)icons); + } + } + catch (Exception e) + { + System.err.println("Couldn't set window icon or title."); + e.printStackTrace(System.err); + } + } + } + .start(); +*/ + // start Minecraft + String[] paramsArray = mcparams.toArray(new String[mcparams.size()]); // init params accordingly + try + { + meth.invoke(null, (Object) paramsArray); // static method doesn't have an instance + } catch (Exception e) + { + System.err.println("Failed to start Minecraft:"); + e.printStackTrace(System.err); + return -1; + } + return 0; + } +} diff --git a/depends/launcher/org/simplericity/macify/eawt/Application.java b/depends/launcher/org/simplericity/macify/eawt/Application.java new file mode 100644 index 00000000..153bb9ee --- /dev/null +++ b/depends/launcher/org/simplericity/macify/eawt/Application.java @@ -0,0 +1,176 @@ +package org.simplericity.macify.eawt; + +/* + * Copyright 2007 Eirik Bjorsnos. + * + * 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. + */ + +import java.awt.*; +import java.awt.image.BufferedImage; + +/** + * The Macify Library API interface provides integration with the OS X platform for Java Applications. + * The API includes a facade to the + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/index.html"> + * Apple Java Extensions API + * </a>. + * Additionally, it provides access to several useful methods in the Cocoa NSApplication API. + * + * The default implementation of this interface is {@link org.simplericity.macify.eawt.DefaultApplication}. + */ +public interface Application { + + static int REQUEST_USER_ATTENTION_TYPE_CRITICAL = 1 ; + static int REQUEST_USER_ATTENTION_TYPE_INFORMATIONAL = 2 ; + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#addAboutMenuItem()"> + * Apple's API + * </a>. + */ + void addAboutMenuItem(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#addApplicationListener(com.apple.eawt.ApplicationListener)"> + * Apple's API + * </a>. + */ + void addApplicationListener(ApplicationListener applicationListener); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#addPreferencesMenuItem()"> + * Apple's API + * </a>. + */ + void addPreferencesMenuItem(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#getEnabledAboutMenu()"> + * Apple's API + * </a>. + */ + boolean getEnabledAboutMenu(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#getEnabledPreferencesMenu()"> + * Apple's API + * </a>. + */ + boolean getEnabledPreferencesMenu(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#isAboutMenuItemPresent()"> + * Apple's API + * </a>. + */ + boolean isAboutMenuItemPresent(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#isPreferencesMenuItemPresent()"> + * Apple's API + * </a>. + */ + boolean isPreferencesMenuItemPresent(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#removeAboutMenuItem()"> + * Apple's API + * </a>. + */ + void removeAboutMenuItem(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#removeApplicationListener(com.apple.eawt.ApplicationListener)"> + * Apple's API + * </a>. + */ + void removeApplicationListener(ApplicationListener applicationListener); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#removePreferencesMenuItem()"> + * Apple's API + * </a>. + */ + void removePreferencesMenuItem(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#getEnabledAboutMenu()"> + * Apple's API + * </a>. + */ + void setEnabledAboutMenu(boolean enabled); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#getEnabledPreferencesMenu()"> + * Apple's API + * </a>. + */ + void setEnabledPreferencesMenu(boolean enabled); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#getMouseLocationOnScreen()"> + * Apple's API + * </a>. + */ + Point getMouseLocationOnScreen(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/index.html#//apple_ref/doc/uid/TP40004004"> + * Apple's NSApplication Class Reference + * </a>. + * @param type on of {@link #REQUEST_USER_ATTENTION_TYPE_CRITICAL} or {@link #REQUEST_USER_ATTENTION_TYPE_INFORMATIONAL}. + */ + int requestUserAttention(int type); + + /** + * See + * <a href="http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/index.html#//apple_ref/doc/uid/TP40004004"> + * Apple's NSApplication Class Reference + * </a> + */ + void cancelUserAttentionRequest(int request); + + /** + * Update the application's icon image + * @param image + */ + void setApplicationIconImage(BufferedImage image); + + /** + * Get the application's icon image. + */ + BufferedImage getApplicationIconImage(); + + /** + * Determines whether the application is running on a Mac AND the Apple Extensions API classes are available. + * @return + */ + boolean isMac(); + + +} diff --git a/depends/launcher/org/simplericity/macify/eawt/ApplicationAdapter.java b/depends/launcher/org/simplericity/macify/eawt/ApplicationAdapter.java new file mode 100644 index 00000000..e9c3db7d --- /dev/null +++ b/depends/launcher/org/simplericity/macify/eawt/ApplicationAdapter.java @@ -0,0 +1,48 @@ +package org.simplericity.macify.eawt; + +/* + * Copyright 2007 Eirik Bjorsnos. + * + * 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. + */ + +public class ApplicationAdapter implements ApplicationListener { + + public void handleQuit(ApplicationEvent event) { + + } + + public void handleAbout(ApplicationEvent event) { + + } + + public void handleOpenApplication(ApplicationEvent event) { + + } + + public void handleOpenFile(ApplicationEvent event) { + + } + + public void handlePreferences(ApplicationEvent event) { + + } + + public void handlePrintFile(ApplicationEvent event) { + + } + + public void handleReOpenApplication(ApplicationEvent event) { + + } +} diff --git a/depends/launcher/org/simplericity/macify/eawt/ApplicationEvent.java b/depends/launcher/org/simplericity/macify/eawt/ApplicationEvent.java new file mode 100644 index 00000000..78420355 --- /dev/null +++ b/depends/launcher/org/simplericity/macify/eawt/ApplicationEvent.java @@ -0,0 +1,25 @@ +package org.simplericity.macify.eawt; + +/* + * Copyright 2007 Eirik Bjorsnos. + * + * 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. + */ + +public interface ApplicationEvent { + String getFilename(); + boolean isHandled(); + void setHandled(boolean handled); + Object getSource(); + String toString(); +} diff --git a/depends/launcher/org/simplericity/macify/eawt/ApplicationListener.java b/depends/launcher/org/simplericity/macify/eawt/ApplicationListener.java new file mode 100644 index 00000000..a291bee4 --- /dev/null +++ b/depends/launcher/org/simplericity/macify/eawt/ApplicationListener.java @@ -0,0 +1,27 @@ +package org.simplericity.macify.eawt; + +/* + * Copyright 2007 Eirik Bjorsnos. + * + * 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. + */ + +public interface ApplicationListener { + void handleAbout(ApplicationEvent event); + void handleOpenApplication(ApplicationEvent event); + void handleOpenFile(ApplicationEvent event); + void handlePreferences(ApplicationEvent event); + void handlePrintFile(ApplicationEvent event); + void handleQuit(ApplicationEvent event); + void handleReOpenApplication(ApplicationEvent event); +} diff --git a/depends/launcher/org/simplericity/macify/eawt/DefaultApplication.java b/depends/launcher/org/simplericity/macify/eawt/DefaultApplication.java new file mode 100644 index 00000000..5752a350 --- /dev/null +++ b/depends/launcher/org/simplericity/macify/eawt/DefaultApplication.java @@ -0,0 +1,418 @@ +package org.simplericity.macify.eawt; + +/* + * Copyright 2007 Eirik Bjorsnos. + * + * 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. + */ + + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.lang.reflect.*; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.MalformedURLException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + + +/** + * Implements Application by calling the Mac OS X API through reflection. + * If this class is used on a non-OS X platform the operations will have no effect or they will simulate + * what the Apple API would do for those who manipulate state. ({@link #setEnabledAboutMenu(boolean)} etc.) + */ +@SuppressWarnings("unchecked") +public class DefaultApplication implements Application { + + private Object application; + private Class applicationListenerClass; + + Map listenerMap = Collections.synchronizedMap(new HashMap<Object, Object>()); + private boolean enabledAboutMenu = true; + private boolean enabledPreferencesMenu; + private boolean aboutMenuItemPresent = true; + private boolean preferencesMenuItemPresent; + private ClassLoader classLoader; + + public DefaultApplication() { + try { + final File file = new File("/System/Library/Java"); + if (file.exists()) { + ClassLoader scl = ClassLoader.getSystemClassLoader(); + Class clc = scl.getClass(); + if (URLClassLoader.class.isAssignableFrom(clc)) { + Method addUrl = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class}); + addUrl.setAccessible(true); + addUrl.invoke(scl, new Object[]{file.toURI().toURL()}); + } + } + + Class appClass = Class.forName("com.apple.eawt.Application"); + application = appClass.getMethod("getApplication", new Class[0]).invoke(null, new Object[0]); + applicationListenerClass = Class.forName("com.apple.eawt.ApplicationListener"); + } catch (ClassNotFoundException e) { + application = null; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + + } + + public boolean isMac() { + return application != null; + } + + public void addAboutMenuItem() { + if (isMac()) { + callMethod(application, "addAboutMenuItem"); + } else { + this.aboutMenuItemPresent = true; + } + } + + public void addApplicationListener(ApplicationListener applicationListener) { + + if (!Modifier.isPublic(applicationListener.getClass().getModifiers())) { + throw new IllegalArgumentException("ApplicationListener must be a public class"); + } + if (isMac()) { + Object listener = Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{applicationListenerClass}, + new ApplicationListenerInvocationHandler(applicationListener)); + + callMethod(application, "addApplicationListener", new Class[]{applicationListenerClass}, new Object[]{listener}); + listenerMap.put(applicationListener, listener); + } else { + listenerMap.put(applicationListener, applicationListener); + } + } + + public void addPreferencesMenuItem() { + if (isMac()) { + callMethod("addPreferencesMenuItem"); + } else { + this.preferencesMenuItemPresent = true; + } + } + + public boolean getEnabledAboutMenu() { + if (isMac()) { + return callMethod("getEnabledAboutMenu").equals(Boolean.TRUE); + } else { + return enabledAboutMenu; + } + } + + public boolean getEnabledPreferencesMenu() { + if (isMac()) { + Object result = callMethod("getEnabledPreferencesMenu"); + return result.equals(Boolean.TRUE); + } else { + return enabledPreferencesMenu; + } + } + + public Point getMouseLocationOnScreen() { + if (isMac()) { + try { + Method method = application.getClass().getMethod("getMouseLocationOnScreen", new Class[0]); + return (Point) method.invoke(null, new Object[0]); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } else { + return new Point(0, 0); + } + } + + public boolean isAboutMenuItemPresent() { + if (isMac()) { + return callMethod("isAboutMenuItemPresent").equals(Boolean.TRUE); + } else { + return aboutMenuItemPresent; + } + } + + public boolean isPreferencesMenuItemPresent() { + if (isMac()) { + return callMethod("isPreferencesMenuItemPresent").equals(Boolean.TRUE); + } else { + return this.preferencesMenuItemPresent; + } + } + + public void removeAboutMenuItem() { + if (isMac()) { + callMethod("removeAboutMenuItem"); + } else { + this.aboutMenuItemPresent = false; + } + } + + public synchronized void removeApplicationListener(ApplicationListener applicationListener) { + if (isMac()) { + Object listener = listenerMap.get(applicationListener); + callMethod(application, "removeApplicationListener", new Class[]{applicationListenerClass}, new Object[]{listener}); + + } + listenerMap.remove(applicationListener); + } + + public void removePreferencesMenuItem() { + if (isMac()) { + callMethod("removeAboutMenuItem"); + } else { + this.preferencesMenuItemPresent = false; + } + } + + public void setEnabledAboutMenu(boolean enabled) { + if (isMac()) { + callMethod(application, "setEnabledAboutMenu", new Class[]{Boolean.TYPE}, new Object[]{Boolean.valueOf(enabled)}); + } else { + this.enabledAboutMenu = enabled; + } + } + + public void setEnabledPreferencesMenu(boolean enabled) { + if (isMac()) { + callMethod(application, "setEnabledPreferencesMenu", new Class[]{Boolean.TYPE}, new Object[]{Boolean.valueOf(enabled)}); + } else { + this.enabledPreferencesMenu = enabled; + } + + } + + public int requestUserAttention(int type) { + if (type != REQUEST_USER_ATTENTION_TYPE_CRITICAL && type != REQUEST_USER_ATTENTION_TYPE_INFORMATIONAL) { + throw new IllegalArgumentException("Requested user attention type is not allowed: " + type); + } + try { + Object application = getNSApplication(); + Field critical = application.getClass().getField("UserAttentionRequestCritical"); + Field informational = application.getClass().getField("UserAttentionRequestInformational"); + Field actual = type == REQUEST_USER_ATTENTION_TYPE_CRITICAL ? critical : informational; + + return ((Integer) application.getClass().getMethod("requestUserAttention", new Class[]{Integer.TYPE}).invoke(application, new Object[]{actual.get(null)})).intValue(); + + } catch (ClassNotFoundException e) { + return -1; + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + + public void cancelUserAttentionRequest(int request) { + try { + Object application = getNSApplication(); + application.getClass().getMethod("cancelUserAttentionRequest", new Class[]{Integer.TYPE}).invoke(application, new Object[]{new Integer(request)}); + } catch (ClassNotFoundException e) { + // Nada + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private Object getNSApplication() throws ClassNotFoundException { + try { + Class applicationClass = Class.forName("com.apple.cocoa.application.NSApplication"); + return applicationClass.getMethod("sharedApplication", new Class[0]).invoke(null, new Object[0]); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + public void setApplicationIconImage(BufferedImage image) { + if (isMac()) { + try { + Method setDockIconImage = application.getClass().getMethod("setDockIconImage", Image.class); + + try { + setDockIconImage.invoke(application, image); + } catch (IllegalAccessException e) { + + } catch (InvocationTargetException e) { + + } + } catch (NoSuchMethodException mnfe) { + + + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + ImageIO.write(image, "png", stream); + } catch (IOException e) { + throw new RuntimeException(e); + } + + try { + Class nsDataClass = Class.forName("com.apple.cocoa.foundation.NSData"); + Constructor constructor = nsDataClass.getConstructor(new Class[]{new byte[0].getClass()}); + + Object nsData = constructor.newInstance(new Object[]{stream.toByteArray()}); + + Class nsImageClass = Class.forName("com.apple.cocoa.application.NSImage"); + Object nsImage = nsImageClass.getConstructor(new Class[]{nsDataClass}).newInstance(new Object[]{nsData}); + + Object application = getNSApplication(); + + application.getClass().getMethod("setApplicationIconImage", new Class[]{nsImageClass}).invoke(application, new Object[]{nsImage}); + + } catch (ClassNotFoundException e) { + + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } + + } + + } + } + + public BufferedImage getApplicationIconImage() { + if (isMac()) { + + try { + Method getDockIconImage = application.getClass().getMethod("getDockIconImage"); + try { + return (BufferedImage) getDockIconImage.invoke(application); + } catch (IllegalAccessException e) { + + } catch (InvocationTargetException e) { + + } + } catch (NoSuchMethodException nsme) { + + try { + Class nsDataClass = Class.forName("com.apple.cocoa.foundation.NSData"); + Class nsImageClass = Class.forName("com.apple.cocoa.application.NSImage"); + Object application = getNSApplication(); + Object nsImage = application.getClass().getMethod("applicationIconImage", new Class[0]).invoke(application, new Object[0]); + + Object nsData = nsImageClass.getMethod("TIFFRepresentation", new Class[0]).invoke(nsImage, new Object[0]); + + Integer length = (Integer) nsDataClass.getMethod("length", new Class[0]).invoke(nsData, new Object[0]); + byte[] bytes = (byte[]) nsDataClass.getMethod("bytes", new Class[]{Integer.TYPE, Integer.TYPE}).invoke(nsData, new Object[]{Integer.valueOf(0), length}); + + BufferedImage image = ImageIO.read(new ByteArrayInputStream(bytes)); + return image; + + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + } + + return null; + } + + private Object callMethod(String methodname) { + return callMethod(application, methodname, new Class[0], new Object[0]); + } + + private Object callMethod(Object object, String methodname) { + return callMethod(object, methodname, new Class[0], new Object[0]); + } + + private Object callMethod(Object object, String methodname, Class[] classes, Object[] arguments) { + try { + if (classes == null) { + classes = new Class[arguments.length]; + for (int i = 0; i < classes.length; i++) { + classes[i] = arguments[i].getClass(); + + } + } + Method addListnerMethod = object.getClass().getMethod(methodname, classes); + return addListnerMethod.invoke(object, arguments); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + class ApplicationListenerInvocationHandler implements InvocationHandler { + private ApplicationListener applicationListener; + + ApplicationListenerInvocationHandler(ApplicationListener applicationListener) { + this.applicationListener = applicationListener; + } + + public Object invoke(Object object, Method appleMethod, Object[] objects) throws Throwable { + + ApplicationEvent event = createApplicationEvent(objects[0]); + try { + Method method = applicationListener.getClass().getMethod(appleMethod.getName(), new Class[]{ApplicationEvent.class}); + return method.invoke(applicationListener, new Object[]{event}); + } catch (NoSuchMethodException e) { + if (appleMethod.getName().equals("equals") && objects.length == 1) { + return Boolean.valueOf(object == objects[0]); + } + return null; + } + } + } + + private ApplicationEvent createApplicationEvent(final Object appleApplicationEvent) { + return (ApplicationEvent) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{ApplicationEvent.class}, new InvocationHandler() { + public Object invoke(Object o, Method method, Object[] objects) throws Throwable { + return appleApplicationEvent.getClass().getMethod(method.getName(), method.getParameterTypes()).invoke(appleApplicationEvent, objects); + } + }); + } +} diff --git a/depends/pack200/CMakeLists.txt b/depends/pack200/CMakeLists.txt new file mode 100644 index 00000000..386c8bb8 --- /dev/null +++ b/depends/pack200/CMakeLists.txt @@ -0,0 +1,59 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) + +IF(WIN32) + # In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows + cmake_policy(SET CMP0020 OLD) +ENDIF() + +project(unpack200) + +# Find ZLIB for quazip +# Use system zlib on unix and Qt ZLIB on Windows +IF(UNIX) + find_package(ZLIB REQUIRED) +ELSE(UNIX) + get_filename_component (ZLIB_FOUND_DIR "${Qt5Core_DIR}/../../../include/QtZlib" ABSOLUTE) + SET(ZLIB_INCLUDE_DIRS ${ZLIB_FOUND_DIR} CACHE PATH "Path to ZLIB headers of Qt") + SET(ZLIB_LIBRARIES "") + IF(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h") + MESSAGE("Please specify a valid zlib include dir") + ENDIF(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h") +ENDIF(UNIX) + +SET(PACK200_SRC +include/unpack200.h +src/bands.cpp +src/bands.h +src/bytes.cpp +src/bytes.h +src/coding.cpp +src/coding.h +src/constants.h +src/defines.h +src/unpack200.cpp +src/unpack.cpp +src/unpack.h +src/utils.cpp +src/utils.h +src/zip.cpp +src/zip.h +) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +SET(PACK200_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE) +include_directories( + include + ${ZLIB_INCLUDE_DIRS} +) +add_library(unpack200 STATIC ${PACK200_SRC}) + +IF(UNIX) + target_link_libraries(unpack200 ${ZLIB_LIBRARIES}) +ELSE() + # zlib is part of Qt on windows. use it. + QT5_USE_MODULES(unpack200 Core) +ENDIF() + +add_executable(anti200 anti200.cpp) +target_link_libraries(anti200 unpack200) diff --git a/depends/pack200/LICENSE b/depends/pack200/LICENSE new file mode 100644 index 00000000..b40a0f45 --- /dev/null +++ b/depends/pack200/LICENSE @@ -0,0 +1,347 @@ +The GNU General Public License (GPL) + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to +most of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software is +covered by the GNU Library General Public License instead.) You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom to +distribute copies of free software (and charge for this service if you wish), +that you receive source code or can get it if you want it, that you can change +the software or use pieces of it in new free programs; and that you know you +can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny +you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for +a fee, you must give the recipients all the rights that you have. You must +make sure that they, too, receive or can get the source code. And you must +show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If the +software is modified by someone else and passed on, we want its recipients to +know that what they have is not the original, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will +individually obtain patent licenses, in effect making the program proprietary. +To prevent this, we have made it clear that any patent must be licensed for +everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms of +this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or +translated into another language. (Hereinafter, translation is included +without limitation in the term "modification".) Each licensee is addressed as +"you". + +Activities other than copying, distribution and modification are not covered by +this License; they are outside its scope. The act of running the Program is +not restricted, and the output from the Program is covered only if its contents +constitute a work based on the Program (independent of having been made by +running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as +you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this License +and to the absence of any warranty; and give any other recipients of the +Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may +at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus +forming a work based on the Program, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all of +these conditions: + + a) You must cause the modified files to carry prominent notices stating + that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in whole or + in part contains or is derived from the Program or any part thereof, to be + licensed as a whole at no charge to all third parties under the terms of + this License. + + c) If the modified program normally reads commands interactively when run, + you must cause it, when started running for such interactive use in the + most ordinary way, to print or display an announcement including an + appropriate copyright notice and a notice that there is no warranty (or + else, saying that you provide a warranty) and that users may redistribute + the program under these conditions, and telling the user how to view a copy + of this License. (Exception: if the Program itself is interactive but does + not normally print such an announcement, your work based on the Program is + not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, and +its terms, do not apply to those sections when you distribute them as separate +works. But when you distribute the same sections as part of a whole which is a +work based on the Program, the distribution of the whole must be on the terms +of this License, whose permissions for other licensees extend to the entire +whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise the +right to control the distribution of derivative or collective works based on +the Program. + +In addition, mere aggregation of another work not based on the Program with the +Program (or with a work based on the Program) on a volume of a storage or +distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 and +2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable source + code, which must be distributed under the terms of Sections 1 and 2 above + on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three years, to + give any third party, for a charge no more than your cost of physically + performing source distribution, a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed only + for noncommercial distribution and only if you received the program in + object code or executable form with such an offer, in accord with + Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all +the source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code +distributed need not include anything that is normally distributed (in either +source or binary form) with the major components (compiler, kernel, and so on) +of the operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the source +code from the same place counts as distribution of the source code, even though +third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, or +rights, from you under this License will not have their licenses terminated so +long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. +However, nothing else grants you permission to modify or distribute the Program +or its derivative works. These actions are prohibited by law if you do not +accept this License. Therefore, by modifying or distributing the Program (or +any work based on the Program), you indicate your acceptance of this License to +do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor to +copy, distribute or modify the Program subject to these terms and conditions. +You may not impose any further restrictions on the recipients' exercise of the +rights granted herein. You are not responsible for enforcing compliance by +third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), conditions +are imposed on you (whether by court order, agreement or otherwise) that +contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot distribute so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not distribute the Program at all. +For example, if a patent license would not permit royalty-free redistribution +of the Program by all those who receive copies directly or indirectly through +you, then the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or +other property right claims or to contest validity of any such claims; this +section has the sole purpose of protecting the integrity of the free software +distribution system, which is implemented by public license practices. Many +people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose that +choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original +copyright holder who places the Program under this License may add an explicit +geographical distribution limitation excluding those countries, so that +distribution is permitted only in or among countries not thus excluded. In +such case, this License incorporates the limitation as if written in the body +of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the +General Public License from time to time. Such new versions will be similar in +spirit to the present version, but may differ in detail to address new problems +or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any later +version", you have the option of following the terms and conditions either of +that version or of any later version published by the Free Software Foundation. +If the Program does not specify a version number of this License, you may +choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status of +all derivatives of our free software and of promoting the sharing and reuse of +software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE +PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, +YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL +ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE +PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR +INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA +BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER +OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach +them to the start of each source file to most effectively convey the exclusion +of warranty; and each file should have at least the "copyright" line and a +pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + + Copyright (C) <year> <name of author> + + 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 2 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, write to the Free Software Foundation, Inc., 59 + Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when it +starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author Gnomovision comes + with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free + software, and you are welcome to redistribute it under certain conditions; + type 'show c' for details. + +The hypothetical commands 'show w' and 'show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may be +called something other than 'show w' and 'show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the program, if necessary. Here +is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + 'Gnomovision' (which makes passes at compilers) written by James Hacker. + + signature of Ty Coon, 1 April 1989 + + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General Public +License instead of this License. + + +"CLASSPATH" EXCEPTION TO THE GPL + +Certain source files distributed by Oracle America and/or its affiliates are +subject to the following clarification and special exception to the GPL, but +only where Oracle has expressly included in the particular source file's header +the words "Oracle designates this particular file as subject to the "Classpath" +exception as provided by Oracle in the LICENSE file that accompanied this code." + + Linking this library statically or dynamically with other modules is making + a combined work based on this library. Thus, the terms and conditions of + the GNU General Public License cover the whole combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent modules, + and to copy and distribute the resulting executable under terms of your + choice, provided that you also meet, for each linked independent module, + the terms and conditions of the license of that module. An independent + module is a module which is not derived from or based on this library. If + you modify this library, you may extend this exception to your version of + the library, but you are not obligated to do so. If you do not wish to do + so, delete this exception statement from your version. diff --git a/depends/pack200/anti200.cpp b/depends/pack200/anti200.cpp new file mode 100644 index 00000000..1e1ec0c8 --- /dev/null +++ b/depends/pack200/anti200.cpp @@ -0,0 +1,43 @@ +/* + * This is trivial. Do what thou wilt with it. Public domain. + */ + +#include <stdexcept> +#include <iostream> +#include "unpack200.h" + +int main(int argc, char **argv) +{ + if (argc != 3) + { + std::cerr << "Simple pack200 unpacker!" << std::endl << "Run like this:" << std::endl + << " " << argv[0] << " input.jar.lzma output.jar" << std::endl; + return EXIT_FAILURE; + } + + FILE *input = fopen(argv[1], "rb"); + FILE *output = fopen(argv[2], "wb"); + if (!input) + { + std::cerr << "Can't open input file"; + return EXIT_FAILURE; + } + if (!output) + { + fclose(output); + std::cerr << "Can't open output file"; + return EXIT_FAILURE; + } + try + { + unpack_200(input, output); + } + catch (std::runtime_error &e) + { + std::cerr << "Bad things happened: " << e.what() << std::endl; + fclose(input); + fclose(output); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/depends/pack200/include/unpack200.h b/depends/pack200/include/unpack200.h new file mode 100644 index 00000000..f9239488 --- /dev/null +++ b/depends/pack200/include/unpack200.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#pragma once +#include <string> + +/** + * @brief Unpack a PACK200 file + * + * @param input_path Path to the input file in PACK200 format. System native string encoding. + * @param output_path Path to the output file in PACK200 format. System native string encoding. + * @return void + * @throw std::runtime_error for any error encountered + */ +void unpack_200(FILE * input, FILE * output); diff --git a/depends/pack200/src/bands.cpp b/depends/pack200/src/bands.cpp new file mode 100644 index 00000000..1608d838 --- /dev/null +++ b/depends/pack200/src/bands.cpp @@ -0,0 +1,423 @@ +/* + * Copyright (c) 2002, 2009, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// -*- C++ -*- +// Small program for unpacking specially compressed Java packages. +// John R. Rose + +#include <sys/types.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> +#include <assert.h> +#include <stdint.h> + +#include "defines.h" +#include "bytes.h" +#include "utils.h" +#include "coding.h" +#include "bands.h" + +#include "constants.h" +#include "unpack.h" + +void band::readData(int expectedLength) +{ + assert(expectedLength >= 0); + assert(vs[0].cmk == cmk_ERROR); + if (expectedLength != 0) + { + assert(length == 0); + length = expectedLength; + } + if (length == 0) + { + assert((rplimit = cm.vs0.rp = u->rp) != nullptr); + return; + } + assert(length > 0); + + bool is_BYTE1 = (defc->spec == BYTE1_spec); + + if (is_BYTE1) + { + // No possibility of coding change. Sizing is exact. + u->ensure_input(length); + } + else + { + // Make a conservatively generous estimate of band size in bytes. + // Assume B == 5 everywhere. + // Assume awkward pop with all {U} values (2*5 per value) + int64_t generous = (int64_t)length * (B_MAX * 3 + 1) + C_SLOP; + u->ensure_input(generous); + } + + // Read one value to see what it might be. + int XB = _meta_default; + if (!is_BYTE1) + { + // must be a variable-length coding + assert(defc->B() > 1 && defc->L() > 0); + + value_stream xvs; + coding *valc = defc; + if (valc->D() != 0) + { + valc = coding::findBySpec(defc->B(), defc->H(), defc->S()); + assert(!valc->isMalloc); + } + xvs.init(u->rp, u->rplimit, valc); + int X = xvs.getInt(); + if (valc->S() != 0) + { + assert(valc->min <= -256); + XB = -1 - X; + } + else + { + int L = valc->L(); + assert(valc->max >= L + 255); + XB = X - L; + } + if (0 <= XB && XB < 256) + { + // Skip over the escape value. + u->rp = xvs.rp; + } + else + { + // No, it's still default. + XB = _meta_default; + } + } + + if (XB <= _meta_canon_max) + { + byte XB_byte = (byte)XB; + byte *XB_ptr = &XB_byte; + cm.init(u->rp, u->rplimit, XB_ptr, 0, defc, length, nullptr); + } + else + { + assert(u->meta_rp != nullptr); + // Scribble the initial byte onto the band. + byte *save_meta_rp = --u->meta_rp; + byte save_meta_xb = (*save_meta_rp); + (*save_meta_rp) = (byte)XB; + cm.init(u->rp, u->rplimit, u->meta_rp, 0, defc, length, nullptr); + (*save_meta_rp) = save_meta_xb; // put it back, just to be tidy + } + rplimit = u->rp; + + rewind(); +} + +void band::setIndex(cpindex *ix_) +{ + assert(ix_ == nullptr || ixTag == ix_->ixTag); + ix = ix_; +} +void band::setIndexByTag(byte tag) +{ + setIndex(u->cp.getIndex(tag)); +} + +entry *band::getRefCommon(cpindex *ix_, bool nullOKwithCaller) +{ + assert(ix_->ixTag == ixTag || + (ixTag == CONSTANT_Literal && ix_->ixTag >= CONSTANT_Integer && + ix_->ixTag <= CONSTANT_String)); + int n = vs[0].getInt() - nullOK; + // Note: band-local nullOK means nullptr encodes as 0. + // But nullOKwithCaller means caller is willing to tolerate a nullptr. + entry *ref = ix_->get(n); + if (ref == nullptr && !(nullOKwithCaller && n == -1)) + unpack_abort(n == -1 ? "nullptr ref" : "bad ref"); + return ref; +} + +int64_t band::getLong(band &lo_band, bool have_hi) +{ + band &hi_band = (*this); + assert(lo_band.bn == hi_band.bn + 1); + uint32_t lo = lo_band.getInt(); + if (!have_hi) + { + assert(hi_band.length == 0); + return makeLong(0, lo); + } + uint32_t hi = hi_band.getInt(); + return makeLong(hi, lo); +} + +int band::getIntTotal() +{ + if (length == 0) + return 0; + if (total_memo > 0) + return total_memo - 1; + int total = getInt(); + // overflow checks require that none of the addends are <0, + // and that the partial sums never overflow (wrap negative) + if (total < 0) + { + unpack_abort("overflow detected"); + } + for (int k = length - 1; k > 0; k--) + { + int prev_total = total; + total += vs[0].getInt(); + if (total < prev_total) + { + unpack_abort("overflow detected"); + } + } + rewind(); + total_memo = total + 1; + return total; +} + +int band::getIntCount(int tag) +{ + if (length == 0) + return 0; + if (tag >= HIST0_MIN && tag <= HIST0_MAX) + { + if (hist0 == nullptr) + { + // Lazily calculate an approximate histogram. + hist0 = U_NEW(int, (HIST0_MAX - HIST0_MIN) + 1); + for (int k = length; k > 0; k--) + { + int x = vs[0].getInt(); + if (x >= HIST0_MIN && x <= HIST0_MAX) + hist0[x - HIST0_MIN] += 1; + } + rewind(); + } + return hist0[tag - HIST0_MIN]; + } + int total = 0; + for (int k = length; k > 0; k--) + { + total += (vs[0].getInt() == tag) ? 1 : 0; + } + rewind(); + return total; +} + +#define INDEX_INIT(tag, nullOK, subindex) ((tag) + (subindex) * SUBINDEX_BIT + (nullOK) * 256) + +#define INDEX(tag) INDEX_INIT(tag, 0, 0) +#define NULL_OR_INDEX(tag) INDEX_INIT(tag, 1, 0) +#define SUB_INDEX(tag) INDEX_INIT(tag, 0, 1) +#define NO_INDEX 0 + +struct band_init +{ + int defc; + int index; +}; + +#define BAND_INIT(name, cspec, ix) \ + { \ + cspec, ix \ + } + +const band_init all_band_inits[] = + { + // BAND_INIT(archive_magic, BYTE1_spec, 0), + // BAND_INIT(archive_header, UNSIGNED5_spec, 0), + // BAND_INIT(band_headers, BYTE1_spec, 0), + BAND_INIT(cp_Utf8_prefix, DELTA5_spec, 0), BAND_INIT(cp_Utf8_suffix, UNSIGNED5_spec, 0), + BAND_INIT(cp_Utf8_chars, CHAR3_spec, 0), BAND_INIT(cp_Utf8_big_suffix, DELTA5_spec, 0), + BAND_INIT(cp_Utf8_big_chars, DELTA5_spec, 0), BAND_INIT(cp_Int, UDELTA5_spec, 0), + BAND_INIT(cp_Float, UDELTA5_spec, 0), BAND_INIT(cp_Long_hi, UDELTA5_spec, 0), + BAND_INIT(cp_Long_lo, DELTA5_spec, 0), BAND_INIT(cp_Double_hi, UDELTA5_spec, 0), + BAND_INIT(cp_Double_lo, DELTA5_spec, 0), + BAND_INIT(cp_String, UDELTA5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(cp_Class, UDELTA5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(cp_Signature_form, DELTA5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(cp_Signature_classes, UDELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(cp_Descr_name, DELTA5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(cp_Descr_type, UDELTA5_spec, INDEX(CONSTANT_Signature)), + BAND_INIT(cp_Field_class, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(cp_Field_desc, UDELTA5_spec, INDEX(CONSTANT_NameandType)), + BAND_INIT(cp_Method_class, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(cp_Method_desc, UDELTA5_spec, INDEX(CONSTANT_NameandType)), + BAND_INIT(cp_Imethod_class, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(cp_Imethod_desc, UDELTA5_spec, INDEX(CONSTANT_NameandType)), + BAND_INIT(attr_definition_headers, BYTE1_spec, 0), + BAND_INIT(attr_definition_name, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(attr_definition_layout, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(ic_this_class, UDELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(ic_flags, UNSIGNED5_spec, 0), + BAND_INIT(ic_outer_class, DELTA5_spec, NULL_OR_INDEX(CONSTANT_Class)), + BAND_INIT(ic_name, DELTA5_spec, NULL_OR_INDEX(CONSTANT_Utf8)), + BAND_INIT(class_this, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(class_super, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(class_interface_count, DELTA5_spec, 0), + BAND_INIT(class_interface, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(class_field_count, DELTA5_spec, 0), + BAND_INIT(class_method_count, DELTA5_spec, 0), + BAND_INIT(field_descr, DELTA5_spec, INDEX(CONSTANT_NameandType)), + BAND_INIT(field_flags_hi, UNSIGNED5_spec, 0), + BAND_INIT(field_flags_lo, UNSIGNED5_spec, 0), + BAND_INIT(field_attr_count, UNSIGNED5_spec, 0), + BAND_INIT(field_attr_indexes, UNSIGNED5_spec, 0), + BAND_INIT(field_attr_calls, UNSIGNED5_spec, 0), + BAND_INIT(field_ConstantValue_KQ, UNSIGNED5_spec, INDEX(CONSTANT_Literal)), + BAND_INIT(field_Signature_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)), + BAND_INIT(field_metadata_bands, -1, -1), BAND_INIT(field_attr_bands, -1, -1), + BAND_INIT(method_descr, MDELTA5_spec, INDEX(CONSTANT_NameandType)), + BAND_INIT(method_flags_hi, UNSIGNED5_spec, 0), + BAND_INIT(method_flags_lo, UNSIGNED5_spec, 0), + BAND_INIT(method_attr_count, UNSIGNED5_spec, 0), + BAND_INIT(method_attr_indexes, UNSIGNED5_spec, 0), + BAND_INIT(method_attr_calls, UNSIGNED5_spec, 0), + BAND_INIT(method_Exceptions_N, UNSIGNED5_spec, 0), + BAND_INIT(method_Exceptions_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(method_Signature_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)), + BAND_INIT(method_metadata_bands, -1, -1), BAND_INIT(method_attr_bands, -1, -1), + BAND_INIT(class_flags_hi, UNSIGNED5_spec, 0), + BAND_INIT(class_flags_lo, UNSIGNED5_spec, 0), + BAND_INIT(class_attr_count, UNSIGNED5_spec, 0), + BAND_INIT(class_attr_indexes, UNSIGNED5_spec, 0), + BAND_INIT(class_attr_calls, UNSIGNED5_spec, 0), + BAND_INIT(class_SourceFile_RUN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Utf8)), + BAND_INIT(class_EnclosingMethod_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(class_EnclosingMethod_RDN, UNSIGNED5_spec, + NULL_OR_INDEX(CONSTANT_NameandType)), + BAND_INIT(class_Signature_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)), + BAND_INIT(class_metadata_bands, -1, -1), + BAND_INIT(class_InnerClasses_N, UNSIGNED5_spec, 0), + BAND_INIT(class_InnerClasses_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(class_InnerClasses_F, UNSIGNED5_spec, 0), + BAND_INIT(class_InnerClasses_outer_RCN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Class)), + BAND_INIT(class_InnerClasses_name_RUN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Utf8)), + BAND_INIT(class_ClassFile_version_minor_H, UNSIGNED5_spec, 0), + BAND_INIT(class_ClassFile_version_major_H, UNSIGNED5_spec, 0), + BAND_INIT(class_attr_bands, -1, -1), BAND_INIT(code_headers, BYTE1_spec, 0), + BAND_INIT(code_max_stack, UNSIGNED5_spec, 0), + BAND_INIT(code_max_na_locals, UNSIGNED5_spec, 0), + BAND_INIT(code_handler_count, UNSIGNED5_spec, 0), + BAND_INIT(code_handler_start_P, BCI5_spec, 0), + BAND_INIT(code_handler_end_PO, BRANCH5_spec, 0), + BAND_INIT(code_handler_catch_PO, BRANCH5_spec, 0), + BAND_INIT(code_handler_class_RCN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Class)), + BAND_INIT(code_flags_hi, UNSIGNED5_spec, 0), + BAND_INIT(code_flags_lo, UNSIGNED5_spec, 0), + BAND_INIT(code_attr_count, UNSIGNED5_spec, 0), + BAND_INIT(code_attr_indexes, UNSIGNED5_spec, 0), + BAND_INIT(code_attr_calls, UNSIGNED5_spec, 0), + BAND_INIT(code_StackMapTable_N, UNSIGNED5_spec, 0), + BAND_INIT(code_StackMapTable_frame_T, BYTE1_spec, 0), + BAND_INIT(code_StackMapTable_local_N, UNSIGNED5_spec, 0), + BAND_INIT(code_StackMapTable_stack_N, UNSIGNED5_spec, 0), + BAND_INIT(code_StackMapTable_offset, UNSIGNED5_spec, 0), + BAND_INIT(code_StackMapTable_T, BYTE1_spec, 0), + BAND_INIT(code_StackMapTable_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(code_StackMapTable_P, BCI5_spec, 0), + BAND_INIT(code_LineNumberTable_N, UNSIGNED5_spec, 0), + BAND_INIT(code_LineNumberTable_bci_P, BCI5_spec, 0), + BAND_INIT(code_LineNumberTable_line, UNSIGNED5_spec, 0), + BAND_INIT(code_LocalVariableTable_N, UNSIGNED5_spec, 0), + BAND_INIT(code_LocalVariableTable_bci_P, BCI5_spec, 0), + BAND_INIT(code_LocalVariableTable_span_O, BRANCH5_spec, 0), + BAND_INIT(code_LocalVariableTable_name_RU, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(code_LocalVariableTable_type_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)), + BAND_INIT(code_LocalVariableTable_slot, UNSIGNED5_spec, 0), + BAND_INIT(code_LocalVariableTypeTable_N, UNSIGNED5_spec, 0), + BAND_INIT(code_LocalVariableTypeTable_bci_P, BCI5_spec, 0), + BAND_INIT(code_LocalVariableTypeTable_span_O, BRANCH5_spec, 0), + BAND_INIT(code_LocalVariableTypeTable_name_RU, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(code_LocalVariableTypeTable_type_RS, UNSIGNED5_spec, + INDEX(CONSTANT_Signature)), + BAND_INIT(code_LocalVariableTypeTable_slot, UNSIGNED5_spec, 0), + BAND_INIT(code_attr_bands, -1, -1), BAND_INIT(bc_codes, BYTE1_spec, 0), + BAND_INIT(bc_case_count, UNSIGNED5_spec, 0), BAND_INIT(bc_case_value, DELTA5_spec, 0), + BAND_INIT(bc_byte, BYTE1_spec, 0), BAND_INIT(bc_short, DELTA5_spec, 0), + BAND_INIT(bc_local, UNSIGNED5_spec, 0), BAND_INIT(bc_label, BRANCH5_spec, 0), + BAND_INIT(bc_intref, DELTA5_spec, INDEX(CONSTANT_Integer)), + BAND_INIT(bc_floatref, DELTA5_spec, INDEX(CONSTANT_Float)), + BAND_INIT(bc_longref, DELTA5_spec, INDEX(CONSTANT_Long)), + BAND_INIT(bc_doubleref, DELTA5_spec, INDEX(CONSTANT_Double)), + BAND_INIT(bc_stringref, DELTA5_spec, INDEX(CONSTANT_String)), + BAND_INIT(bc_classref, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Class)), + BAND_INIT(bc_fieldref, DELTA5_spec, INDEX(CONSTANT_Fieldref)), + BAND_INIT(bc_methodref, UNSIGNED5_spec, INDEX(CONSTANT_Methodref)), + BAND_INIT(bc_imethodref, DELTA5_spec, INDEX(CONSTANT_InterfaceMethodref)), + BAND_INIT(bc_thisfield, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Fieldref)), + BAND_INIT(bc_superfield, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Fieldref)), + BAND_INIT(bc_thismethod, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Methodref)), + BAND_INIT(bc_supermethod, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Methodref)), + BAND_INIT(bc_initref, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Methodref)), + BAND_INIT(bc_escref, UNSIGNED5_spec, INDEX(CONSTANT_All)), + BAND_INIT(bc_escrefsize, UNSIGNED5_spec, 0), BAND_INIT(bc_escsize, UNSIGNED5_spec, 0), + BAND_INIT(bc_escbyte, BYTE1_spec, 0), + BAND_INIT(file_name, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(file_size_hi, UNSIGNED5_spec, 0), BAND_INIT(file_size_lo, UNSIGNED5_spec, 0), + BAND_INIT(file_modtime, DELTA5_spec, 0), BAND_INIT(file_options, UNSIGNED5_spec, 0), + // BAND_INIT(file_bits, BYTE1_spec, 0), + {0, 0}}; + +band *band::makeBands(unpacker *u) +{ + band *tmp_all_bands = U_NEW(band, BAND_LIMIT); + for (int i = 0; i < BAND_LIMIT; i++) + { + assert((byte *)&all_band_inits[i + 1] < + (byte *)all_band_inits + sizeof(all_band_inits)); + const band_init &bi = all_band_inits[i]; + band &b = tmp_all_bands[i]; + coding *defc = coding::findBySpec(bi.defc); + assert((defc == nullptr) == (bi.defc == -1)); // no garbage, please + assert(defc == nullptr || !defc->isMalloc); + b.init(u, i, defc); + if (bi.index > 0) + { + b.nullOK = ((bi.index >> 8) & 1); + b.ixTag = (bi.index & 0xFF); + } + } + return tmp_all_bands; +} + +void band::initIndexes(unpacker *u) +{ + band *tmp_all_bands = u->all_bands; + for (int i = 0; i < BAND_LIMIT; i++) + { + band *scan = &tmp_all_bands[i]; + uint32_t tag = scan->ixTag; // Cf. #define INDEX(tag) above + if (tag != 0 && tag != CONSTANT_Literal && (tag & SUBINDEX_BIT) == 0) + { + scan->setIndex(u->cp.getIndex(tag)); + } + } +} diff --git a/depends/pack200/src/bands.h b/depends/pack200/src/bands.h new file mode 100644 index 00000000..a56cd7d5 --- /dev/null +++ b/depends/pack200/src/bands.h @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2002, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// -*- C++ -*- +struct entry; +struct cpindex; +struct unpacker; + +struct band +{ + int bn; // band_number of this band + coding *defc; // default coding method + cpindex *ix; // CP entry mapping, if CPRefBand + byte ixTag; // 0 or 1; nullptr is coded as (nullOK?0:-1) + byte nullOK; // 0 or 1; nullptr is coded as (nullOK?0:-1) + int length; // expected # values + unpacker *u; // back pointer + + value_stream vs[2]; // source of values + coding_method cm; // method used for initial state of vs[0] + byte *rplimit; // end of band (encoded, transmitted) + + int total_memo; // cached value of getIntTotal, or -1 + int *hist0; // approximate. histogram + enum + { + HIST0_MIN = 0, + HIST0_MAX = 255 + }; // catches the usual cases + + // properties for attribute layout elements: + byte le_kind; // EK_XXX + byte le_bci; // 0,EK_BCI,EK_BCD,EK_BCO + byte le_back; // ==EF_BACK + byte le_len; // 0,1,2,4 (size in classfile), or call addr + band **le_body; // body of repl, union, call (nullptr-terminated) +// Note: EK_CASE elements use hist0 to record union tags. +#define le_casetags hist0 + + band &nextBand() + { + return this[1]; + } + band &prevBand() + { + return this[-1]; + } + + void init(unpacker *u_, int bn_, coding *defc_) + { + u = u_; + cm.u = u_; + bn = bn_; + defc = defc_; + } + void init(unpacker *u_, int bn_, int defcSpec) + { + init(u_, bn_, coding::findBySpec(defcSpec)); + } + void initRef(int ixTag_ = 0, bool nullOK_ = false) + { + ixTag = ixTag_; + nullOK = nullOK_; + setIndexByTag(ixTag); + } + + void expectMoreLength(int l) + { + assert(length >= 0); // able to accept a length + assert((int)l >= 0); // no overflow + assert(rplimit == nullptr); // readData not yet called + length += l; + assert(length >= l); // no overflow + } + + void setIndex(cpindex *ix_); + void setIndexByTag(byte tag); + + // Parse the band and its meta-coding header. + void readData(int expectedLength = 0); + + // Reset the band for another pass (Cf. Java Band.resetForSecondPass.) + void rewind() + { + cm.reset(&vs[0]); + } + + byte *&curRP() + { + return vs[0].rp; + } + byte *minRP() + { + return cm.vs0.rp; + } + byte *maxRP() + { + return rplimit; + } + size_t size() + { + return maxRP() - minRP(); + } + + int getByte() + { + assert(ix == nullptr); + return vs[0].getByte(); + } + int getInt() + { + assert(ix == nullptr); + return vs[0].getInt(); + } + entry *getRefN() + { + assert(ix != nullptr); + return getRefCommon(ix, true); + } + entry *getRef() + { + assert(ix != nullptr); + return getRefCommon(ix, false); + } + entry *getRefUsing(cpindex *ix2) + { + assert(ix == nullptr); + return getRefCommon(ix2, true); + } + entry *getRefCommon(cpindex *ix, bool nullOK); + int64_t getLong(band &lo_band, bool have_hi); + + static int64_t makeLong(uint32_t hi, uint32_t lo) + { + return ((uint64_t)hi << 32) + (((uint64_t)lo << 32) >> 32); + } + + int getIntTotal(); + int getIntCount(int tag); + + static band *makeBands(unpacker *u); + static void initIndexes(unpacker *u); +}; + +extern band all_bands[]; + +#define BAND_LOCAL /* \ + band* band_temp = all_bands; \ + band* all_bands = band_temp */ + +// Band schema: +enum band_number +{ + // e_archive_magic, + // e_archive_header, + // e_band_headers, + + // constant pool contents + e_cp_Utf8_prefix, + e_cp_Utf8_suffix, + e_cp_Utf8_chars, + e_cp_Utf8_big_suffix, + e_cp_Utf8_big_chars, + e_cp_Int, + e_cp_Float, + e_cp_Long_hi, + e_cp_Long_lo, + e_cp_Double_hi, + e_cp_Double_lo, + e_cp_String, + e_cp_Class, + e_cp_Signature_form, + e_cp_Signature_classes, + e_cp_Descr_name, + e_cp_Descr_type, + e_cp_Field_class, + e_cp_Field_desc, + e_cp_Method_class, + e_cp_Method_desc, + e_cp_Imethod_class, + e_cp_Imethod_desc, + + // bands which define transmission of attributes + e_attr_definition_headers, + e_attr_definition_name, + e_attr_definition_layout, + + // band for hardwired InnerClasses attribute (shared across the package) + e_ic_this_class, + e_ic_flags, + // These bands contain data only where flags sets ACC_IC_LONG_FORM: + e_ic_outer_class, + e_ic_name, + + // bands for carrying class schema information: + e_class_this, + e_class_super, + e_class_interface_count, + e_class_interface, + + // bands for class members + e_class_field_count, + e_class_method_count, + e_field_descr, + e_field_flags_hi, + e_field_flags_lo, + e_field_attr_count, + e_field_attr_indexes, + e_field_attr_calls, + e_field_ConstantValue_KQ, + e_field_Signature_RS, + e_field_metadata_bands, + e_field_attr_bands, + e_method_descr, + e_method_flags_hi, + e_method_flags_lo, + e_method_attr_count, + e_method_attr_indexes, + e_method_attr_calls, + e_method_Exceptions_N, + e_method_Exceptions_RC, + e_method_Signature_RS, + e_method_metadata_bands, + e_method_attr_bands, + e_class_flags_hi, + e_class_flags_lo, + e_class_attr_count, + e_class_attr_indexes, + e_class_attr_calls, + e_class_SourceFile_RUN, + e_class_EnclosingMethod_RC, + e_class_EnclosingMethod_RDN, + e_class_Signature_RS, + e_class_metadata_bands, + e_class_InnerClasses_N, + e_class_InnerClasses_RC, + e_class_InnerClasses_F, + e_class_InnerClasses_outer_RCN, + e_class_InnerClasses_name_RUN, + e_class_ClassFile_version_minor_H, + e_class_ClassFile_version_major_H, + e_class_attr_bands, + e_code_headers, + e_code_max_stack, + e_code_max_na_locals, + e_code_handler_count, + e_code_handler_start_P, + e_code_handler_end_PO, + e_code_handler_catch_PO, + e_code_handler_class_RCN, + + // code attributes + e_code_flags_hi, + e_code_flags_lo, + e_code_attr_count, + e_code_attr_indexes, + e_code_attr_calls, + e_code_StackMapTable_N, + e_code_StackMapTable_frame_T, + e_code_StackMapTable_local_N, + e_code_StackMapTable_stack_N, + e_code_StackMapTable_offset, + e_code_StackMapTable_T, + e_code_StackMapTable_RC, + e_code_StackMapTable_P, + e_code_LineNumberTable_N, + e_code_LineNumberTable_bci_P, + e_code_LineNumberTable_line, + e_code_LocalVariableTable_N, + e_code_LocalVariableTable_bci_P, + e_code_LocalVariableTable_span_O, + e_code_LocalVariableTable_name_RU, + e_code_LocalVariableTable_type_RS, + e_code_LocalVariableTable_slot, + e_code_LocalVariableTypeTable_N, + e_code_LocalVariableTypeTable_bci_P, + e_code_LocalVariableTypeTable_span_O, + e_code_LocalVariableTypeTable_name_RU, + e_code_LocalVariableTypeTable_type_RS, + e_code_LocalVariableTypeTable_slot, + e_code_attr_bands, + + // bands for bytecodes + e_bc_codes, + // remaining bands provide typed opcode fields required by the bc_codes + e_bc_case_count, + e_bc_case_value, + e_bc_byte, + e_bc_short, + e_bc_local, + e_bc_label, + + // ldc* operands: + e_bc_intref, + e_bc_floatref, + e_bc_longref, + e_bc_doubleref, + e_bc_stringref, + e_bc_classref, + e_bc_fieldref, + e_bc_methodref, + e_bc_imethodref, + + // _self_linker_op family + e_bc_thisfield, + e_bc_superfield, + e_bc_thismethod, + e_bc_supermethod, + + // bc_invokeinit family: + e_bc_initref, + + // bytecode escape sequences + e_bc_escref, + e_bc_escrefsize, + e_bc_escsize, + e_bc_escbyte, + + // file attributes and contents + e_file_name, + e_file_size_hi, + e_file_size_lo, + e_file_modtime, + e_file_options, + // e_file_bits, // handled specially as an appendix + BAND_LIMIT +}; + +// Symbolic names for bands, as if in a giant global struct: +//#define archive_magic all_bands[e_archive_magic] +//#define archive_header all_bands[e_archive_header] +//#define band_headers all_bands[e_band_headers] +#define cp_Utf8_prefix all_bands[e_cp_Utf8_prefix] +#define cp_Utf8_suffix all_bands[e_cp_Utf8_suffix] +#define cp_Utf8_chars all_bands[e_cp_Utf8_chars] +#define cp_Utf8_big_suffix all_bands[e_cp_Utf8_big_suffix] +#define cp_Utf8_big_chars all_bands[e_cp_Utf8_big_chars] +#define cp_Int all_bands[e_cp_Int] +#define cp_Float all_bands[e_cp_Float] +#define cp_Long_hi all_bands[e_cp_Long_hi] +#define cp_Long_lo all_bands[e_cp_Long_lo] +#define cp_Double_hi all_bands[e_cp_Double_hi] +#define cp_Double_lo all_bands[e_cp_Double_lo] +#define cp_String all_bands[e_cp_String] +#define cp_Class all_bands[e_cp_Class] +#define cp_Signature_form all_bands[e_cp_Signature_form] +#define cp_Signature_classes all_bands[e_cp_Signature_classes] +#define cp_Descr_name all_bands[e_cp_Descr_name] +#define cp_Descr_type all_bands[e_cp_Descr_type] +#define cp_Field_class all_bands[e_cp_Field_class] +#define cp_Field_desc all_bands[e_cp_Field_desc] +#define cp_Method_class all_bands[e_cp_Method_class] +#define cp_Method_desc all_bands[e_cp_Method_desc] +#define cp_Imethod_class all_bands[e_cp_Imethod_class] +#define cp_Imethod_desc all_bands[e_cp_Imethod_desc] +#define attr_definition_headers all_bands[e_attr_definition_headers] +#define attr_definition_name all_bands[e_attr_definition_name] +#define attr_definition_layout all_bands[e_attr_definition_layout] +#define ic_this_class all_bands[e_ic_this_class] +#define ic_flags all_bands[e_ic_flags] +#define ic_outer_class all_bands[e_ic_outer_class] +#define ic_name all_bands[e_ic_name] +#define class_this all_bands[e_class_this] +#define class_super all_bands[e_class_super] +#define class_interface_count all_bands[e_class_interface_count] +#define class_interface all_bands[e_class_interface] +#define class_field_count all_bands[e_class_field_count] +#define class_method_count all_bands[e_class_method_count] +#define field_descr all_bands[e_field_descr] +#define field_flags_hi all_bands[e_field_flags_hi] +#define field_flags_lo all_bands[e_field_flags_lo] +#define field_attr_count all_bands[e_field_attr_count] +#define field_attr_indexes all_bands[e_field_attr_indexes] +#define field_ConstantValue_KQ all_bands[e_field_ConstantValue_KQ] +#define field_Signature_RS all_bands[e_field_Signature_RS] +#define field_attr_bands all_bands[e_field_attr_bands] +#define method_descr all_bands[e_method_descr] +#define method_flags_hi all_bands[e_method_flags_hi] +#define method_flags_lo all_bands[e_method_flags_lo] +#define method_attr_count all_bands[e_method_attr_count] +#define method_attr_indexes all_bands[e_method_attr_indexes] +#define method_Exceptions_N all_bands[e_method_Exceptions_N] +#define method_Exceptions_RC all_bands[e_method_Exceptions_RC] +#define method_Signature_RS all_bands[e_method_Signature_RS] +#define method_attr_bands all_bands[e_method_attr_bands] +#define class_flags_hi all_bands[e_class_flags_hi] +#define class_flags_lo all_bands[e_class_flags_lo] +#define class_attr_count all_bands[e_class_attr_count] +#define class_attr_indexes all_bands[e_class_attr_indexes] +#define class_SourceFile_RUN all_bands[e_class_SourceFile_RUN] +#define class_EnclosingMethod_RC all_bands[e_class_EnclosingMethod_RC] +#define class_EnclosingMethod_RDN all_bands[e_class_EnclosingMethod_RDN] +#define class_Signature_RS all_bands[e_class_Signature_RS] +#define class_InnerClasses_N all_bands[e_class_InnerClasses_N] +#define class_InnerClasses_RC all_bands[e_class_InnerClasses_RC] +#define class_InnerClasses_F all_bands[e_class_InnerClasses_F] +#define class_InnerClasses_outer_RCN all_bands[e_class_InnerClasses_outer_RCN] +#define class_InnerClasses_name_RUN all_bands[e_class_InnerClasses_name_RUN] +#define class_ClassFile_version_minor_H all_bands[e_class_ClassFile_version_minor_H] +#define class_ClassFile_version_major_H all_bands[e_class_ClassFile_version_major_H] +#define class_attr_bands all_bands[e_class_attr_bands] +#define code_headers all_bands[e_code_headers] +#define code_max_stack all_bands[e_code_max_stack] +#define code_max_na_locals all_bands[e_code_max_na_locals] +#define code_handler_count all_bands[e_code_handler_count] +#define code_handler_start_P all_bands[e_code_handler_start_P] +#define code_handler_end_PO all_bands[e_code_handler_end_PO] +#define code_handler_catch_PO all_bands[e_code_handler_catch_PO] +#define code_handler_class_RCN all_bands[e_code_handler_class_RCN] +#define code_flags_hi all_bands[e_code_flags_hi] +#define code_flags_lo all_bands[e_code_flags_lo] +#define code_attr_count all_bands[e_code_attr_count] +#define code_attr_indexes all_bands[e_code_attr_indexes] +#define code_StackMapTable_N all_bands[e_code_StackMapTable_N] +#define code_StackMapTable_frame_T all_bands[e_code_StackMapTable_frame_T] +#define code_StackMapTable_local_N all_bands[e_code_StackMapTable_local_N] +#define code_StackMapTable_stack_N all_bands[e_code_StackMapTable_stack_N] +#define code_StackMapTable_offset all_bands[e_code_StackMapTable_offset] +#define code_StackMapTable_T all_bands[e_code_StackMapTable_T] +#define code_StackMapTable_RC all_bands[e_code_StackMapTable_RC] +#define code_StackMapTable_P all_bands[e_code_StackMapTable_P] +#define code_LineNumberTable_N all_bands[e_code_LineNumberTable_N] +#define code_LineNumberTable_bci_P all_bands[e_code_LineNumberTable_bci_P] +#define code_LineNumberTable_line all_bands[e_code_LineNumberTable_line] +#define code_LocalVariableTable_N all_bands[e_code_LocalVariableTable_N] +#define code_LocalVariableTable_bci_P all_bands[e_code_LocalVariableTable_bci_P] +#define code_LocalVariableTable_span_O all_bands[e_code_LocalVariableTable_span_O] +#define code_LocalVariableTable_name_RU all_bands[e_code_LocalVariableTable_name_RU] +#define code_LocalVariableTable_type_RS all_bands[e_code_LocalVariableTable_type_RS] +#define code_LocalVariableTable_slot all_bands[e_code_LocalVariableTable_slot] +#define code_LocalVariableTypeTable_N all_bands[e_code_LocalVariableTypeTable_N] +#define code_LocalVariableTypeTable_bci_P all_bands[e_code_LocalVariableTypeTable_bci_P] +#define code_LocalVariableTypeTable_span_O all_bands[e_code_LocalVariableTypeTable_span_O] +#define code_LocalVariableTypeTable_name_RU all_bands[e_code_LocalVariableTypeTable_name_RU] +#define code_LocalVariableTypeTable_type_RS all_bands[e_code_LocalVariableTypeTable_type_RS] +#define code_LocalVariableTypeTable_slot all_bands[e_code_LocalVariableTypeTable_slot] +#define code_attr_bands all_bands[e_code_attr_bands] +#define bc_codes all_bands[e_bc_codes] +#define bc_case_count all_bands[e_bc_case_count] +#define bc_case_value all_bands[e_bc_case_value] +#define bc_byte all_bands[e_bc_byte] +#define bc_short all_bands[e_bc_short] +#define bc_local all_bands[e_bc_local] +#define bc_label all_bands[e_bc_label] +#define bc_intref all_bands[e_bc_intref] +#define bc_floatref all_bands[e_bc_floatref] +#define bc_longref all_bands[e_bc_longref] +#define bc_doubleref all_bands[e_bc_doubleref] +#define bc_stringref all_bands[e_bc_stringref] +#define bc_classref all_bands[e_bc_classref] +#define bc_fieldref all_bands[e_bc_fieldref] +#define bc_methodref all_bands[e_bc_methodref] +#define bc_imethodref all_bands[e_bc_imethodref] +#define bc_thisfield all_bands[e_bc_thisfield] +#define bc_superfield all_bands[e_bc_superfield] +#define bc_thismethod all_bands[e_bc_thismethod] +#define bc_supermethod all_bands[e_bc_supermethod] +#define bc_initref all_bands[e_bc_initref] +#define bc_escref all_bands[e_bc_escref] +#define bc_escrefsize all_bands[e_bc_escrefsize] +#define bc_escsize all_bands[e_bc_escsize] +#define bc_escbyte all_bands[e_bc_escbyte] +#define file_name all_bands[e_file_name] +#define file_size_hi all_bands[e_file_size_hi] +#define file_size_lo all_bands[e_file_size_lo] +#define file_modtime all_bands[e_file_modtime] +#define file_options all_bands[e_file_options] diff --git a/depends/pack200/src/bytes.cpp b/depends/pack200/src/bytes.cpp new file mode 100644 index 00000000..d3808afa --- /dev/null +++ b/depends/pack200/src/bytes.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <stdint.h> +#include "defines.h" +#include "bytes.h" +#include "utils.h" + +static byte dummy[1 << 10]; + +bool bytes::inBounds(const void *p) +{ + return p >= ptr && p < limit(); +} + +void bytes::malloc(size_t len_) +{ + len = len_; + ptr = NEW(byte, add_size(len_, 1)); // add trailing zero byte always + if (ptr == nullptr) + { + // set ptr to some victim memory, to ease escape + set(dummy, sizeof(dummy) - 1); + unpack_abort(ERROR_ENOMEM); + } +} + +void bytes::realloc(size_t len_) +{ + if (len == len_) + return; // nothing to do + if (ptr == dummy) + return; // escaping from an error + if (ptr == nullptr) + { + malloc(len_); + return; + } + byte *oldptr = ptr; + ptr = (len_ >= PSIZE_MAX) ? nullptr : (byte *)::realloc(ptr, add_size(len_, 1)); + if (ptr != nullptr) + { + if (len < len_) + memset(ptr + len, 0, len_ - len); + ptr[len_] = 0; + len = len_; + } + else + { + ptr = oldptr; // ease our escape + unpack_abort(ERROR_ENOMEM); + } +} + +void bytes::free() +{ + if (ptr == dummy) + return; // escaping from an error + if (ptr != nullptr) + { + ::free(ptr); + } + len = 0; + ptr = 0; +} + +int bytes::indexOf(byte c) +{ + byte *p = (byte *)memchr(ptr, c, len); + return (p == 0) ? -1 : (int)(p - ptr); +} + +byte *bytes::writeTo(byte *bp) +{ + memcpy(bp, ptr, len); + return bp + len; +} + +int bytes::compareTo(bytes &other) +{ + size_t l1 = len; + size_t l2 = other.len; + int cmp = memcmp(ptr, other.ptr, (l1 < l2) ? l1 : l2); + if (cmp != 0) + return cmp; + return (l1 < l2) ? -1 : (l1 > l2) ? 1 : 0; +} + +void bytes::saveFrom(const void *ptr_, size_t len_) +{ + malloc(len_); + // Save as much as possible. + if (len_ > len) + { + assert(ptr == dummy); // error recovery + len_ = len; + } + copyFrom(ptr_, len_); +} + +//#TODO: Need to fix for exception handling +void bytes::copyFrom(const void *ptr_, size_t len_, size_t offset) +{ + assert(len_ == 0 || inBounds(ptr + offset)); + assert(len_ == 0 || inBounds(ptr + offset + len_ - 1)); + memcpy(ptr + offset, ptr_, len_); +} + +// Make sure there are 'o' bytes beyond the fill pointer, +// advance the fill pointer, and return the old fill pointer. +byte *fillbytes::grow(size_t s) +{ + size_t nlen = add_size(b.len, s); + if (nlen <= allocated) + { + b.len = nlen; + return limit() - s; + } + size_t maxlen = nlen; + if (maxlen < 128) + maxlen = 128; + if (maxlen < allocated * 2) + maxlen = allocated * 2; + if (allocated == 0) + { + // Initial buffer was not malloced. Do not reallocate it. + bytes old = b; + b.malloc(maxlen); + if (b.len == maxlen) + old.writeTo(b.ptr); + } + else + { + b.realloc(maxlen); + } + allocated = b.len; + if (allocated != maxlen) + { + b.len = nlen - s; // back up + return dummy; // scribble during error recov. + } + // after realloc, recompute pointers + b.len = nlen; + assert(b.len <= allocated); + return limit() - s; +} + +void fillbytes::ensureSize(size_t s) +{ + if (allocated >= s) + return; + size_t len0 = b.len; + grow(s - size()); + b.len = len0; // put it back +} + +int ptrlist::indexOf(const void *x) +{ + int len = length(); + for (int i = 0; i < len; i++) + { + if (get(i) == x) + return i; + } + return -1; +} + +void ptrlist::freeAll() +{ + int len = length(); + for (int i = 0; i < len; i++) + { + void *p = (void *)get(i); + if (p != nullptr) + { + ::free(p); + } + } + free(); +} + +int intlist::indexOf(int x) +{ + int len = length(); + for (int i = 0; i < len; i++) + { + if (get(i) == x) + return i; + } + return -1; +} diff --git a/depends/pack200/src/bytes.h b/depends/pack200/src/bytes.h new file mode 100644 index 00000000..6ed0b729 --- /dev/null +++ b/depends/pack200/src/bytes.h @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +struct bytes +{ + int8_t *ptr; + size_t len; + int8_t *limit() + { + return ptr + len; + } + + void set(int8_t *ptr_, size_t len_) + { + ptr = ptr_; + len = len_; + } + void set(const char *str) + { + ptr = (int8_t *)str; + len = strlen(str); + } + bool inBounds(const void *p); // p in [ptr, limit) + void malloc(size_t len_); + void realloc(size_t len_); + void free(); + void copyFrom(const void *ptr_, size_t len_, size_t offset = 0); + void saveFrom(const void *ptr_, size_t len_); + void saveFrom(const char *str) + { + saveFrom(str, strlen(str)); + } + void copyFrom(bytes &other, size_t offset = 0) + { + copyFrom(other.ptr, other.len, offset); + } + void saveFrom(bytes &other) + { + saveFrom(other.ptr, other.len); + } + void clear(int fill_byte = 0) + { + memset(ptr, fill_byte, len); + } + int8_t *writeTo(int8_t *bp); + bool equals(bytes &other) + { + return 0 == compareTo(other); + } + int compareTo(bytes &other); + bool contains(int8_t c) + { + return indexOf(c) >= 0; + } + int indexOf(int8_t c); + // substrings: + static bytes of(int8_t *ptr, size_t len) + { + bytes res; + res.set(ptr, len); + return res; + } + bytes slice(size_t beg, size_t end) + { + bytes res; + res.ptr = ptr + beg; + res.len = end - beg; + assert(res.len == 0 ||(inBounds(res.ptr) && inBounds(res.limit() - 1))); + return res; + } + // building C strings inside byte buffers: + bytes &strcat(const char *str) + { + ::strcat((char *)ptr, str); + return *this; + } + bytes &strcat(bytes &other) + { + ::strncat((char *)ptr, (char *)other.ptr, other.len); + return *this; + } + char *strval() + { + assert(strlen((char *)ptr) == len); + return (char *)ptr; + } +}; +#define BYTES_OF(var) (bytes::of((int8_t *)&(var), sizeof(var))) + +struct fillbytes +{ + bytes b; + size_t allocated; + + int8_t *base() + { + return b.ptr; + } + size_t size() + { + return b.len; + } + int8_t *limit() + { + return b.limit(); + } // logical limit + void setLimit(int8_t *lp) + { + assert(isAllocated(lp)); + b.len = lp - b.ptr; + } + int8_t *end() + { + return b.ptr + allocated; + } // physical limit + int8_t *loc(size_t o) + { + assert(o < b.len); + return b.ptr + o; + } + void init() + { + allocated = 0; + b.set(nullptr, 0); + } + void init(size_t s) + { + init(); + ensureSize(s); + } + void free() + { + if (allocated != 0) + b.free(); + allocated = 0; + } + void empty() + { + b.len = 0; + } + int8_t *grow(size_t s); // grow so that limit() += s + int getByte(uint32_t i) + { + return *loc(i) & 0xFF; + } + void addByte(int8_t x) + { + *grow(1) = x; + } + void ensureSize(size_t s); // make sure allocated >= s + void trimToSize() + { + if (allocated > size()) + b.realloc(allocated = size()); + } + bool canAppend(size_t s) + { + return allocated > b.len + s; + } + bool isAllocated(int8_t *p) + { + return p >= base() && p <= end(); + } // asserts + void set(bytes &src) + { + set(src.ptr, src.len); + } + + void set(int8_t *ptr, size_t len) + { + b.set(ptr, len); + allocated = 0; // mark as not reallocatable + } + + // block operations on resizing byte buffer: + fillbytes &append(const void *ptr_, size_t len_) + { + memcpy(grow(len_), ptr_, len_); + return (*this); + } + fillbytes &append(bytes &other) + { + return append(other.ptr, other.len); + } + fillbytes &append(const char *str) + { + return append(str, strlen(str)); + } +}; + +struct ptrlist : fillbytes +{ + typedef const void *cvptr; + int length() + { + return (int)(size() / sizeof(cvptr)); + } + cvptr *base() + { + return (cvptr *)fillbytes::base(); + } + cvptr &get(int i) + { + return *(cvptr *)loc(i * sizeof(cvptr)); + } + cvptr *limit() + { + return (cvptr *)fillbytes::limit(); + } + void add(cvptr x) + { + *(cvptr *)grow(sizeof(x)) = x; + } + void popTo(int l) + { + assert(l <= length()); + b.len = l * sizeof(cvptr); + } + int indexOf(cvptr x); + bool contains(cvptr x) + { + return indexOf(x) >= 0; + } + void freeAll(); // frees every ptr on the list, plus the list itself +}; +// Use a macro rather than mess with subtle mismatches +// between member and non-member function pointers. +#define PTRLIST_QSORT(ptrls, fn) ::qsort((ptrls).base(), (ptrls).length(), sizeof(void *), fn) + +struct intlist : fillbytes +{ + int length() + { + return (int)(size() / sizeof(int)); + } + int *base() + { + return (int *)fillbytes::base(); + } + int &get(int i) + { + return *(int *)loc(i * sizeof(int)); + } + int *limit() + { + return (int *)fillbytes::limit(); + } + void add(int x) + { + *(int *)grow(sizeof(x)) = x; + } + void popTo(int l) + { + assert(l <= length()); + b.len = l * sizeof(int); + } + int indexOf(int x); + bool contains(int x) + { + return indexOf(x) >= 0; + } +}; diff --git a/depends/pack200/src/coding.cpp b/depends/pack200/src/coding.cpp new file mode 100644 index 00000000..3e311131 --- /dev/null +++ b/depends/pack200/src/coding.cpp @@ -0,0 +1,1041 @@ +/* + * Copyright (c) 2002, 2009, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// -*- C++ -*- +// Small program for unpacking specially compressed Java packages. +// John R. Rose + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> +#include <assert.h> +#include <stdint.h> + +#include "defines.h" +#include "bytes.h" +#include "utils.h" +#include "coding.h" + +#include "constants.h" +#include "unpack.h" + +extern coding basic_codings[]; + +#define CODING_PRIVATE(spec) \ + int spec_ = spec; \ + int B = CODING_B(spec_); \ + int H = CODING_H(spec_); \ + int L = 256 - H; \ + int S = CODING_S(spec_); \ + int D = CODING_D(spec_) + +#define IS_NEG_CODE(S, codeVal) ((((int)(codeVal) + 1) & ((1 << S) - 1)) == 0) + +#define DECODE_SIGN_S1(ux) (((uint32_t)(ux) >> 1) ^ -((int)(ux) & 1)) + +static int decode_sign(int S, uint32_t ux) +{ // == Coding.decodeSign32 + assert(S > 0); + uint32_t sigbits = (ux >> S); + if (IS_NEG_CODE(S, ux)) + return (int)(~sigbits); + else + return (int)(ux - sigbits); + // Note that (int)(ux-sigbits) can be negative, if ux is large enough. +} + +coding *coding::init() +{ + if (umax > 0) + return this; // already done + assert(spec != 0); // sanity + + // fill in derived fields + CODING_PRIVATE(spec); + + // Return nullptr if 'arb(BHSD)' parameter constraints are not met: + if (B < 1 || B > B_MAX) + return nullptr; + if (H < 1 || H > 256) + return nullptr; + if (S < 0 || S > 2) + return nullptr; + if (D < 0 || D > 1) + return nullptr; + if (B == 1 && H != 256) + return nullptr; // 1-byte coding must be fixed-size + if (B >= 5 && H == 256) + return nullptr; // no 5-byte fixed-size coding + + // first compute the range of the coding, in 64 bits + int64_t range = 0; + { + int64_t H_i = 1; + for (int i = 0; i < B; i++) + { + range += H_i; + H_i *= H; + } + range *= L; + range += H_i; + } + assert(range > 0); // no useless codings, please + + int this_umax; + + // now, compute min and max + if (range >= ((int64_t)1 << 32)) + { + this_umax = INT_MAX_VALUE; + this->umin = INT_MIN_VALUE; + this->max = INT_MAX_VALUE; + this->min = INT_MIN_VALUE; + } + else + { + this_umax = (range > INT_MAX_VALUE) ? INT_MAX_VALUE : (int)range - 1; + this->max = this_umax; + this->min = this->umin = 0; + if (S != 0 && range != 0) + { + int64_t maxPosCode = range - 1; + int64_t maxNegCode = range - 1; + while (IS_NEG_CODE(S, maxPosCode)) + --maxPosCode; + while (!IS_NEG_CODE(S, maxNegCode)) + --maxNegCode; + int maxPos = decode_sign(S, (uint32_t)maxPosCode); + if (maxPos < 0) + this->max = INT_MAX_VALUE; // 32-bit wraparound + else + this->max = maxPos; + if (maxNegCode < 0) + this->min = 0; // No negative codings at all. + else + this->min = decode_sign(S, (uint32_t)maxNegCode); + } + } + + assert(!(isFullRange | isSigned | isSubrange)); // init + if (min < 0) + this->isSigned = true; + if (max < INT_MAX_VALUE && range <= INT_MAX_VALUE) + this->isSubrange = true; + if (max == INT_MAX_VALUE && min == INT_MIN_VALUE) + this->isFullRange = true; + + // do this last, to reduce MT exposure (should have a membar too) + this->umax = this_umax; + + return this; +} + +coding *coding::findBySpec(int spec) +{ + for (coding *scan = &basic_codings[0];; scan++) + { + if (scan->spec == spec) + return scan->init(); + if (scan->spec == 0) + break; + } + coding *ptr = NEW(coding, 1); + if (!ptr) + return nullptr; + coding *c = ptr->initFrom(spec); + if (c == nullptr) + { + ::free(ptr); + } + else + // else caller should free it... + c->isMalloc = true; + return c; +} + +coding *coding::findBySpec(int B, int H, int S, int D) +{ + if (B < 1 || B > B_MAX) + return nullptr; + if (H < 1 || H > 256) + return nullptr; + if (S < 0 || S > 2) + return nullptr; + if (D < 0 || D > 1) + return nullptr; + return findBySpec(CODING_SPEC(B, H, S, D)); +} + +void coding::free() +{ + if (isMalloc) + { + ::free(this); + } +} + +void coding_method::reset(value_stream *state) +{ + assert(state->rp == state->rplimit); // not in mid-stream, please + // assert(this == vs0.cm); + state[0] = vs0; + if (uValues != nullptr) + { + uValues->reset(state->helper()); + } +} + +uint32_t coding::parse(byte *&rp, int B, int H) +{ + int L = 256 - H; + byte *ptr = rp; + // hand peel the i==0 part of the loop: + uint32_t b_i = *ptr++ & 0xFF; + if (B == 1 || b_i < (uint32_t)L) + { + rp = ptr; + return b_i; + } + uint32_t sum = b_i; + uint32_t H_i = H; + assert(B <= B_MAX); + for (int i = 2; i <= B_MAX; i++) + { // easy for compilers to unroll if desired + b_i = *ptr++ & 0xFF; + sum += b_i * H_i; + if (i == B || b_i < (uint32_t)L) + { + rp = ptr; + return sum; + } + H_i *= H; + } + assert(false); + return 0; +} + +uint32_t coding::parse_lgH(byte *&rp, int B, int H, int lgH) +{ + assert(H == (1 << lgH)); + int L = 256 - (1 << lgH); + byte *ptr = rp; + // hand peel the i==0 part of the loop: + uint32_t b_i = *ptr++ & 0xFF; + if (B == 1 || b_i < (uint32_t)L) + { + rp = ptr; + return b_i; + } + uint32_t sum = b_i; + uint32_t lg_H_i = lgH; + assert(B <= B_MAX); + for (int i = 2; i <= B_MAX; i++) + { // easy for compilers to unroll if desired + b_i = *ptr++ & 0xFF; + sum += b_i << lg_H_i; + if (i == B || b_i < (uint32_t)L) + { + rp = ptr; + return sum; + } + lg_H_i += lgH; + } + assert(false); + return 0; +} + +static const char ERB[] = "EOF reading band"; + +void coding::parseMultiple(byte *&rp, int N, byte *limit, int B, int H) +{ + if (N < 0) + { + unpack_abort("bad value count"); + return; + } + byte *ptr = rp; + if (B == 1 || H == 256) + { + size_t len = (size_t)N * B; + if (len / B != (size_t)N || ptr + len > limit) + { + unpack_abort(ERB); + return; + } + rp = ptr + len; + return; + } + // Note: We assume rp has enough zero-padding. + int L = 256 - H; + int n = B; + while (N > 0) + { + ptr += 1; + if (--n == 0) + { + // end of encoding at B bytes, regardless of byte value + } + else + { + int b = (ptr[-1] & 0xFF); + if (b >= L) + { + // keep going, unless we find a byte < L + continue; + } + } + // found the last byte + N -= 1; + n = B; // reset length counter + // do an error check here + if (ptr > limit) + { + unpack_abort(ERB); + return; + } + } + rp = ptr; + return; +} + +bool value_stream::hasHelper() +{ + // If my coding method is a pop-style method, + // then I need a second value stream to transmit + // unfavored values. + // This can be determined by examining fValues. + return cm->fValues != nullptr; +} + +void value_stream::init(byte *rp_, byte *rplimit_, coding *defc) +{ + rp = rp_; + rplimit = rplimit_; + sum = 0; + cm = nullptr; // no need in the simple case + setCoding(defc); +} + +void value_stream::setCoding(coding *defc) +{ + if (defc == nullptr) + { + unpack_abort("bad coding"); + defc = coding::findByIndex(_meta_canon_min); // random pick for recovery + } + + c = (*defc); + + // choose cmk + cmk = cmk_ERROR; + switch (c.spec) + { + case BYTE1_spec: + cmk = cmk_BYTE1; + break; + case CHAR3_spec: + cmk = cmk_CHAR3; + break; + case UNSIGNED5_spec: + cmk = cmk_UNSIGNED5; + break; + case DELTA5_spec: + cmk = cmk_DELTA5; + break; + case BCI5_spec: + cmk = cmk_BCI5; + break; + case BRANCH5_spec: + cmk = cmk_BRANCH5; + break; + default: + if (c.D() == 0) + { + switch (c.S()) + { + case 0: + cmk = cmk_BHS0; + break; + case 1: + cmk = cmk_BHS1; + break; + default: + cmk = cmk_BHS; + break; + } + } + else + { + if (c.S() == 1) + { + if (c.isFullRange) + cmk = cmk_BHS1D1full; + if (c.isSubrange) + cmk = cmk_BHS1D1sub; + } + if (cmk == cmk_ERROR) + cmk = cmk_BHSD1; + } + } +} + +static int getPopValue(value_stream *self, uint32_t uval) +{ + if (uval > 0) + { + // note that the initial parse performed a range check + assert(uval <= (uint32_t)self->cm->fVlength); + return self->cm->fValues[uval - 1]; + } + else + { + // take an unfavored value + return self->helper()->getInt(); + } +} + +int coding::sumInUnsignedRange(int x, int y) +{ + assert(isSubrange); + int range = (int)(umax + 1); + assert(range > 0); + x += y; + if (x != (int)((int64_t)(x - y) + (int64_t)y)) + { + // 32-bit overflow interferes with range reduction. + // Back off from the overflow by adding a multiple of range: + if (x < 0) + { + x -= range; + assert(x >= 0); + } + else + { + x += range; + assert(x < 0); + } + } + if (x < 0) + { + x += range; + if (x >= 0) + return x; + } + else if (x >= range) + { + x -= range; + if (x < range) + return x; + } + else + { + // in range + return x; + } + // do it the hard way + x %= range; + if (x < 0) + x += range; + return x; +} + +static int getDeltaValue(value_stream *self, uint32_t uval, bool isSubrange) +{ + assert((uint32_t)(self->c.isSubrange) == (uint32_t)isSubrange); + assert(self->c.isSubrange | self->c.isFullRange); + if (isSubrange) + return self->sum = self->c.sumInUnsignedRange(self->sum, (int)uval); + else + return self->sum += (int)uval; +} + +bool value_stream::hasValue() +{ + if (rp < rplimit) + return true; + if (cm == nullptr) + return false; + if (cm->next == nullptr) + return false; + cm->next->reset(this); + return hasValue(); +} + +int value_stream::getInt() +{ + if (rp >= rplimit) + { + // Advance to next coding segment. + if (rp > rplimit || cm == nullptr || cm->next == nullptr) + { + // Must perform this check and throw an exception on bad input. + unpack_abort(ERB); + return 0; + } + cm->next->reset(this); + return getInt(); + } + + CODING_PRIVATE(c.spec); + uint32_t uval; + enum + { + B5 = 5, + B3 = 3, + H128 = 128, + H64 = 64, + H4 = 4 + }; + switch (cmk) + { + case cmk_BHS: + assert(D == 0); + uval = coding::parse(rp, B, H); + if (S == 0) + return (int)uval; + return decode_sign(S, uval); + + case cmk_BHS0: + assert(S == 0 && D == 0); + uval = coding::parse(rp, B, H); + return (int)uval; + + case cmk_BHS1: + assert(S == 1 && D == 0); + uval = coding::parse(rp, B, H); + return DECODE_SIGN_S1(uval); + + case cmk_BYTE1: + assert(c.spec == BYTE1_spec); + assert(B == 1 && H == 256 && S == 0 && D == 0); + return *rp++ & 0xFF; + + case cmk_CHAR3: + assert(c.spec == CHAR3_spec); + assert(B == B3 && H == H128 && S == 0 && D == 0); + return coding::parse_lgH(rp, B3, H128, 7); + + case cmk_UNSIGNED5: + assert(c.spec == UNSIGNED5_spec); + assert(B == B5 && H == H64 && S == 0 && D == 0); + return coding::parse_lgH(rp, B5, H64, 6); + + case cmk_BHSD1: + assert(D == 1); + uval = coding::parse(rp, B, H); + if (S != 0) + uval = (uint32_t)decode_sign(S, uval); + return getDeltaValue(this, uval, (bool)c.isSubrange); + + case cmk_BHS1D1full: + assert(S == 1 && D == 1 && c.isFullRange); + uval = coding::parse(rp, B, H); + uval = (uint32_t)DECODE_SIGN_S1(uval); + return getDeltaValue(this, uval, false); + + case cmk_BHS1D1sub: + assert(S == 1 && D == 1 && c.isSubrange); + uval = coding::parse(rp, B, H); + uval = (uint32_t)DECODE_SIGN_S1(uval); + return getDeltaValue(this, uval, true); + + case cmk_DELTA5: + assert(c.spec == DELTA5_spec); + assert(B == B5 && H == H64 && S == 1 && D == 1 && c.isFullRange); + uval = coding::parse_lgH(rp, B5, H64, 6); + sum += DECODE_SIGN_S1(uval); + return sum; + + case cmk_BCI5: + assert(c.spec == BCI5_spec); + assert(B == B5 && H == H4 && S == 0 && D == 0); + return coding::parse_lgH(rp, B5, H4, 2); + + case cmk_BRANCH5: + assert(c.spec == BRANCH5_spec); + assert(B == B5 && H == H4 && S == 2 && D == 0); + uval = coding::parse_lgH(rp, B5, H4, 2); + return decode_sign(S, uval); + + case cmk_pop: + uval = coding::parse(rp, B, H); + if (S != 0) + { + uval = (uint32_t)decode_sign(S, uval); + } + if (D != 0) + { + assert(c.isSubrange | c.isFullRange); + if (c.isSubrange) + sum = c.sumInUnsignedRange(sum, (int)uval); + else + sum += (int)uval; + uval = (uint32_t)sum; + } + return getPopValue(this, uval); + + case cmk_pop_BHS0: + assert(S == 0 && D == 0); + uval = coding::parse(rp, B, H); + return getPopValue(this, uval); + + case cmk_pop_BYTE1: + assert(c.spec == BYTE1_spec); + assert(B == 1 && H == 256 && S == 0 && D == 0); + return getPopValue(this, *rp++ & 0xFF); + + default: + break; + } + assert(false); + return 0; +} + +static int moreCentral(int x, int y) +{ // used to find end of Pop.{F} + // Suggested implementation from the Pack200 specification: + uint32_t kx = (x >> 31) ^ (x << 1); + uint32_t ky = (y >> 31) ^ (y << 1); + return (kx < ky ? x : y); +} +// static maybe_inline +// int moreCentral2(int x, int y, int min) { +// // Strict implementation of buggy 150.7 specification. +// // The bug is that the spec. says absolute-value ties are broken +// // in favor of positive numbers, but the suggested implementation +// // (also mentioned in the spec.) breaks ties in favor of negative numbers. +// if ((x + y) != 0) +// return min; +// else +// // return the other value, which breaks a tie in the positive direction +// return (x > y)? x: y; +//} + +static const byte *no_meta[] = {nullptr}; +#define NO_META (*(byte **)no_meta) +enum +{ + POP_FAVORED_N = -2 +}; + +// mode bits +#define DISABLE_RUN 1 // used immediately inside ACodee +#define DISABLE_POP 2 // used recursively in all pop sub-bands + +// This function knows all about meta-coding. +void coding_method::init(byte *&band_rp, byte *band_limit, byte *&meta_rp, int mode, + coding *defc, int N, intlist *valueSink) +{ + assert(N != 0); + + assert(u != nullptr); // must be pre-initialized + // if (u == nullptr) u = unpacker::current(); // expensive + + int op = (meta_rp == nullptr) ? _meta_default : (*meta_rp++ & 0xFF); + coding *foundc = nullptr; + coding *to_free = nullptr; + + if (op == _meta_default) + { + foundc = defc; + // and fall through + } + else if (op >= _meta_canon_min && op <= _meta_canon_max) + { + foundc = coding::findByIndex(op); + // and fall through + } + else if (op == _meta_arb) + { + int args = (*meta_rp++ & 0xFF); + // args = (D:[0..1] + 2*S[0..2] + 8*(B:[1..5]-1)) + int D = ((args >> 0) & 1); + int S = ((args >> 1) & 3); + int B = ((args >> 3) & -1) + 1; + // & (H[1..256]-1) + int H = (*meta_rp++ & 0xFF) + 1; + foundc = coding::findBySpec(B, H, S, D); + to_free = foundc; // findBySpec may dynamically allocate + if (foundc == nullptr) + { + unpack_abort("illegal arbitrary coding"); + return; + } + // and fall through + } + else if (op >= _meta_run && op < _meta_pop) + { + int args = (op - _meta_run); + // args: KX:[0..3] + 4*(KBFlag:[0..1]) + 8*(ABDef:[0..2]) + int KX = ((args >> 0) & 3); + int KBFlag = ((args >> 2) & 1); + int ABDef = ((args >> 3) & -1); + assert(ABDef <= 2); + // & KB: one of [0..255] if KBFlag=1 + int KB = (!KBFlag ? 3 : (*meta_rp++ & 0xFF)); + int K = (KB + 1) << (KX * 4); + int N2 = (N >= 0) ? N - K : N; + if (N == 0 || (N2 <= 0 && N2 != N)) + { + unpack_abort("illegal run encoding"); + } + if ((mode & DISABLE_RUN) != 0) + { + unpack_abort("illegal nested run encoding"); + } + + // & Enc{ ACode } if ADef=0 (ABDef != 1) + // No direct nesting of 'run' in ACode, but in BCode it's OK. + int disRun = mode | DISABLE_RUN; + if (ABDef == 1) + { + this->init(band_rp, band_limit, NO_META, disRun, defc, K, valueSink); + } + else + { + this->init(band_rp, band_limit, meta_rp, disRun, defc, K, valueSink); + } + + // & Enc{ BCode } if BDef=0 (ABDef != 2) + coding_method *tail = U_NEW(coding_method, 1); + if (!tail) + return; + tail->u = u; + + // The 'run' codings may be nested indirectly via 'pop' codings. + // This means that this->next may already be filled in, if + // ACode was of type 'pop' with a 'run' token coding. + // No problem: Just chain the upcoming BCode onto the end. + for (coding_method *self = this;; self = self->next) + { + if (self->next == nullptr) + { + self->next = tail; + break; + } + } + + if (ABDef == 2) + { + tail->init(band_rp, band_limit, NO_META, mode, defc, N2, valueSink); + } + else + { + tail->init(band_rp, band_limit, meta_rp, mode, defc, N2, valueSink); + } + // Note: The preceding calls to init should be tail-recursive. + + return; // done; no falling through + } + else if (op >= _meta_pop && op < _meta_limit) + { + int args = (op - _meta_pop); + // args: (FDef:[0..1]) + 2*UDef:[0..1] + 4*(TDefL:[0..11]) + int FDef = ((args >> 0) & 1); + int UDef = ((args >> 1) & 1); + int TDefL = ((args >> 2) & -1); + assert(TDefL <= 11); + int TDef = (TDefL > 0); + int TL = (TDefL <= 6) ? (2 << TDefL) : (256 - (4 << (11 - TDefL))); + int TH = (256 - TL); + if (N <= 0) + { + unpack_abort("illegal pop encoding"); + } + if ((mode & DISABLE_POP) != 0) + { + unpack_abort("illegal nested pop encoding"); + } + + // No indirect nesting of 'pop', but 'run' is OK. + int disPop = DISABLE_POP; + + // & Enc{ FCode } if FDef=0 + int FN = POP_FAVORED_N; + assert(valueSink == nullptr); + intlist fValueSink; + fValueSink.init(); + coding_method fval; + BYTES_OF(fval).clear(); + fval.u = u; + if (FDef != 0) + { + fval.init(band_rp, band_limit, NO_META, disPop, defc, FN, &fValueSink); + } + else + { + fval.init(band_rp, band_limit, meta_rp, disPop, defc, FN, &fValueSink); + } + bytes fvbuf; + fValues = (u->saveTo(fvbuf, fValueSink.b), (int *)fvbuf.ptr); + fVlength = fValueSink.length(); // i.e., the parameter K + fValueSink.free(); + + // Skip the first {F} run in all subsequent passes. + // The next call to this->init(...) will set vs0.rp to point after the {F}. + + // & Enc{ TCode } if TDef=0 (TDefL==0) + if (TDef != 0) + { + coding *tcode = coding::findBySpec(1, 256); // BYTE1 + // find the most narrowly sufficient code: + for (int B = 2; B <= B_MAX; B++) + { + if (fVlength <= tcode->umax) + break; // found it + tcode->free(); + tcode = coding::findBySpec(B, TH); + if (!tcode) + return; + } + if (!(fVlength <= tcode->umax)) + { + unpack_abort("pop.L value too small"); + } + this->init(band_rp, band_limit, NO_META, disPop, tcode, N, nullptr); + tcode->free(); + } + else + { + this->init(band_rp, band_limit, meta_rp, disPop, defc, N, nullptr); + } + + // Count the number of zero tokens right now. + // Also verify that they are in bounds. + int UN = 0; // one {U} for each zero in {T} + value_stream vs = vs0; + for (int i = 0; i < N; i++) + { + uint32_t val = vs.getInt(); + if (val == 0) + UN += 1; + if (!(val <= (uint32_t)fVlength)) + { + unpack_abort("pop token out of range"); + } + } + vs.done(); + + // & Enc{ UCode } if UDef=0 + if (UN != 0) + { + uValues = U_NEW(coding_method, 1); + if (uValues == nullptr) + return; + uValues->u = u; + if (UDef != 0) + { + uValues->init(band_rp, band_limit, NO_META, disPop, defc, UN, nullptr); + } + else + { + uValues->init(band_rp, band_limit, meta_rp, disPop, defc, UN, nullptr); + } + } + else + { + if (UDef == 0) + { + int uop = (*meta_rp++ & 0xFF); + if (uop > _meta_canon_max) + // %%% Spec. requires the more strict (uop != _meta_default). + unpack_abort("bad meta-coding for empty pop/U"); + } + } + + // Bug fix for 6259542 + // Last of all, adjust vs0.cmk to the 'pop' flavor + for (coding_method *self = this; self != nullptr; self = self->next) + { + coding_method_kind cmk2 = cmk_pop; + switch (self->vs0.cmk) + { + case cmk_BHS0: + cmk2 = cmk_pop_BHS0; + break; + case cmk_BYTE1: + cmk2 = cmk_pop_BYTE1; + break; + default: + break; + } + self->vs0.cmk = cmk2; + if (self != this) + { + assert(self->fValues == nullptr); // no double init + self->fValues = this->fValues; + self->fVlength = this->fVlength; + assert(self->uValues == nullptr); // must stay nullptr + } + } + + return; // done; no falling through + } + else + { + unpack_abort("bad meta-coding"); + } + + // Common code here skips a series of values with one coding. + assert(foundc != nullptr); + + assert(vs0.cmk == cmk_ERROR); // no garbage, please + assert(vs0.rp == nullptr); // no garbage, please + assert(vs0.rplimit == nullptr); // no garbage, please + assert(vs0.sum == 0); // no garbage, please + + vs0.init(band_rp, band_limit, foundc); + + // Done with foundc. Free if necessary. + if (to_free != nullptr) + { + to_free->free(); + to_free = nullptr; + } + foundc = nullptr; + + coding &c = vs0.c; + CODING_PRIVATE(c.spec); + // assert sane N + assert((uint32_t)N < INT_MAX_VALUE || N == POP_FAVORED_N); + + // Look at the values, or at least skip over them quickly. + if (valueSink == nullptr) + { + // Skip and ignore values in the first pass. + c.parseMultiple(band_rp, N, band_limit, B, H); + } + else if (N >= 0) + { + // Pop coding, {F} sequence, initial run of values... + assert((mode & DISABLE_POP) != 0); + value_stream vs = vs0; + for (int n = 0; n < N; n++) + { + int val = vs.getInt(); + valueSink->add(val); + } + band_rp = vs.rp; + } + else + { + // Pop coding, {F} sequence, final run of values... + assert((mode & DISABLE_POP) != 0); + assert(N == POP_FAVORED_N); + int min = INT_MIN_VALUE; // farthest from the center + // min2 is based on the buggy specification of centrality in version 150.7 + // no known implementations transmit this value, but just in case... + // int min2 = INT_MIN_VALUE; + int last = 0; + // if there were initial runs, find the potential sentinels in them: + for (int i = 0; i < valueSink->length(); i++) + { + last = valueSink->get(i); + min = moreCentral(min, last); + // min2 = moreCentral2(min2, last, min); + } + value_stream vs = vs0; + for (;;) + { + int val = vs.getInt(); + if (valueSink->length() > 0 && (val == last || val == min)) //|| val == min2 + break; + valueSink->add(val); + last = val; + min = moreCentral(min, last); + // min2 = moreCentral2(min2, last, min); + } + band_rp = vs.rp; + } + + // Get an accurate upper limit now. + vs0.rplimit = band_rp; + vs0.cm = this; + + return; // success +} + +coding basic_codings[] = { + // This one is not a usable irregular coding, but is used by cp_Utf8_chars. + CODING_INIT(3, 128, 0, 0), + + // Fixed-length codings: + CODING_INIT(1, 256, 0, 0), CODING_INIT(1, 256, 1, 0), CODING_INIT(1, 256, 0, 1), + CODING_INIT(1, 256, 1, 1), CODING_INIT(2, 256, 0, 0), CODING_INIT(2, 256, 1, 0), + CODING_INIT(2, 256, 0, 1), CODING_INIT(2, 256, 1, 1), CODING_INIT(3, 256, 0, 0), + CODING_INIT(3, 256, 1, 0), CODING_INIT(3, 256, 0, 1), CODING_INIT(3, 256, 1, 1), + CODING_INIT(4, 256, 0, 0), CODING_INIT(4, 256, 1, 0), CODING_INIT(4, 256, 0, 1), + CODING_INIT(4, 256, 1, 1), + + // Full-range variable-length codings: + CODING_INIT(5, 4, 0, 0), CODING_INIT(5, 4, 1, 0), CODING_INIT(5, 4, 2, 0), + CODING_INIT(5, 16, 0, 0), CODING_INIT(5, 16, 1, 0), CODING_INIT(5, 16, 2, 0), + CODING_INIT(5, 32, 0, 0), CODING_INIT(5, 32, 1, 0), CODING_INIT(5, 32, 2, 0), + CODING_INIT(5, 64, 0, 0), CODING_INIT(5, 64, 1, 0), CODING_INIT(5, 64, 2, 0), + CODING_INIT(5, 128, 0, 0), CODING_INIT(5, 128, 1, 0), CODING_INIT(5, 128, 2, 0), + CODING_INIT(5, 4, 0, 1), CODING_INIT(5, 4, 1, 1), CODING_INIT(5, 4, 2, 1), + CODING_INIT(5, 16, 0, 1), CODING_INIT(5, 16, 1, 1), CODING_INIT(5, 16, 2, 1), + CODING_INIT(5, 32, 0, 1), CODING_INIT(5, 32, 1, 1), CODING_INIT(5, 32, 2, 1), + CODING_INIT(5, 64, 0, 1), CODING_INIT(5, 64, 1, 1), CODING_INIT(5, 64, 2, 1), + CODING_INIT(5, 128, 0, 1), CODING_INIT(5, 128, 1, 1), CODING_INIT(5, 128, 2, 1), + + // Variable length subrange codings: + CODING_INIT(2, 192, 0, 0), CODING_INIT(2, 224, 0, 0), CODING_INIT(2, 240, 0, 0), + CODING_INIT(2, 248, 0, 0), CODING_INIT(2, 252, 0, 0), CODING_INIT(2, 8, 0, 1), + CODING_INIT(2, 8, 1, 1), CODING_INIT(2, 16, 0, 1), CODING_INIT(2, 16, 1, 1), + CODING_INIT(2, 32, 0, 1), CODING_INIT(2, 32, 1, 1), CODING_INIT(2, 64, 0, 1), + CODING_INIT(2, 64, 1, 1), CODING_INIT(2, 128, 0, 1), CODING_INIT(2, 128, 1, 1), + CODING_INIT(2, 192, 0, 1), CODING_INIT(2, 192, 1, 1), CODING_INIT(2, 224, 0, 1), + CODING_INIT(2, 224, 1, 1), CODING_INIT(2, 240, 0, 1), CODING_INIT(2, 240, 1, 1), + CODING_INIT(2, 248, 0, 1), CODING_INIT(2, 248, 1, 1), CODING_INIT(3, 192, 0, 0), + CODING_INIT(3, 224, 0, 0), CODING_INIT(3, 240, 0, 0), CODING_INIT(3, 248, 0, 0), + CODING_INIT(3, 252, 0, 0), CODING_INIT(3, 8, 0, 1), CODING_INIT(3, 8, 1, 1), + CODING_INIT(3, 16, 0, 1), CODING_INIT(3, 16, 1, 1), CODING_INIT(3, 32, 0, 1), + CODING_INIT(3, 32, 1, 1), CODING_INIT(3, 64, 0, 1), CODING_INIT(3, 64, 1, 1), + CODING_INIT(3, 128, 0, 1), CODING_INIT(3, 128, 1, 1), CODING_INIT(3, 192, 0, 1), + CODING_INIT(3, 192, 1, 1), CODING_INIT(3, 224, 0, 1), CODING_INIT(3, 224, 1, 1), + CODING_INIT(3, 240, 0, 1), CODING_INIT(3, 240, 1, 1), CODING_INIT(3, 248, 0, 1), + CODING_INIT(3, 248, 1, 1), CODING_INIT(4, 192, 0, 0), CODING_INIT(4, 224, 0, 0), + CODING_INIT(4, 240, 0, 0), CODING_INIT(4, 248, 0, 0), CODING_INIT(4, 252, 0, 0), + CODING_INIT(4, 8, 0, 1), CODING_INIT(4, 8, 1, 1), CODING_INIT(4, 16, 0, 1), + CODING_INIT(4, 16, 1, 1), CODING_INIT(4, 32, 0, 1), CODING_INIT(4, 32, 1, 1), + CODING_INIT(4, 64, 0, 1), CODING_INIT(4, 64, 1, 1), CODING_INIT(4, 128, 0, 1), + CODING_INIT(4, 128, 1, 1), CODING_INIT(4, 192, 0, 1), CODING_INIT(4, 192, 1, 1), + CODING_INIT(4, 224, 0, 1), CODING_INIT(4, 224, 1, 1), CODING_INIT(4, 240, 0, 1), + CODING_INIT(4, 240, 1, 1), CODING_INIT(4, 248, 0, 1), CODING_INIT(4, 248, 1, 1), + CODING_INIT(0, 0, 0, 0)}; +#define BASIC_INDEX_LIMIT (int)(sizeof(basic_codings) / sizeof(basic_codings[0]) - 1) + +coding *coding::findByIndex(int idx) +{ + int index_limit = BASIC_INDEX_LIMIT; + assert(_meta_canon_min == 1 && _meta_canon_max + 1 == index_limit); + + if (idx >= _meta_canon_min && idx <= _meta_canon_max) + return basic_codings[idx].init(); + else + return nullptr; +} diff --git a/depends/pack200/src/coding.h b/depends/pack200/src/coding.h new file mode 100644 index 00000000..f9bd6ca2 --- /dev/null +++ b/depends/pack200/src/coding.h @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2002, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +struct unpacker; + +#define INT_MAX_VALUE ((int)0x7FFFFFFF) +#define INT_MIN_VALUE ((int)0x80000000) + +#define CODING_SPEC(B, H, S, D) ((B) << 20 | (H) << 8 | (S) << 4 | (D) << 0) +#define CODING_B(x) ((x) >> 20 & 0xF) +#define CODING_H(x) ((x) >> 8 & 0xFFF) +#define CODING_S(x) ((x) >> 4 & 0xF) +#define CODING_D(x) ((x) >> 0 & 0xF) + +#define CODING_INIT(B, H, S, D) \ + { \ + CODING_SPEC(B, H, S, D), 0, 0, 0, 0, 0, 0, 0, 0 \ + } + +// For debugging purposes, some compilers do not like this and will complain. +// #define long do_not_use_C_long_types_use_jlong_or_int +// Use of the type "long" is problematic, do not use it. + +struct coding +{ + int spec; // B,H,S,D + + // Handy values derived from the spec: + int B() + { + return CODING_B(spec); + } + int H() + { + return CODING_H(spec); + } + int S() + { + return CODING_S(spec); + } + int D() + { + return CODING_D(spec); + } + int L() + { + return 256 - CODING_H(spec); + } + int min, max; + int umin, umax; + char isSigned, isSubrange, isFullRange, isMalloc; + + coding *init(); // returns self or nullptr if error + coding *initFrom(int spec_) + { + assert(this->spec == 0); + this->spec = spec_; + return init(); + } + + static coding *findBySpec(int spec); + static coding *findBySpec(int B, int H, int S = 0, int D = 0); + static coding *findByIndex(int irregularCodingIndex); + + static uint32_t parse(byte *&rp, int B, int H); + static uint32_t parse_lgH(byte *&rp, int B, int H, int lgH); + static void parseMultiple(byte *&rp, int N, byte *limit, int B, int H); + + uint32_t parse(byte *&rp) + { + return parse(rp, CODING_B(spec), CODING_H(spec)); + } + void parseMultiple(byte *&rp, int N, byte *limit) + { + parseMultiple(rp, N, limit, CODING_B(spec), CODING_H(spec)); + } + + bool canRepresent(int x) + { + return (x >= min && x <= max); + } + bool canRepresentUnsigned(int x) + { + return (x >= umin && x <= umax); + } + + int sumInUnsignedRange(int x, int y); + + int readFrom(byte *&rpVar, int *dbase); + void readArrayFrom(byte *&rpVar, int *dbase, int length, int *values); + void skipArrayFrom(byte *&rpVar, int length) + { + readArrayFrom(rpVar, (int *)NULL, length, (int *)NULL); + } + + void free(); // free self if isMalloc +}; + +enum coding_method_kind +{ + cmk_ERROR, + cmk_BHS, + cmk_BHS0, + cmk_BHS1, + cmk_BHSD1, + cmk_BHS1D1full, // isFullRange + cmk_BHS1D1sub, // isSubRange + + // special cases hand-optimized (~50% of all decoded values) + cmk_BYTE1, //(1,256) 6% + cmk_CHAR3, //(3,128) 7% + cmk_UNSIGNED5, //(5,64) 13% + cmk_DELTA5, //(5,64,1,1) 5% + cmk_BCI5, //(5,4) 18% + cmk_BRANCH5, //(5,4,2) 4% + // cmk_UNSIGNED5H16, //(5,16) 5% + // cmk_UNSIGNED2H4, //(2,4) 6% + // cmk_DELTA4H8, //(4,8,1,1) 10% + // cmk_DELTA3H16, //(3,16,1,1) 9% + cmk_BHS_LIMIT, + cmk_pop, + cmk_pop_BHS0, + cmk_pop_BYTE1, + cmk_pop_LIMIT, + cmk_LIMIT +}; + +enum +{ + BYTE1_spec = CODING_SPEC(1, 256, 0, 0), + CHAR3_spec = CODING_SPEC(3, 128, 0, 0), + UNSIGNED4_spec = CODING_SPEC(4, 256, 0, 0), + UNSIGNED5_spec = CODING_SPEC(5, 64, 0, 0), + SIGNED5_spec = CODING_SPEC(5, 64, 1, 0), + DELTA5_spec = CODING_SPEC(5, 64, 1, 1), + UDELTA5_spec = CODING_SPEC(5, 64, 0, 1), + MDELTA5_spec = CODING_SPEC(5, 64, 2, 1), + BCI5_spec = CODING_SPEC(5, 4, 0, 0), + BRANCH5_spec = CODING_SPEC(5, 4, 2, 0) +}; + +enum +{ + B_MAX = 5, + C_SLOP = B_MAX * 10 +}; + +struct coding_method; + +// iterator under the control of a meta-coding +struct value_stream +{ + // current coding of values or values + coding c; // B,H,S,D,etc. + coding_method_kind cmk; // type of decoding needed + byte *rp; // read pointer + byte *rplimit; // final value of read pointer + int sum; // partial sum of all values so far (D=1 only) + coding_method *cm; // coding method that defines this stream + + void init(byte *band_rp, byte *band_limit, coding *defc); + void init(byte *band_rp, byte *band_limit, int spec) + { + init(band_rp, band_limit, coding::findBySpec(spec)); + } + + void setCoding(coding *c); + void setCoding(int spec) + { + setCoding(coding::findBySpec(spec)); + } + + // Parse and decode a single value. + int getInt(); + + // Parse and decode a single byte, with no error checks. + int getByte() + { + assert(cmk == cmk_BYTE1); + assert(rp < rplimit); + return *rp++ & 0xFF; + } + + // Used only for asserts. + bool hasValue(); + + void done() + { + assert(!hasValue()); + } + + // Sometimes a value stream has an auxiliary (but there are never two). + value_stream *helper() + { + assert(hasHelper()); + return this + 1; + } + bool hasHelper(); +}; + +struct coding_method +{ + value_stream vs0; // initial state snapshot (vs.meta==this) + + coding_method *next; // what to do when we run out of bytes + + // these fields are used for pop codes only: + int *fValues; // favored value array + int fVlength; // maximum favored value token + coding_method *uValues; // unfavored value stream + + // pointer to outer unpacker, for error checks etc. + unpacker *u; + + // Initialize a value stream. + void reset(value_stream *state); + + // Parse a band header, size a band, and initialize for further action. + // band_rp advances (but not past band_limit), and meta_rp advances. + // The mode gives context, such as "inside a pop". + // The defc and N are the incoming parameters to a meta-coding. + // The value sink is used to collect output values, when desired. + void init(byte *&band_rp, byte *band_limit, byte *&meta_rp, int mode, coding *defc, int N, + intlist *valueSink); +}; diff --git a/depends/pack200/src/constants.h b/depends/pack200/src/constants.h new file mode 100644 index 00000000..2cc14b7d --- /dev/null +++ b/depends/pack200/src/constants.h @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2001, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + Java Class Version numbers history + 1.0 to 1.3.X 45,3 + 1.4 to 1.4.X 46,0 + 1.5 to 1.5.X 49,0 + 1.6 to 1.5.x 50,0 NOTE Assumed for now +*/ + +// classfile constants +#define JAVA_MAGIC 0xCAFEBABE +#define JAVA_MIN_MAJOR_VERSION 45 +#define JAVA_MIN_MINOR_VERSION 3 +#define JAVA5_MAX_MAJOR_VERSION 49 +#define JAVA5_MAX_MINOR_VERSION 0 +// NOTE: Assume for now +#define JAVA6_MAX_MAJOR_VERSION 50 +#define JAVA6_MAX_MINOR_VERSION 0 + +// package file constants +#define JAVA_PACKAGE_MAGIC 0xCAFED00D +#define JAVA5_PACKAGE_MAJOR_VERSION 150 +#define JAVA5_PACKAGE_MINOR_VERSION 7 + +#define JAVA6_PACKAGE_MAJOR_VERSION 160 +#define JAVA6_PACKAGE_MINOR_VERSION 1 + +// magic number for gzip streams (for processing pack200-gzip data) +#define GZIP_MAGIC 0x1F8B0800 +#define GZIP_MAGIC_MASK 0xFFFFFF00 // last \bchar\b is variable "flg" field + +enum +{ + CONSTANT_None, + CONSTANT_Utf8, + CONSTANT_unused2, /* unused, was Unicode */ + CONSTANT_Integer, + CONSTANT_Float, + CONSTANT_Long, + CONSTANT_Double, + CONSTANT_Class, + CONSTANT_String, + CONSTANT_Fieldref, + CONSTANT_Methodref, + CONSTANT_InterfaceMethodref, + CONSTANT_NameandType, + CONSTANT_Signature = 13, + CONSTANT_All = 14, + CONSTANT_Limit = 15, + CONSTANT_NONE = 0, + CONSTANT_Literal = 20, // pseudo-tag for debugging + CONSTANT_Member = 21, // pseudo-tag for debugging + SUBINDEX_BIT = 64, // combined with CONSTANT_xxx for ixTag + ACC_STATIC = 0x0008, + ACC_IC_LONG_FORM = (1 << 16), // for ic_flags + CLASS_ATTR_SourceFile = 17, + CLASS_ATTR_EnclosingMethod = 18, + CLASS_ATTR_InnerClasses = 23, + CLASS_ATTR_ClassFile_version = 24, + FIELD_ATTR_ConstantValue = 17, + METHOD_ATTR_Code = 17, + METHOD_ATTR_Exceptions = 18, + METHOD_ATTR_RuntimeVisibleParameterAnnotations = 23, + METHOD_ATTR_RuntimeInvisibleParameterAnnotations = 24, + METHOD_ATTR_AnnotationDefault = 25, + CODE_ATTR_StackMapTable = 0, + CODE_ATTR_LineNumberTable = 1, + CODE_ATTR_LocalVariableTable = 2, + CODE_ATTR_LocalVariableTypeTable = 3, + // X_ATTR_Synthetic = 12, // ACC_SYNTHETIC; not predefined + X_ATTR_Signature = 19, + X_ATTR_Deprecated = 20, + X_ATTR_RuntimeVisibleAnnotations = 21, + X_ATTR_RuntimeInvisibleAnnotations = 22, + X_ATTR_OVERFLOW = 16, + X_ATTR_LIMIT_NO_FLAGS_HI = 32, + X_ATTR_LIMIT_FLAGS_HI = 63, + +#define O_ATTR_DO(F) \ + F(X_ATTR_OVERFLOW, 01) \ + /*(end)*/ +#define X_ATTR_DO(F) \ + O_ATTR_DO(F) F(X_ATTR_Signature, Signature) F(X_ATTR_Deprecated, Deprecated) \ + F(X_ATTR_RuntimeVisibleAnnotations, RuntimeVisibleAnnotations) \ + F(X_ATTR_RuntimeInvisibleAnnotations, RuntimeInvisibleAnnotations) \ + /*F(X_ATTR_Synthetic,Synthetic)*/ \ + /*(end)*/ +#define CLASS_ATTR_DO(F) \ + F(CLASS_ATTR_SourceFile, SourceFile) F(CLASS_ATTR_InnerClasses, InnerClasses) \ + F(CLASS_ATTR_EnclosingMethod, EnclosingMethod) F(CLASS_ATTR_ClassFile_version, 02) \ + /*(end)*/ +#define FIELD_ATTR_DO(F) \ + F(FIELD_ATTR_ConstantValue, ConstantValue) \ + /*(end)*/ +#define METHOD_ATTR_DO(F) \ + F(METHOD_ATTR_Code, Code) F(METHOD_ATTR_Exceptions, Exceptions) \ + F(METHOD_ATTR_RuntimeVisibleParameterAnnotations, RuntimeVisibleParameterAnnotations) \ + F(METHOD_ATTR_RuntimeInvisibleParameterAnnotations, \ + RuntimeInvisibleParameterAnnotations) \ + F(METHOD_ATTR_AnnotationDefault, AnnotationDefault) \ + /*(end)*/ +#define CODE_ATTR_DO(F) \ + F(CODE_ATTR_StackMapTable, StackMapTable) F(CODE_ATTR_LineNumberTable, LineNumberTable) \ + F(CODE_ATTR_LocalVariableTable, LocalVariableTable) \ + F(CODE_ATTR_LocalVariableTypeTable, LocalVariableTypeTable) \ + /*(end)*/ +#define ALL_ATTR_DO(F) \ + X_ATTR_DO(F) CLASS_ATTR_DO(F) FIELD_ATTR_DO(F) METHOD_ATTR_DO(F) CODE_ATTR_DO(F) \ + /*(end)*/ + + // attribute "context types" + ATTR_CONTEXT_CLASS = 0, + ATTR_CONTEXT_FIELD = 1, + ATTR_CONTEXT_METHOD = 2, + ATTR_CONTEXT_CODE = 3, + ATTR_CONTEXT_LIMIT = 4, + + // constants for parsed layouts (stored in band::le_kind) + EK_NONE = 0, // not a layout element + EK_INT = 'I', // B H I SH etc., also FH etc. + EK_BCI = 'P', // PH etc. + EK_BCID = 'Q', // POH etc. + EK_BCO = 'O', // OH etc. + EK_REPL = 'N', // NH[...] etc. + EK_REF = 'R', // RUH, RUNH, KQH, etc. + EK_UN = 'T', // TB(...)[...] etc. + EK_CASE = 'K', // (...)[...] etc. + EK_CALL = '(', // (0), (1), etc. + EK_CBLE = '[', // [...][...] etc. + NO_BAND_INDEX = -1, + + // File option bits, from LSB in ascending bit position. + FO_DEFLATE_HINT = 1 << 0, + FO_IS_CLASS_STUB = 1 << 1, + + // Archive option bits, from LSB in ascending bit position: + AO_HAVE_SPECIAL_FORMATS = 1 << 0, + AO_HAVE_CP_NUMBERS = 1 << 1, + AO_HAVE_ALL_CODE_FLAGS = 1 << 2, + AO_3_UNUSED_MBZ = 1 << 3, + AO_HAVE_FILE_HEADERS = 1 << 4, + AO_DEFLATE_HINT = 1 << 5, + AO_HAVE_FILE_MODTIME = 1 << 6, + AO_HAVE_FILE_OPTIONS = 1 << 7, + AO_HAVE_FILE_SIZE_HI = 1 << 8, + AO_HAVE_CLASS_FLAGS_HI = 1 << 9, + AO_HAVE_FIELD_FLAGS_HI = 1 << 10, + AO_HAVE_METHOD_FLAGS_HI = 1 << 11, + AO_HAVE_CODE_FLAGS_HI = 1 << 12, +#define ARCHIVE_BIT_DO(F) \ + F(AO_HAVE_SPECIAL_FORMATS) F(AO_HAVE_CP_NUMBERS) F(AO_HAVE_ALL_CODE_FLAGS) \ + /*F(AO_3_UNUSED_MBZ)*/ \ + F(AO_HAVE_FILE_HEADERS) F(AO_DEFLATE_HINT) F(AO_HAVE_FILE_MODTIME) \ + F(AO_HAVE_FILE_OPTIONS) F(AO_HAVE_FILE_SIZE_HI) F(AO_HAVE_CLASS_FLAGS_HI) \ + F(AO_HAVE_FIELD_FLAGS_HI) F(AO_HAVE_METHOD_FLAGS_HI) F(AO_HAVE_CODE_FLAGS_HI) \ + /*(end)*/ + + // Constants for decoding attribute definition header bytes. + ADH_CONTEXT_MASK = 0x3, // (hdr & ADH_CONTEXT_MASK) + ADH_BIT_SHIFT = 0x2, // (hdr >> ADH_BIT_SHIFT) + ADH_BIT_IS_LSB = 1, // (hdr >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB +#define ADH_BYTE(context, index) ((((index) + ADH_BIT_IS_LSB) << ADH_BIT_SHIFT) + (context)) +#define ADH_BYTE_CONTEXT(adhb) ((adhb) & ADH_CONTEXT_MASK) +#define ADH_BYTE_INDEX(adhb) (((adhb) >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB) + NO_MODTIME = 0, // nullptr modtime value + + // meta-coding + _meta_default = 0, + _meta_canon_min = 1, + _meta_canon_max = 115, + _meta_arb = 116, + _meta_run = 117, + _meta_pop = 141, + _meta_limit = 189, + _meta_error = 255, + _xxx_1_end +}; + +// Bytecodes. + +enum +{ + bc_nop = 0, // 0x00 + bc_aconst_null = 1, // 0x01 + bc_iconst_m1 = 2, // 0x02 + bc_iconst_0 = 3, // 0x03 + bc_iconst_1 = 4, // 0x04 + bc_iconst_2 = 5, // 0x05 + bc_iconst_3 = 6, // 0x06 + bc_iconst_4 = 7, // 0x07 + bc_iconst_5 = 8, // 0x08 + bc_lconst_0 = 9, // 0x09 + bc_lconst_1 = 10, // 0x0a + bc_fconst_0 = 11, // 0x0b + bc_fconst_1 = 12, // 0x0c + bc_fconst_2 = 13, // 0x0d + bc_dconst_0 = 14, // 0x0e + bc_dconst_1 = 15, // 0x0f + bc_bipush = 16, // 0x10 + bc_sipush = 17, // 0x11 + bc_ldc = 18, // 0x12 + bc_ldc_w = 19, // 0x13 + bc_ldc2_w = 20, // 0x14 + bc_iload = 21, // 0x15 + bc_lload = 22, // 0x16 + bc_fload = 23, // 0x17 + bc_dload = 24, // 0x18 + bc_aload = 25, // 0x19 + bc_iload_0 = 26, // 0x1a + bc_iload_1 = 27, // 0x1b + bc_iload_2 = 28, // 0x1c + bc_iload_3 = 29, // 0x1d + bc_lload_0 = 30, // 0x1e + bc_lload_1 = 31, // 0x1f + bc_lload_2 = 32, // 0x20 + bc_lload_3 = 33, // 0x21 + bc_fload_0 = 34, // 0x22 + bc_fload_1 = 35, // 0x23 + bc_fload_2 = 36, // 0x24 + bc_fload_3 = 37, // 0x25 + bc_dload_0 = 38, // 0x26 + bc_dload_1 = 39, // 0x27 + bc_dload_2 = 40, // 0x28 + bc_dload_3 = 41, // 0x29 + bc_aload_0 = 42, // 0x2a + bc_aload_1 = 43, // 0x2b + bc_aload_2 = 44, // 0x2c + bc_aload_3 = 45, // 0x2d + bc_iaload = 46, // 0x2e + bc_laload = 47, // 0x2f + bc_faload = 48, // 0x30 + bc_daload = 49, // 0x31 + bc_aaload = 50, // 0x32 + bc_baload = 51, // 0x33 + bc_caload = 52, // 0x34 + bc_saload = 53, // 0x35 + bc_istore = 54, // 0x36 + bc_lstore = 55, // 0x37 + bc_fstore = 56, // 0x38 + bc_dstore = 57, // 0x39 + bc_astore = 58, // 0x3a + bc_istore_0 = 59, // 0x3b + bc_istore_1 = 60, // 0x3c + bc_istore_2 = 61, // 0x3d + bc_istore_3 = 62, // 0x3e + bc_lstore_0 = 63, // 0x3f + bc_lstore_1 = 64, // 0x40 + bc_lstore_2 = 65, // 0x41 + bc_lstore_3 = 66, // 0x42 + bc_fstore_0 = 67, // 0x43 + bc_fstore_1 = 68, // 0x44 + bc_fstore_2 = 69, // 0x45 + bc_fstore_3 = 70, // 0x46 + bc_dstore_0 = 71, // 0x47 + bc_dstore_1 = 72, // 0x48 + bc_dstore_2 = 73, // 0x49 + bc_dstore_3 = 74, // 0x4a + bc_astore_0 = 75, // 0x4b + bc_astore_1 = 76, // 0x4c + bc_astore_2 = 77, // 0x4d + bc_astore_3 = 78, // 0x4e + bc_iastore = 79, // 0x4f + bc_lastore = 80, // 0x50 + bc_fastore = 81, // 0x51 + bc_dastore = 82, // 0x52 + bc_aastore = 83, // 0x53 + bc_bastore = 84, // 0x54 + bc_castore = 85, // 0x55 + bc_sastore = 86, // 0x56 + bc_pop = 87, // 0x57 + bc_pop2 = 88, // 0x58 + bc_dup = 89, // 0x59 + bc_dup_x1 = 90, // 0x5a + bc_dup_x2 = 91, // 0x5b + bc_dup2 = 92, // 0x5c + bc_dup2_x1 = 93, // 0x5d + bc_dup2_x2 = 94, // 0x5e + bc_swap = 95, // 0x5f + bc_iadd = 96, // 0x60 + bc_ladd = 97, // 0x61 + bc_fadd = 98, // 0x62 + bc_dadd = 99, // 0x63 + bc_isub = 100, // 0x64 + bc_lsub = 101, // 0x65 + bc_fsub = 102, // 0x66 + bc_dsub = 103, // 0x67 + bc_imul = 104, // 0x68 + bc_lmul = 105, // 0x69 + bc_fmul = 106, // 0x6a + bc_dmul = 107, // 0x6b + bc_idiv = 108, // 0x6c + bc_ldiv = 109, // 0x6d + bc_fdiv = 110, // 0x6e + bc_ddiv = 111, // 0x6f + bc_irem = 112, // 0x70 + bc_lrem = 113, // 0x71 + bc_frem = 114, // 0x72 + bc_drem = 115, // 0x73 + bc_ineg = 116, // 0x74 + bc_lneg = 117, // 0x75 + bc_fneg = 118, // 0x76 + bc_dneg = 119, // 0x77 + bc_ishl = 120, // 0x78 + bc_lshl = 121, // 0x79 + bc_ishr = 122, // 0x7a + bc_lshr = 123, // 0x7b + bc_iushr = 124, // 0x7c + bc_lushr = 125, // 0x7d + bc_iand = 126, // 0x7e + bc_land = 127, // 0x7f + bc_ior = 128, // 0x80 + bc_lor = 129, // 0x81 + bc_ixor = 130, // 0x82 + bc_lxor = 131, // 0x83 + bc_iinc = 132, // 0x84 + bc_i2l = 133, // 0x85 + bc_i2f = 134, // 0x86 + bc_i2d = 135, // 0x87 + bc_l2i = 136, // 0x88 + bc_l2f = 137, // 0x89 + bc_l2d = 138, // 0x8a + bc_f2i = 139, // 0x8b + bc_f2l = 140, // 0x8c + bc_f2d = 141, // 0x8d + bc_d2i = 142, // 0x8e + bc_d2l = 143, // 0x8f + bc_d2f = 144, // 0x90 + bc_i2b = 145, // 0x91 + bc_i2c = 146, // 0x92 + bc_i2s = 147, // 0x93 + bc_lcmp = 148, // 0x94 + bc_fcmpl = 149, // 0x95 + bc_fcmpg = 150, // 0x96 + bc_dcmpl = 151, // 0x97 + bc_dcmpg = 152, // 0x98 + bc_ifeq = 153, // 0x99 + bc_ifne = 154, // 0x9a + bc_iflt = 155, // 0x9b + bc_ifge = 156, // 0x9c + bc_ifgt = 157, // 0x9d + bc_ifle = 158, // 0x9e + bc_if_icmpeq = 159, // 0x9f + bc_if_icmpne = 160, // 0xa0 + bc_if_icmplt = 161, // 0xa1 + bc_if_icmpge = 162, // 0xa2 + bc_if_icmpgt = 163, // 0xa3 + bc_if_icmple = 164, // 0xa4 + bc_if_acmpeq = 165, // 0xa5 + bc_if_acmpne = 166, // 0xa6 + bc_goto = 167, // 0xa7 + bc_jsr = 168, // 0xa8 + bc_ret = 169, // 0xa9 + bc_tableswitch = 170, // 0xaa + bc_lookupswitch = 171, // 0xab + bc_ireturn = 172, // 0xac + bc_lreturn = 173, // 0xad + bc_freturn = 174, // 0xae + bc_dreturn = 175, // 0xaf + bc_areturn = 176, // 0xb0 + bc_return = 177, // 0xb1 + bc_getstatic = 178, // 0xb2 + bc_putstatic = 179, // 0xb3 + bc_getfield = 180, // 0xb4 + bc_putfield = 181, // 0xb5 + bc_invokevirtual = 182, // 0xb6 + bc_invokespecial = 183, // 0xb7 + bc_invokestatic = 184, // 0xb8 + bc_invokeinterface = 185, // 0xb9 + bc_xxxunusedxxx = 186, // 0xba + bc_new = 187, // 0xbb + bc_newarray = 188, // 0xbc + bc_anewarray = 189, // 0xbd + bc_arraylength = 190, // 0xbe + bc_athrow = 191, // 0xbf + bc_checkcast = 192, // 0xc0 + bc_instanceof = 193, // 0xc1 + bc_monitorenter = 194, // 0xc2 + bc_monitorexit = 195, // 0xc3 + bc_wide = 196, // 0xc4 + bc_multianewarray = 197, // 0xc5 + bc_ifnull = 198, // 0xc6 + bc_ifnonnull = 199, // 0xc7 + bc_goto_w = 200, // 0xc8 + bc_jsr_w = 201, // 0xc9 + bc_bytecode_limit = 202 // 0xca +}; + +enum +{ + bc_end_marker = 255, + bc_byte_escape = 254, + bc_ref_escape = 253, + _first_linker_op = bc_getstatic, + _last_linker_op = bc_invokestatic, + _num_linker_ops = (_last_linker_op - _first_linker_op) + 1, + _self_linker_op = bc_bytecode_limit, + _self_linker_aload_flag = 1 * _num_linker_ops, + _self_linker_super_flag = 2 * _num_linker_ops, + _self_linker_limit = _self_linker_op + 4 * _num_linker_ops, + _invokeinit_op = _self_linker_limit, + _invokeinit_self_option = 0, + _invokeinit_super_option = 1, + _invokeinit_new_option = 2, + _invokeinit_limit = _invokeinit_op + 3, + _xldc_op = _invokeinit_limit, + bc_aldc = bc_ldc, + bc_cldc = _xldc_op + 0, + bc_ildc = _xldc_op + 1, + bc_fldc = _xldc_op + 2, + bc_aldc_w = bc_ldc_w, + bc_cldc_w = _xldc_op + 3, + bc_ildc_w = _xldc_op + 4, + bc_fldc_w = _xldc_op + 5, + bc_lldc2_w = bc_ldc2_w, + bc_dldc2_w = _xldc_op + 6, + _xldc_limit = _xldc_op + 7, + _xxx_3_end +}; diff --git a/depends/pack200/src/defines.h b/depends/pack200/src/defines.h new file mode 100644 index 00000000..cfe5fc28 --- /dev/null +++ b/depends/pack200/src/defines.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2001, 2009, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// random definitions + +#ifdef _MSC_VER +#include <windows.h> +#include <winuser.h> +#else +#include <unistd.h> +#endif + +// Error messages that we have +#define ERROR_ENOMEM "Memory allocation failed" +#define ERROR_FORMAT "Corrupted pack file" +#define ERROR_RESOURCE "Cannot extract resource file" +#define ERROR_OVERFLOW "Internal buffer overflow" +#define ERROR_INTERNAL "Internal error" + +#define lengthof(array) (sizeof(array) / sizeof(array[0])) + +#define NEW(T, n) (T *) must_malloc((int)(scale_size(n, sizeof(T)))) +#define U_NEW(T, n) (T *) u->alloc(scale_size(n, sizeof(T))) +#define T_NEW(T, n) (T *) u->temp_alloc(scale_size(n, sizeof(T))) + +typedef signed char byte; + +#ifdef _MSC_VER +#define MKDIR(dir) mkdir(dir) +#define getpid() _getpid() +#define PATH_MAX MAX_PATH +#define dup2(a, b) _dup2(a, b) +#define strcasecmp(s1, s2) _stricmp(s1, s2) +#define tempname _tempname +#define sleep Sleep +#else +#define MKDIR(dir) mkdir(dir, 0777); +#endif + +/* Must cast to void *, then size_t, then int. */ +#define ptrlowbits(x) ((int)(size_t)(void *)(x)) + +#define DEFAULT_ARCHIVE_MODTIME 1060000000 // Aug 04, 2003 5:26 PM PDT diff --git a/depends/pack200/src/unpack.cpp b/depends/pack200/src/unpack.cpp new file mode 100644 index 00000000..55d253b2 --- /dev/null +++ b/depends/pack200/src/unpack.cpp @@ -0,0 +1,4793 @@ +/* + * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// -*- C++ -*- +// Program for unpacking specially compressed Java packages. +// John R. Rose + +/* + * When compiling for a 64bit LP64 system (longs and pointers being 64bits), + * the printf format %ld is correct and use of %lld will cause warning + * errors from some compilers (gcc/g++). + * _LP64 can be explicitly set (used on Linux). + * Solaris compilers will define __sparcv9 or __x86_64 on 64bit compilations. + */ +#if defined(_LP64) || defined(__sparcv9) || defined(__x86_64) +#define LONG_LONG_FORMAT "%ld" +#define LONG_LONG_HEX_FORMAT "%lx" +#else +#define LONG_LONG_FORMAT "%lld" +#define LONG_LONG_HEX_FORMAT "%016llx" +#endif + +#include <sys/types.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> +#include <assert.h> +#include <limits.h> +#include <time.h> +#include <stdint.h> + +#include "defines.h" +#include "bytes.h" +#include "utils.h" +#include "coding.h" +#include "bands.h" + +#include "constants.h" + +#include "zip.h" + +#include "unpack.h" + +// tags, in canonical order: +static const byte TAGS_IN_ORDER[] = { + CONSTANT_Utf8, CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long, + CONSTANT_Double, CONSTANT_String, CONSTANT_Class, CONSTANT_Signature, + CONSTANT_NameandType, CONSTANT_Fieldref, CONSTANT_Methodref, CONSTANT_InterfaceMethodref}; +#define N_TAGS_IN_ORDER (sizeof TAGS_IN_ORDER) + +// REQUESTED must be -2 for u2 and REQUESTED_LDC must be -1 for u1 +enum +{ + NOT_REQUESTED = 0, + REQUESTED = -2, + REQUESTED_LDC = -1 +}; + +#define NO_INORD ((uint32_t) - 1) + +struct entry +{ + byte tag; + unsigned short nrefs; // pack w/ tag + + int outputIndex; + uint32_t inord; // &cp.entries[cp.tag_base[this->tag]+this->inord] == this + + entry **refs; + + // put last to pack best + union + { + bytes b; + int i; + int64_t l; + } value; + + void requestOutputIndex(constant_pool &cp, int req = REQUESTED); + int getOutputIndex() + { + assert(outputIndex > NOT_REQUESTED); + return outputIndex; + } + + entry *ref(int refnum) + { + assert((uint32_t)refnum < nrefs); + return refs[refnum]; + } + + const char *utf8String() + { + assert(tagMatches(CONSTANT_Utf8)); + assert(value.b.len == strlen((const char *)value.b.ptr)); + return (const char *)value.b.ptr; + } + + entry *className() + { + assert(tagMatches(CONSTANT_Class)); + return ref(0); + } + + entry *memberClass() + { + assert(tagMatches(CONSTANT_Member)); + return ref(0); + } + + entry *memberDescr() + { + assert(tagMatches(CONSTANT_Member)); + return ref(1); + } + + entry *descrName() + { + assert(tagMatches(CONSTANT_NameandType)); + return ref(0); + } + + entry *descrType() + { + assert(tagMatches(CONSTANT_NameandType)); + return ref(1); + } + + int typeSize(); + + bytes &asUtf8(); + int asInteger() + { + assert(tag == CONSTANT_Integer); + return value.i; + } + + bool isUtf8(bytes &b) + { + return tagMatches(CONSTANT_Utf8) && value.b.equals(b); + } + + bool isDoubleWord() + { + return tag == CONSTANT_Double || tag == CONSTANT_Long; + } + + bool tagMatches(byte tag2) + { + return (tag2 == tag) || (tag2 == CONSTANT_Utf8 && tag == CONSTANT_Signature) || + (tag2 == CONSTANT_Literal && tag >= CONSTANT_Integer && tag <= CONSTANT_String && + tag != CONSTANT_Class) || + (tag2 == CONSTANT_Member && tag >= CONSTANT_Fieldref && + tag <= CONSTANT_InterfaceMethodref); + } +}; + +entry *cpindex::get(uint32_t i) +{ + if (i >= len) + return nullptr; + else if (base1 != nullptr) + // primary index + return &base1[i]; + else + // secondary index + return base2[i]; +} + +inline bytes &entry::asUtf8() +{ + assert(tagMatches(CONSTANT_Utf8)); + return value.b; +} + +int entry::typeSize() +{ + assert(tagMatches(CONSTANT_Utf8)); + const char *sigp = (char *)value.b.ptr; + switch (*sigp) + { + case '(': + sigp++; + break; // skip opening '(' + case 'D': + case 'J': + return 2; // double field + default: + return 1; // field + } + int siglen = 0; + for (;;) + { + int ch = *sigp++; + switch (ch) + { + case 'D': + case 'J': + siglen += 1; + break; + case '[': + // Skip rest of array info. + while (ch == '[') + { + ch = *sigp++; + } + if (ch != 'L') + break; + // else fall through + case 'L': + sigp = strchr(sigp, ';'); + if (sigp == nullptr) + { + unpack_abort("bad data"); + return 0; + } + sigp += 1; + break; + case ')': // closing ')' + return siglen; + } + siglen += 1; + } +} + +inline cpindex *constant_pool::getFieldIndex(entry *classRef) +{ + assert(classRef->tagMatches(CONSTANT_Class)); + assert((uint32_t)classRef->inord < (uint32_t)tag_count[CONSTANT_Class]); + return &member_indexes[classRef->inord * 2 + 0]; +} +inline cpindex *constant_pool::getMethodIndex(entry *classRef) +{ + assert(classRef->tagMatches(CONSTANT_Class)); + assert((uint32_t)classRef->inord < (uint32_t)tag_count[CONSTANT_Class]); + return &member_indexes[classRef->inord * 2 + 1]; +} + +struct inner_class +{ + entry *inner; + entry *outer; + entry *name; + int flags; + inner_class *next_sibling; + bool requested; +}; + +// Here is where everything gets deallocated: +void unpacker::free() +{ + int i; + if (jarout != nullptr) + jarout->reset(); + if (gzin != nullptr) + { + gzin->free(); + gzin = nullptr; + } + if (free_input) + input.free(); + /* + * free everybody ever allocated with U_NEW or (recently) with T_NEW + */ + assert(smallbuf.base() == nullptr || mallocs.contains(smallbuf.base())); + assert(tsmallbuf.base() == nullptr || tmallocs.contains(tsmallbuf.base())); + mallocs.freeAll(); + tmallocs.freeAll(); + smallbuf.init(); + tsmallbuf.init(); + bcimap.free(); + class_fixup_type.free(); + class_fixup_offset.free(); + class_fixup_ref.free(); + code_fixup_type.free(); + code_fixup_offset.free(); + code_fixup_source.free(); + requested_ics.free(); + cur_classfile_head.free(); + cur_classfile_tail.free(); + for (i = 0; i < ATTR_CONTEXT_LIMIT; i++) + attr_defs[i].free(); + + // free CP state + cp.outputEntries.free(); + for (i = 0; i < CONSTANT_Limit; i++) + cp.tag_extras[i].free(); +} + +// input handling +// Attempts to advance rplimit so that (rplimit-rp) is at least 'more'. +// Will eagerly read ahead by larger chunks, if possible. +// Returns false if (rplimit-rp) is not at least 'more', +// unless rplimit hits input.limit(). +bool unpacker::ensure_input(int64_t more) +{ + uint64_t want = more - input_remaining(); + if ((int64_t)want <= 0) + return true; // it's already in the buffer + if (rplimit == input.limit()) + return true; // not expecting any more + + if (read_input_fn == nullptr) + { + // assume it is already all there + bytes_read += input.limit() - rplimit; + rplimit = input.limit(); + return true; + } + + uint64_t remaining = (input.limit() - rplimit); // how much left to read? + byte *rpgoal = (want >= remaining) ? input.limit() : rplimit + (size_t)want; + enum + { + CHUNK_SIZE = (1 << 14) + }; + uint64_t fetch = want; + if (fetch < CHUNK_SIZE) + fetch = CHUNK_SIZE; + if (fetch > remaining * 3 / 4) + fetch = remaining; + // Try to fetch at least "more" bytes. + while ((int64_t)fetch > 0) + { + int64_t nr = (*read_input_fn)(this, rplimit, fetch, remaining); + if (nr <= 0) + { + return (rplimit >= rpgoal); + } + remaining -= nr; + rplimit += nr; + fetch -= nr; + bytes_read += nr; + assert(remaining == (uint64_t)(input.limit() - rplimit)); + } + return true; +} + +// output handling + +fillbytes *unpacker::close_output(fillbytes *which) +{ + assert(wp != nullptr); + if (which == nullptr) + { + if (wpbase == cur_classfile_head.base()) + { + which = &cur_classfile_head; + } + else + { + which = &cur_classfile_tail; + } + } + assert(wpbase == which->base()); + assert(wplimit == which->end()); + which->setLimit(wp); + wp = nullptr; + wplimit = nullptr; + // wpbase = nullptr; + return which; +} + +// maybe_inline +void unpacker::ensure_put_space(size_t size) +{ + if (wp + size <= wplimit) + return; + // Determine which segment needs expanding. + fillbytes *which = close_output(); + byte *wp0 = which->grow(size); + wpbase = which->base(); + wplimit = which->end(); + wp = wp0; +} + +byte *unpacker::put_space(size_t size) +{ + byte *wp0 = wp; + byte *wp1 = wp0 + size; + if (wp1 > wplimit) + { + ensure_put_space(size); + wp0 = wp; + wp1 = wp0 + size; + } + wp = wp1; + return wp0; +} + +void unpacker::putu2_at(byte *wp, int n) +{ + if (n != (unsigned short)n) + { + unpack_abort(ERROR_OVERFLOW); + return; + } + wp[0] = (n) >> 8; + wp[1] = (n) >> 0; +} + +void unpacker::putu4_at(byte *wp, int n) +{ + wp[0] = (n) >> 24; + wp[1] = (n) >> 16; + wp[2] = (n) >> 8; + wp[3] = (n) >> 0; +} + +void unpacker::putu8_at(byte *wp, int64_t n) +{ + putu4_at(wp + 0, (int)((uint64_t)n >> 32)); + putu4_at(wp + 4, (int)((uint64_t)n >> 0)); +} + +void unpacker::putu2(int n) +{ + putu2_at(put_space(2), n); +} + +void unpacker::putu4(int n) +{ + putu4_at(put_space(4), n); +} + +void unpacker::putu8(int64_t n) +{ + putu8_at(put_space(8), n); +} + +int unpacker::putref_index(entry *e, int size) +{ + if (e == nullptr) + return 0; + else if (e->outputIndex > NOT_REQUESTED) + return e->outputIndex; + else if (e->tag == CONSTANT_Signature) + return putref_index(e->ref(0), size); + else + { + e->requestOutputIndex(cp, -size); + // Later on we'll fix the bits. + class_fixup_type.addByte(size); + class_fixup_offset.add((int)wpoffset()); + class_fixup_ref.add(e); + return 0; + } +} + +void unpacker::putref(entry *e) +{ + int oidx = putref_index(e, 2); + putu2_at(put_space(2), oidx); +} + +void unpacker::putu1ref(entry *e) +{ + int oidx = putref_index(e, 1); + putu1_at(put_space(1), oidx); +} + +// Allocation of small and large blocks. + +enum +{ + CHUNK = (1 << 14), + SMALL = (1 << 9) +}; + +// Call malloc. Try to combine small blocks and free much later. +void *unpacker::alloc_heap(size_t size, bool smallOK, bool temp) +{ + if (!smallOK || size > SMALL) + { + void *res = must_malloc((int)size); + (temp ? &tmallocs : &mallocs)->add(res); + return res; + } + fillbytes &xsmallbuf = *(temp ? &tsmallbuf : &smallbuf); + if (!xsmallbuf.canAppend(size + 1)) + { + xsmallbuf.init(CHUNK); + (temp ? &tmallocs : &mallocs)->add(xsmallbuf.base()); + } + int growBy = (int)size; + growBy += -growBy & 7; // round up mod 8 + return xsmallbuf.grow(growBy); +} + +void unpacker::saveTo(bytes &b, byte *ptr, size_t len) +{ + b.ptr = U_NEW(byte, add_size(len, 1)); + b.len = len; + b.copyFrom(ptr, len); +} + +// Read up through band_headers. +// Do the archive_size dance to set the size of the input mega-buffer. +void unpacker::read_file_header() +{ + // Read file header to determine file type and total size. + enum + { + MAGIC_BYTES = 4, + AH_LENGTH_0 = 3, // minver, majver, options are outside of archive_size + AH_LENGTH_0_MAX = AH_LENGTH_0 + 1, // options might have 2 bytes + AH_LENGTH = 26, // maximum archive header length (w/ all fields) + // Length contributions from optional header fields: + AH_FILE_HEADER_LEN = 5, // sizehi/lo/next/modtime/files + AH_ARCHIVE_SIZE_LEN = 2, // sizehi/lo only; part of AH_FILE_HEADER_LEN + AH_CP_NUMBER_LEN = 4, // int/float/long/double + AH_SPECIAL_FORMAT_LEN = 2, // layouts/band-headers + AH_LENGTH_MIN = + AH_LENGTH - (AH_FILE_HEADER_LEN + AH_SPECIAL_FORMAT_LEN + AH_CP_NUMBER_LEN), + ARCHIVE_SIZE_MIN = AH_LENGTH_MIN - (AH_LENGTH_0 + AH_ARCHIVE_SIZE_LEN), + FIRST_READ = MAGIC_BYTES + AH_LENGTH_MIN + }; + + assert(AH_LENGTH_MIN == 15); // # of UNSIGNED5 fields required after archive_magic + assert(ARCHIVE_SIZE_MIN == 10); // # of UNSIGNED5 fields required after archive_size + // An absolute minimum nullptr archive is magic[4], {minver,majver,options}[3], + // archive_size[0], cp_counts[8], class_counts[4], for a total of 19 bytes. + // (Note that archive_size is optional; it may be 0..10 bytes in length.) + // The first read must capture everything up through the options field. + // This happens to work even if {minver,majver,options} is a pathological + // 15 bytes long. Legal pack files limit those three fields to 1+1+2 bytes. + assert(FIRST_READ >= MAGIC_BYTES + AH_LENGTH_0 * B_MAX); + + // Up through archive_size, the largest possible archive header is + // magic[4], {minver,majver,options}[4], archive_size[10]. + // (Note only the low 12 bits of options are allowed to be non-zero.) + // In order to parse archive_size, we need at least this many bytes + // in the first read. Of course, if archive_size_hi is more than + // a byte, we probably will fail to allocate the buffer, since it + // will be many gigabytes long. This is a practical, not an + // architectural limit to Pack200 archive sizes. + assert(FIRST_READ >= MAGIC_BYTES + AH_LENGTH_0_MAX + 2 * B_MAX); + + bool foreign_buf = (read_input_fn == nullptr); + byte initbuf[(int)FIRST_READ + (int)C_SLOP + 200]; // 200 is for JAR I/O + if (foreign_buf) + { + // inbytes is all there is + input.set(inbytes); + rp = input.base(); + rplimit = input.limit(); + } + else + { + // inbytes, if not empty, contains some read-ahead we must use first + // ensure_input will take care of copying it into initbuf, + // then querying read_input_fn for any additional data needed. + // However, the caller must assume that we use up all of inbytes. + // There is no way to tell the caller that we used only part of them. + // Therefore, the caller must use only a bare minimum of read-ahead. + if (inbytes.len > FIRST_READ) + { + unpack_abort("too much read-ahead"); + } + input.set(initbuf, sizeof(initbuf)); + input.b.clear(); + input.b.copyFrom(inbytes); + rplimit = rp = input.base(); + rplimit += inbytes.len; + bytes_read += inbytes.len; + } + // Read only 19 bytes, which is certain to contain #archive_options fields, + // but is certain not to overflow past the archive_header. + input.b.len = FIRST_READ; + if (!ensure_input(FIRST_READ)) + unpack_abort("EOF reading archive magic number"); + + if (rp[0] == 'P' && rp[1] == 'K') + { + // In the Unix-style program, we simply simulate a copy command. + // Copy until EOF; assume the JAR file is the last segment. + fprintf(stderr, "Copy-mode.\n"); + for (;;) + { + jarout->write_data(rp, (int)input_remaining()); + if (foreign_buf) + break; // one-time use of a passed in buffer + if (input.size() < CHUNK) + { + // Get some breathing room. + input.set(U_NEW(byte, (size_t)CHUNK + C_SLOP), (size_t)CHUNK); + } + rp = rplimit = input.base(); + if (!ensure_input(1)) + break; + } + jarout->closeJarFile(false); + return; + } + + // Read the magic number. + magic = 0; + for (int i1 = 0; i1 < (int)sizeof(magic); i1++) + { + magic <<= 8; + magic += (*rp++ & 0xFF); + } + + // Read the first 3 values from the header. + value_stream hdr; + int hdrVals = 0; + int hdrValsSkipped = 0; // debug only + hdr.init(rp, rplimit, UNSIGNED5_spec); + minver = hdr.getInt(); + majver = hdr.getInt(); + hdrVals += 2; + + if (magic != (int)JAVA_PACKAGE_MAGIC || + (majver != JAVA5_PACKAGE_MAJOR_VERSION && majver != JAVA6_PACKAGE_MAJOR_VERSION) || + (minver != JAVA5_PACKAGE_MINOR_VERSION && minver != JAVA6_PACKAGE_MINOR_VERSION)) + { + char message[200]; + sprintf(message, "@" ERROR_FORMAT ": magic/ver = " + "%08X/%d.%d should be %08X/%d.%d OR %08X/%d.%d\n", + magic, majver, minver, JAVA_PACKAGE_MAGIC, JAVA5_PACKAGE_MAJOR_VERSION, + JAVA5_PACKAGE_MINOR_VERSION, JAVA_PACKAGE_MAGIC, JAVA6_PACKAGE_MAJOR_VERSION, + JAVA6_PACKAGE_MINOR_VERSION); + unpack_abort(message); + } + + archive_options = hdr.getInt(); + hdrVals += 1; + assert(hdrVals == AH_LENGTH_0); // first three fields only + +#define ORBIT(bit) | (bit) + int OPTION_LIMIT = (0 ARCHIVE_BIT_DO(ORBIT)); +#undef ORBIT + if ((archive_options & ~OPTION_LIMIT) != 0) + { + fprintf(stderr, "Warning: Illegal archive options 0x%x\n", archive_options); + unpack_abort("illegal archive options"); + return; + } + + if ((archive_options & AO_HAVE_FILE_HEADERS) != 0) + { + uint32_t hi = hdr.getInt(); + uint32_t lo = hdr.getInt(); + uint64_t x = band::makeLong(hi, lo); + archive_size = (size_t)x; + if (archive_size != x) + { + // Silly size specified; force overflow. + archive_size = PSIZE_MAX + 1; + } + hdrVals += 2; + } + else + { + hdrValsSkipped += 2; + } + + // Now we can size the whole archive. + // Read everything else into a mega-buffer. + rp = hdr.rp; + int header_size_0 = (int)(rp - input.base()); // used-up header (4byte + 3int) + int header_size_1 = (int)(rplimit - rp); // buffered unused initial fragment + int header_size = header_size_0 + header_size_1; + unsized_bytes_read = header_size_0; + if (foreign_buf) + { + if (archive_size > (size_t)header_size_1) + { + unpack_abort("EOF reading fixed input buffer"); + return; + } + } + else if (archive_size != 0) + { + if (archive_size < ARCHIVE_SIZE_MIN) + { + unpack_abort("impossible archive size"); // bad input data + return; + } + if (archive_size < (size_t)header_size_1) + { + unpack_abort("too much read-ahead"); // somehow we pre-fetched too much? + return; + } + input.set(U_NEW(byte, add_size(header_size_0, archive_size, C_SLOP)), + (size_t)header_size_0 + archive_size); + assert(input.limit()[0] == 0); + // Move all the bytes we read initially into the real buffer. + input.b.copyFrom(initbuf, header_size); + rp = input.b.ptr + header_size_0; + rplimit = input.b.ptr + header_size; + } + else + { + // It's more complicated and painful. + // A zero archive_size means that we must read until EOF. + input.init(CHUNK * 2); + input.b.len = input.allocated; + rp = rplimit = input.base(); + // Set up input buffer as if we already read the header: + input.b.copyFrom(initbuf, header_size); + rplimit += header_size; + while (ensure_input(input.limit() - rp)) + { + size_t dataSoFar = input_remaining(); + size_t nextSize = add_size(dataSoFar, CHUNK); + input.ensureSize(nextSize); + input.b.len = input.allocated; + rp = rplimit = input.base(); + rplimit += dataSoFar; + } + size_t dataSize = (rplimit - input.base()); + input.b.len = dataSize; + input.grow(C_SLOP); + free_input = true; // free it later + input.b.len = dataSize; + assert(input.limit()[0] == 0); + rp = rplimit = input.base(); + rplimit += dataSize; + rp += header_size_0; // already scanned these bytes... + } + live_input = true; // mark as "do not reuse" + + // read the rest of the header fields + ensure_input((AH_LENGTH - AH_LENGTH_0) * B_MAX); + hdr.rp = rp; + hdr.rplimit = rplimit; + + if ((archive_options & AO_HAVE_FILE_HEADERS) != 0) + { + archive_next_count = hdr.getInt(); + if (archive_next_count < 0) + unpack_abort("bad archive_next_count"); + archive_modtime = hdr.getInt(); + file_count = hdr.getInt(); + if (file_count < 0) + unpack_abort("bad file_count"); + hdrVals += 3; + } + else + { + hdrValsSkipped += 3; + } + + if ((archive_options & AO_HAVE_SPECIAL_FORMATS) != 0) + { + band_headers_size = hdr.getInt(); + if (band_headers_size < 0) + unpack_abort("bad band_headers_size"); + attr_definition_count = hdr.getInt(); + if (attr_definition_count < 0) + unpack_abort("bad attr_definition_count"); + hdrVals += 2; + } + else + { + hdrValsSkipped += 2; + } + + int cp_counts[N_TAGS_IN_ORDER]; + for (int k = 0; k < (int)N_TAGS_IN_ORDER; k++) + { + if (!(archive_options & AO_HAVE_CP_NUMBERS)) + { + switch (TAGS_IN_ORDER[k]) + { + case CONSTANT_Integer: + case CONSTANT_Float: + case CONSTANT_Long: + case CONSTANT_Double: + cp_counts[k] = 0; + hdrValsSkipped += 1; + continue; + } + } + cp_counts[k] = hdr.getInt(); + if (cp_counts[k] < 0) + unpack_abort("bad cp_counts"); + hdrVals += 1; + } + + ic_count = hdr.getInt(); + if (ic_count < 0) + unpack_abort("bad ic_count"); + + default_class_minver = hdr.getInt(); + default_class_majver = hdr.getInt(); + + class_count = hdr.getInt(); + if (class_count < 0) + unpack_abort("bad class_count"); + + hdrVals += 4; + + // done with archive_header + hdrVals += hdrValsSkipped; + assert(hdrVals == AH_LENGTH); + + rp = hdr.rp; + if (rp > rplimit) + unpack_abort("EOF reading archive header"); + + // Now size the CP. + cp.init(this, cp_counts); + + default_file_modtime = archive_modtime; + if (default_file_modtime == 0 && !(archive_options & AO_HAVE_FILE_MODTIME)) + default_file_modtime = DEFAULT_ARCHIVE_MODTIME; // taken from driver + if ((archive_options & AO_DEFLATE_HINT) != 0) + default_file_options |= FO_DEFLATE_HINT; + + // meta-bytes, if any, immediately follow archive header + // band_headers.readData(band_headers_size); + ensure_input(band_headers_size); + if (input_remaining() < (size_t)band_headers_size) + { + unpack_abort("EOF reading band headers"); + return; + } + bytes band_headers; + // The "1+" allows an initial byte to be pushed on the front. + band_headers.set(1 + U_NEW(byte, 1 + band_headers_size + C_SLOP), band_headers_size); + + // Start scanning band headers here: + band_headers.copyFrom(rp, band_headers.len); + rp += band_headers.len; + assert(rp <= rplimit); + meta_rp = band_headers.ptr; + // Put evil meta-codes at the end of the band headers, + // so we are sure to throw an error if we run off the end. + bytes::of(band_headers.limit(), C_SLOP).clear(_meta_error); +} + +void unpacker::finish() +{ + if (verbose >= 1) + { + fprintf(stderr, "A total of " LONG_LONG_FORMAT " bytes were read in %d segment(s).\n", + (bytes_read_before_reset + bytes_read), segments_read_before_reset + 1); + fprintf(stderr, "A total of " LONG_LONG_FORMAT " file content bytes were written.\n", + (bytes_written_before_reset + bytes_written)); + fprintf(stderr, + "A total of %d files (of which %d are classes) were written to output.\n", + files_written_before_reset + files_written, + classes_written_before_reset + classes_written); + } + if (jarout != nullptr) + jarout->closeJarFile(true); +} + +// Cf. PackageReader.readConstantPoolCounts +void constant_pool::init(unpacker *u_, int counts[NUM_COUNTS]) +{ + this->u = u_; + + // Fill-pointer for CP. + int next_entry = 0; + + // Size the constant pool: + for (int k = 0; k < (int)N_TAGS_IN_ORDER; k++) + { + byte tag = TAGS_IN_ORDER[k]; + int len = counts[k]; + tag_count[tag] = len; + tag_base[tag] = next_entry; + next_entry += len; + // Detect and defend against constant pool size overflow. + // (Pack200 forbids the sum of CP counts to exceed 2^29-1.) + enum + { + CP_SIZE_LIMIT = (1 << 29), + IMPLICIT_ENTRY_COUNT = 1 // empty Utf8 string + }; + if (len >= (1 << 29) || len < 0 || next_entry >= CP_SIZE_LIMIT + IMPLICIT_ENTRY_COUNT) + { + unpack_abort("archive too large: constant pool limit exceeded"); + } + } + + // Close off the end of the CP: + nentries = next_entry; + + // place a limit on future CP growth: + int generous = 0; + generous = add_size(generous, u->ic_count); // implicit name + generous = add_size(generous, u->ic_count); // outer + generous = add_size(generous, u->ic_count); // outer.utf8 + generous = add_size(generous, 40); // WKUs, misc + generous = add_size(generous, u->class_count); // implicit SourceFile strings + maxentries = add_size(nentries, generous); + + // Note that this CP does not include "empty" entries + // for longs and doubles. Those are introduced when + // the entries are renumbered for classfile output. + + entries = U_NEW(entry, maxentries); + + first_extra_entry = &entries[nentries]; + + // Initialize the standard indexes. + tag_count[CONSTANT_All] = nentries; + tag_base[CONSTANT_All] = 0; + for (int tag = 0; tag < CONSTANT_Limit; tag++) + { + entry *cpMap = &entries[tag_base[tag]]; + tag_index[tag].init(tag_count[tag], cpMap, tag); + } + + // Initialize hashTab to a generous power-of-two size. + uint32_t pow2 = 1; + uint32_t target = maxentries + maxentries / 2; // 60% full + while (pow2 < target) + pow2 <<= 1; + hashTab = U_NEW(entry *, hashTabLength = pow2); +} + +static byte *store_Utf8_char(byte *cp, unsigned short ch) +{ + if (ch >= 0x001 && ch <= 0x007F) + { + *cp++ = (byte)ch; + } + else if (ch <= 0x07FF) + { + *cp++ = (byte)(0xC0 | ((ch >> 6) & 0x1F)); + *cp++ = (byte)(0x80 | ((ch >> 0) & 0x3F)); + } + else + { + *cp++ = (byte)(0xE0 | ((ch >> 12) & 0x0F)); + *cp++ = (byte)(0x80 | ((ch >> 6) & 0x3F)); + *cp++ = (byte)(0x80 | ((ch >> 0) & 0x3F)); + } + return cp; +} + +static byte *skip_Utf8_chars(byte *cp, int len) +{ + for (;; cp++) + { + int ch = *cp & 0xFF; + if ((ch & 0xC0) != 0x80) + { + if (len-- == 0) + return cp; + if (ch < 0x80 && len == 0) + return cp + 1; + } + } +} + +static int compare_Utf8_chars(bytes &b1, bytes &b2) +{ + int l1 = (int)b1.len; + int l2 = (int)b2.len; + int l0 = (l1 < l2) ? l1 : l2; + byte *p1 = b1.ptr; + byte *p2 = b2.ptr; + int c0 = 0; + for (int i = 0; i < l0; i++) + { + int c1 = p1[i] & 0xFF; + int c2 = p2[i] & 0xFF; + if (c1 != c2) + { + // Before returning the obvious answer, + // check to see if c1 or c2 is part of a 0x0000, + // which encodes as {0xC0,0x80}. The 0x0000 is the + // lowest-sorting Java char value, and yet it encodes + // as if it were the first char after 0x7F, which causes + // strings containing nulls to sort too high. All other + // comparisons are consistent between Utf8 and Java chars. + if (c1 == 0xC0 && (p1[i + 1] & 0xFF) == 0x80) + c1 = 0; + if (c2 == 0xC0 && (p2[i + 1] & 0xFF) == 0x80) + c2 = 0; + if (c0 == 0xC0) + { + assert(((c1 | c2) & 0xC0) == 0x80); // c1 & c2 are extension chars + if (c1 == 0x80) + c1 = 0; // will sort below c2 + if (c2 == 0x80) + c2 = 0; // will sort below c1 + } + return c1 - c2; + } + c0 = c1; // save away previous char + } + // common prefix is identical; return length difference if any + return l1 - l2; +} + +// Cf. PackageReader.readUtf8Bands +void unpacker::read_Utf8_values(entry *cpMap, int len) +{ + // Implicit first Utf8 string is the empty string. + enum + { + // certain bands begin with implicit zeroes + PREFIX_SKIP_2 = 2, + SUFFIX_SKIP_1 = 1 + }; + + int i; + + // First band: Read lengths of shared prefixes. + if (len > PREFIX_SKIP_2) + cp_Utf8_prefix.readData(len - PREFIX_SKIP_2); + + // Second band: Read lengths of unshared suffixes: + if (len > SUFFIX_SKIP_1) + cp_Utf8_suffix.readData(len - SUFFIX_SKIP_1); + + bytes *allsuffixes = T_NEW(bytes, len); + + int nbigsuf = 0; + fillbytes charbuf; // buffer to allocate small strings + charbuf.init(); + + // Third band: Read the char values in the unshared suffixes: + cp_Utf8_chars.readData(cp_Utf8_suffix.getIntTotal()); + for (i = 0; i < len; i++) + { + int suffix = (i < SUFFIX_SKIP_1) ? 0 : cp_Utf8_suffix.getInt(); + if (suffix < 0) + { + unpack_abort("bad utf8 suffix"); + } + if (suffix == 0 && i >= SUFFIX_SKIP_1) + { + // chars are packed in cp_Utf8_big_chars + nbigsuf += 1; + continue; + } + bytes &chars = allsuffixes[i]; + uint32_t size3 = suffix * 3; // max Utf8 length + bool isMalloc = (suffix > SMALL); + if (isMalloc) + { + chars.malloc(size3); + } + else + { + if (!charbuf.canAppend(size3 + 1)) + { + assert(charbuf.allocated == 0 || tmallocs.contains(charbuf.base())); + charbuf.init(CHUNK); // Reset to new buffer. + tmallocs.add(charbuf.base()); + } + chars.set(charbuf.grow(size3 + 1), size3); + } + + byte *chp = chars.ptr; + for (int j = 0; j < suffix; j++) + { + unsigned short ch = cp_Utf8_chars.getInt(); + chp = store_Utf8_char(chp, ch); + } + // shrink to fit: + if (isMalloc) + { + chars.realloc(chp - chars.ptr); + tmallocs.add(chars.ptr); // free it later + } + else + { + int shrink = (int)(chars.limit() - chp); + chars.len -= shrink; + charbuf.b.len -= shrink; // ungrow to reclaim buffer space + // Note that we did not reclaim the final '\0'. + assert(chars.limit() == charbuf.limit() - 1); + assert(strlen((char *)chars.ptr) == chars.len); + } + } + // cp_Utf8_chars.done(); + + // Fourth band: Go back and size the specially packed strings. + int maxlen = 0; + cp_Utf8_big_suffix.readData(nbigsuf); + cp_Utf8_suffix.rewind(); + for (i = 0; i < len; i++) + { + int suffix = (i < SUFFIX_SKIP_1) ? 0 : cp_Utf8_suffix.getInt(); + int prefix = (i < PREFIX_SKIP_2) ? 0 : cp_Utf8_prefix.getInt(); + if (prefix < 0 || prefix + suffix < 0) + { + unpack_abort("bad utf8 prefix"); + } + bytes &chars = allsuffixes[i]; + if (suffix == 0 && i >= SUFFIX_SKIP_1) + { + suffix = cp_Utf8_big_suffix.getInt(); + assert(chars.ptr == nullptr); + chars.len = suffix; // just a momentary hack + } + else + { + assert(chars.ptr != nullptr); + } + if (maxlen < prefix + suffix) + { + maxlen = prefix + suffix; + } + } + // cp_Utf8_suffix.done(); // will use allsuffixes[i].len (ptr!=nullptr) + // cp_Utf8_big_suffix.done(); // will use allsuffixes[i].len + + // Fifth band(s): Get the specially packed characters. + cp_Utf8_big_suffix.rewind(); + for (i = 0; i < len; i++) + { + bytes &chars = allsuffixes[i]; + if (chars.ptr != nullptr) + continue; // already input + int suffix = (int)chars.len; // pick up the hack + uint32_t size3 = suffix * 3; + if (suffix == 0) + continue; // done with empty string + chars.malloc(size3); + byte *chp = chars.ptr; + band saved_band = cp_Utf8_big_chars; + cp_Utf8_big_chars.readData(suffix); + for (int j = 0; j < suffix; j++) + { + unsigned short ch = cp_Utf8_big_chars.getInt(); + chp = store_Utf8_char(chp, ch); + } + chars.realloc(chp - chars.ptr); + tmallocs.add(chars.ptr); // free it later + // cp_Utf8_big_chars.done(); + cp_Utf8_big_chars = saved_band; // reset the band for the next string + } + cp_Utf8_big_chars.readData(0); // zero chars + // cp_Utf8_big_chars.done(); + + // Finally, sew together all the prefixes and suffixes. + bytes bigbuf; + bigbuf.malloc(maxlen * 3 + 1); // max Utf8 length, plus slop for nullptr + int prevlen = 0; // previous string length (in chars) + tmallocs.add(bigbuf.ptr); // free after this block + cp_Utf8_prefix.rewind(); + for (i = 0; i < len; i++) + { + bytes &chars = allsuffixes[i]; + int prefix = (i < PREFIX_SKIP_2) ? 0 : cp_Utf8_prefix.getInt(); + int suffix = (int)chars.len; + byte *fillp; + // by induction, the buffer is already filled with the prefix + // make sure the prefix value is not corrupted, though: + if (prefix > prevlen) + { + unpack_abort("utf8 prefix overflow"); + return; + } + fillp = skip_Utf8_chars(bigbuf.ptr, prefix); + // copy the suffix into the same buffer: + fillp = chars.writeTo(fillp); + assert(bigbuf.inBounds(fillp)); + *fillp = 0; // bigbuf must contain a well-formed Utf8 string + int length = (int)(fillp - bigbuf.ptr); + bytes &value = cpMap[i].value.b; + value.set(U_NEW(byte, add_size(length, 1)), length); + value.copyFrom(bigbuf.ptr, length); + // Index all Utf8 strings + entry *&htref = cp.hashTabRef(CONSTANT_Utf8, value); + if (htref == nullptr) + { + // Note that if two identical strings are transmitted, + // the first is taken to be the canonical one. + htref = &cpMap[i]; + } + prevlen = prefix + suffix; + } + // cp_Utf8_prefix.done(); + + // Free intermediate buffers. + free_temps(); +} + +void unpacker::read_single_words(band &cp_band, entry *cpMap, int len) +{ + cp_band.readData(len); + for (int i = 0; i < len; i++) + { + cpMap[i].value.i = cp_band.getInt(); // coding handles signs OK + } +} + +void unpacker::read_double_words(band &cp_bands, entry *cpMap, int len) +{ + band &cp_band_hi = cp_bands; + band &cp_band_lo = cp_bands.nextBand(); + cp_band_hi.readData(len); + cp_band_lo.readData(len); + for (int i = 0; i < len; i++) + { + cpMap[i].value.l = cp_band_hi.getLong(cp_band_lo, true); + } + // cp_band_hi.done(); + // cp_band_lo.done(); +} + +void unpacker::read_single_refs(band &cp_band, byte refTag, entry *cpMap, int len) +{ + assert(refTag == CONSTANT_Utf8); + cp_band.setIndexByTag(refTag); + cp_band.readData(len); + int indexTag = (cp_band.bn == e_cp_Class) ? CONSTANT_Class : 0; + for (int i = 0; i < len; i++) + { + entry &e = cpMap[i]; + e.refs = U_NEW(entry *, e.nrefs = 1); + entry *utf = cp_band.getRef(); + e.refs[0] = utf; + e.value.b = utf->value.b; // copy value of Utf8 string to self + if (indexTag != 0) + { + // Maintain cross-reference: + entry *&htref = cp.hashTabRef(indexTag, e.value.b); + if (htref == nullptr) + { + // Note that if two identical classes are transmitted, + // the first is taken to be the canonical one. + htref = &e; + } + } + } + // cp_band.done(); +} + +void unpacker::read_double_refs(band &cp_band, byte ref1Tag, byte ref2Tag, entry *cpMap, + int len) +{ + band &cp_band1 = cp_band; + band &cp_band2 = cp_band.nextBand(); + cp_band1.setIndexByTag(ref1Tag); + cp_band2.setIndexByTag(ref2Tag); + cp_band1.readData(len); + cp_band2.readData(len); + for (int i = 0; i < len; i++) + { + entry &e = cpMap[i]; + e.refs = U_NEW(entry *, e.nrefs = 2); + e.refs[0] = cp_band1.getRef(); + e.refs[1] = cp_band2.getRef(); + } + // cp_band1.done(); + // cp_band2.done(); +} + +// Cf. PackageReader.readSignatureBands +void unpacker::read_signature_values(entry *cpMap, int len) +{ + cp_Signature_form.setIndexByTag(CONSTANT_Utf8); + cp_Signature_form.readData(len); + int ncTotal = 0; + int i; + for (i = 0; i < len; i++) + { + entry &e = cpMap[i]; + entry &form = *cp_Signature_form.getRef(); + int nc = 0; + + for (const char *ncp = form.utf8String(); *ncp; ncp++) + { + if (*ncp == 'L') + nc++; + } + + ncTotal += nc; + e.refs = U_NEW(entry *, cpMap[i].nrefs = 1 + nc); + e.refs[0] = &form; + } + // cp_Signature_form.done(); + cp_Signature_classes.setIndexByTag(CONSTANT_Class); + cp_Signature_classes.readData(ncTotal); + for (i = 0; i < len; i++) + { + entry &e = cpMap[i]; + for (int j = 1; j < e.nrefs; j++) + { + e.refs[j] = cp_Signature_classes.getRef(); + } + } + // cp_Signature_classes.done(); +} + +// Cf. PackageReader.readConstantPool +void unpacker::read_cp() +{ + int i; + + for (int k = 0; k < (int)N_TAGS_IN_ORDER; k++) + { + byte tag = TAGS_IN_ORDER[k]; + int len = cp.tag_count[tag]; + int base = cp.tag_base[tag]; + + entry *cpMap = &cp.entries[base]; + for (i = 0; i < len; i++) + { + cpMap[i].tag = tag; + cpMap[i].inord = i; + } + + switch (tag) + { + case CONSTANT_Utf8: + read_Utf8_values(cpMap, len); + break; + case CONSTANT_Integer: + read_single_words(cp_Int, cpMap, len); + break; + case CONSTANT_Float: + read_single_words(cp_Float, cpMap, len); + break; + case CONSTANT_Long: + read_double_words(cp_Long_hi /*& cp_Long_lo*/, cpMap, len); + break; + case CONSTANT_Double: + read_double_words(cp_Double_hi /*& cp_Double_lo*/, cpMap, len); + break; + case CONSTANT_String: + read_single_refs(cp_String, CONSTANT_Utf8, cpMap, len); + break; + case CONSTANT_Class: + read_single_refs(cp_Class, CONSTANT_Utf8, cpMap, len); + break; + case CONSTANT_Signature: + read_signature_values(cpMap, len); + break; + case CONSTANT_NameandType: + read_double_refs(cp_Descr_name /*& cp_Descr_type*/, CONSTANT_Utf8, + CONSTANT_Signature, cpMap, len); + break; + case CONSTANT_Fieldref: + read_double_refs(cp_Field_class /*& cp_Field_desc*/, CONSTANT_Class, + CONSTANT_NameandType, cpMap, len); + break; + case CONSTANT_Methodref: + read_double_refs(cp_Method_class /*& cp_Method_desc*/, CONSTANT_Class, + CONSTANT_NameandType, cpMap, len); + break; + case CONSTANT_InterfaceMethodref: + read_double_refs(cp_Imethod_class /*& cp_Imethod_desc*/, CONSTANT_Class, + CONSTANT_NameandType, cpMap, len); + break; + default: + assert(false); + break; + } + } + + cp.expandSignatures(); + cp.initMemberIndexes(); + +#define SNAME(n, s) #s "\0" + const char *symNames = (ALL_ATTR_DO(SNAME) "<init>"); +#undef SNAME + + for (int sn = 0; sn < constant_pool::s_LIMIT; sn++) + { + assert(symNames[0] >= '0' && symNames[0] <= 'Z'); // sanity + bytes name; + name.set(symNames); + if (name.len > 0 && name.ptr[0] != '0') + { + cp.sym[sn] = cp.ensureUtf8(name); + } + symNames += name.len + 1; // skip trailing nullptr to next name + } + + band::initIndexes(this); +} + +static band *no_bands[] = {nullptr}; // shared empty body + +inline band &unpacker::attr_definitions::fixed_band(int e_class_xxx) +{ + return u->all_bands[xxx_flags_hi_bn + (e_class_xxx - e_class_flags_hi)]; +} +inline band &unpacker::attr_definitions::xxx_flags_hi() +{ + return fixed_band(e_class_flags_hi); +} +inline band &unpacker::attr_definitions::xxx_flags_lo() +{ + return fixed_band(e_class_flags_lo); +} +inline band &unpacker::attr_definitions::xxx_attr_count() +{ + return fixed_band(e_class_attr_count); +} +inline band &unpacker::attr_definitions::xxx_attr_indexes() +{ + return fixed_band(e_class_attr_indexes); +} +inline band &unpacker::attr_definitions::xxx_attr_calls() +{ + return fixed_band(e_class_attr_calls); +} + +inline unpacker::layout_definition * +unpacker::attr_definitions::defineLayout(int idx, entry *nameEntry, const char *layout) +{ + const char *name = nameEntry->value.b.strval(); + layout_definition *lo = defineLayout(idx, name, layout); + lo->nameEntry = nameEntry; + return lo; +} + +unpacker::layout_definition *unpacker::attr_definitions::defineLayout(int idx, const char *name, + const char *layout) +{ + assert(flag_limit != 0); // must be set up already + if (idx >= 0) + { + // Fixed attr. + if (idx >= (int)flag_limit) + unpack_abort("attribute index too large"); + if (isRedefined(idx)) + unpack_abort("redefined attribute index"); + redef |= ((uint64_t)1 << idx); + } + else + { + idx = flag_limit + overflow_count.length(); + overflow_count.add(0); // make a new counter + } + layout_definition *lo = U_NEW(layout_definition, 1); + lo->idx = idx; + lo->name = name; + lo->layout = layout; + for (int adds = (idx + 1) - layouts.length(); adds > 0; adds--) + { + layouts.add(nullptr); + } + layouts.get(idx) = lo; + return lo; +} + +band **unpacker::attr_definitions::buildBands(unpacker::layout_definition *lo) +{ + int i; + if (lo->elems != nullptr) + return lo->bands(); + if (lo->layout[0] == '\0') + { + lo->elems = no_bands; + } + else + { + // Create bands for this attribute by parsing the layout. + bool hasCallables = lo->hasCallables(); + bands_made = 0x10000; // base number for bands made + const char *lp = lo->layout; + lp = parseLayout(lp, lo->elems, -1); + if (lp[0] != '\0' || band_stack.length() > 0) + { + unpack_abort("garbage at end of layout"); + } + band_stack.popTo(0); + + // Fix up callables to point at their callees. + band **bands = lo->elems; + assert(bands == lo->bands()); + int num_callables = 0; + if (hasCallables) + { + while (bands[num_callables] != nullptr) + { + if (bands[num_callables]->le_kind != EK_CBLE) + { + unpack_abort("garbage mixed with callables"); + break; + } + num_callables += 1; + } + } + for (i = 0; i < calls_to_link.length(); i++) + { + band &call = *(band *)calls_to_link.get(i); + assert(call.le_kind == EK_CALL); + // Determine the callee. + int call_num = call.le_len; + if (call_num < 0 || call_num >= num_callables) + { + unpack_abort("bad call in layout"); + break; + } + band &cble = *bands[call_num]; + // Link the call to it. + call.le_body[0] = &cble; + // Distinguish backward calls and callables: + assert(cble.le_kind == EK_CBLE); + // FIXME: hit this one + // assert(cble.le_len == call_num); + cble.le_back |= call.le_back; + } + calls_to_link.popTo(0); + } + return lo->elems; +} + +/* attribute layout language parser + + attribute_layout: + ( layout_element )* | ( callable )+ + layout_element: + ( integral | replication | union | call | reference ) + + callable: + '[' body ']' + body: + ( layout_element )+ + + integral: + ( unsigned_int | signed_int | bc_index | bc_offset | flag ) + unsigned_int: + uint_type + signed_int: + 'S' uint_type + any_int: + ( unsigned_int | signed_int ) + bc_index: + ( 'P' uint_type | 'PO' uint_type ) + bc_offset: + 'O' any_int + flag: + 'F' uint_type + uint_type: + ( 'B' | 'H' | 'I' | 'V' ) + + replication: + 'N' uint_type '[' body ']' + + union: + 'T' any_int (union_case)* '(' ')' '[' (body)? ']' + union_case: + '(' union_case_tag (',' union_case_tag)* ')' '[' (body)? ']' + union_case_tag: + ( numeral | numeral '-' numeral ) + call: + '(' numeral ')' + + reference: + reference_type ( 'N' )? uint_type + reference_type: + ( constant_ref | schema_ref | utf8_ref | untyped_ref ) + constant_ref: + ( 'KI' | 'KJ' | 'KF' | 'KD' | 'KS' | 'KQ' ) + schema_ref: + ( 'RC' | 'RS' | 'RD' | 'RF' | 'RM' | 'RI' ) + utf8_ref: + 'RU' + untyped_ref: + 'RQ' + + numeral: + '(' ('-')? (digit)+ ')' + digit: + ( '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' ) + +*/ + +const char *unpacker::attr_definitions::parseIntLayout(const char *lp, band *&res, byte le_kind, + bool can_be_signed) +{ + band *b = U_NEW(band, 1); + char le = *lp++; + int spec = UNSIGNED5_spec; + if (le == 'S' && can_be_signed) + { + // Note: This is the last use of sign. There is no 'EF_SIGN'. + spec = SIGNED5_spec; + le = *lp++; + } + else if (le == 'B') + { + spec = BYTE1_spec; // unsigned byte + } + b->init(u, bands_made++, spec); + b->le_kind = le_kind; + int le_len = 0; + switch (le) + { + case 'B': + le_len = 1; + break; + case 'H': + le_len = 2; + break; + case 'I': + le_len = 4; + break; + case 'V': + le_len = 0; + break; + default: + unpack_abort("bad layout element"); + } + b->le_len = le_len; + band_stack.add(b); + res = b; + return lp; +} + +const char *unpacker::attr_definitions::parseNumeral(const char *lp, int &res) +{ + bool sgn = false; + if (*lp == '0') + { + res = 0; + return lp + 1; + } // special case '0' + if (*lp == '-') + { + sgn = true; + lp++; + } + const char *dp = lp; + int con = 0; + while (*dp >= '0' && *dp <= '9') + { + int con0 = con; + con *= 10; + con += (*dp++) - '0'; + if (con <= con0) + { + con = -1; + break; + } // numeral overflow + } + if (lp == dp) + { + unpack_abort("missing numeral in layout"); + } + lp = dp; + if (con < 0 && !(sgn && con == -con)) + { + // (Portability note: Misses the error if int is not 32 bits.) + unpack_abort("numeral overflow"); + } + if (sgn) + con = -con; + res = con; + return lp; +} + +band **unpacker::attr_definitions::popBody(int bs_base) +{ + // Return everything that was pushed, as a nullptr-terminated pointer array. + int bs_limit = band_stack.length(); + if (bs_base == bs_limit) + { + return no_bands; + } + else + { + int nb = bs_limit - bs_base; + band **res = U_NEW(band *, add_size(nb, 1)); + for (int i = 0; i < nb; i++) + { + band *b = (band *)band_stack.get(bs_base + i); + res[i] = b; + } + band_stack.popTo(bs_base); + return res; + } +} + +const char *unpacker::attr_definitions::parseLayout(const char *lp, band **&res, int curCble) +{ + int bs_base = band_stack.length(); + bool top_level = (bs_base == 0); + band *b; + enum + { + can_be_signed = true + }; // optional arg to parseIntLayout + + for (bool done = false; !done;) + { + switch (*lp++) + { + case 'B': + case 'H': + case 'I': + case 'V': // unsigned_int + case 'S': // signed_int + --lp; // reparse + case 'F': + lp = parseIntLayout(lp, b, EK_INT); + break; + case 'P': + { + int le_bci = EK_BCI; + if (*lp == 'O') + { + ++lp; + le_bci = EK_BCID; + } + assert(*lp != 'S'); // no PSH, etc. + lp = parseIntLayout(lp, b, EK_INT); + b->le_bci = le_bci; + if (le_bci == EK_BCI) + b->defc = coding::findBySpec(BCI5_spec); + else + b->defc = coding::findBySpec(BRANCH5_spec); + } + break; + case 'O': + lp = parseIntLayout(lp, b, EK_INT, can_be_signed); + b->le_bci = EK_BCO; + b->defc = coding::findBySpec(BRANCH5_spec); + break; + case 'N': // replication: 'N' uint32_t '[' elem ... ']' + lp = parseIntLayout(lp, b, EK_REPL); + assert(*lp == '['); + ++lp; + lp = parseLayout(lp, b->le_body, curCble); + break; + case 'T': // union: 'T' any_int union_case* '(' ')' '[' body ']' + lp = parseIntLayout(lp, b, EK_UN, can_be_signed); + { + int union_base = band_stack.length(); + for (;;) + { // for each case + band &k_case = *U_NEW(band, 1); + band_stack.add(&k_case); + k_case.le_kind = EK_CASE; + k_case.bn = bands_made++; + if (*lp++ != '(') + { + unpack_abort("bad union case"); + return ""; + } + if (*lp++ != ')') + { + --lp; // reparse + // Read some case values. (Use band_stack for temp. storage.) + int case_base = band_stack.length(); + for (;;) + { + int caseval = 0; + lp = parseNumeral(lp, caseval); + band_stack.add((void *)(size_t)caseval); + if (*lp == '-') + { + // new in version 160, allow (1-5) for (1,2,3,4,5) + if (u->majver < JAVA6_PACKAGE_MAJOR_VERSION) + { + unpack_abort( + "bad range in union case label (old archive format)"); + return ""; + } + int caselimit = caseval; + lp++; + lp = parseNumeral(lp, caselimit); + if (caseval >= caselimit || + (uint32_t)(caselimit - caseval) > 0x10000) + { + // Note: 0x10000 is arbitrary implementation restriction. + // We can remove it later if it's important to. + unpack_abort("bad range in union case label"); + } + for (;;) + { + ++caseval; + band_stack.add((void *)(size_t)caseval); + if (caseval == caselimit) + break; + } + } + if (*lp != ',') + break; + lp++; + } + if (*lp++ != ')') + { + unpack_abort("bad case label"); + } + // save away the case labels + int ntags = band_stack.length() - case_base; + int *tags = U_NEW(int, add_size(ntags, 1)); + k_case.le_casetags = tags; + *tags++ = ntags; + for (int i = 0; i < ntags; i++) + { + *tags++ = ptrlowbits(band_stack.get(case_base + i)); + } + band_stack.popTo(case_base); + } + // Got le_casetags. Now grab the body. + assert(*lp == '['); + ++lp; + lp = parseLayout(lp, k_case.le_body, curCble); + if (k_case.le_casetags == nullptr) + break; // done + } + b->le_body = popBody(union_base); + } + break; + case '(': // call: '(' -?NN* ')' + { + band &call = *U_NEW(band, 1); + band_stack.add(&call); + call.le_kind = EK_CALL; + call.bn = bands_made++; + call.le_body = U_NEW(band *, 2); // fill in later + int call_num = 0; + lp = parseNumeral(lp, call_num); + call.le_back = (call_num <= 0); + call_num += curCble; // numeral is self-relative offset + call.le_len = call_num; // use le_len as scratch + calls_to_link.add(&call); + if (*lp++ != ')') + { + unpack_abort("bad call label"); + } + } + break; + case 'K': // reference_type: constant_ref + case 'R': // reference_type: schema_ref + { + int ixTag = CONSTANT_None; + if (lp[-1] == 'K') + { + switch (*lp++) + { + case 'I': + ixTag = CONSTANT_Integer; + break; + case 'J': + ixTag = CONSTANT_Long; + break; + case 'F': + ixTag = CONSTANT_Float; + break; + case 'D': + ixTag = CONSTANT_Double; + break; + case 'S': + ixTag = CONSTANT_String; + break; + case 'Q': + ixTag = CONSTANT_Literal; + break; + } + } + else + { + switch (*lp++) + { + case 'C': + ixTag = CONSTANT_Class; + break; + case 'S': + ixTag = CONSTANT_Signature; + break; + case 'D': + ixTag = CONSTANT_NameandType; + break; + case 'F': + ixTag = CONSTANT_Fieldref; + break; + case 'M': + ixTag = CONSTANT_Methodref; + break; + case 'I': + ixTag = CONSTANT_InterfaceMethodref; + break; + case 'U': + ixTag = CONSTANT_Utf8; + break; // utf8_ref + case 'Q': + ixTag = CONSTANT_All; + break; // untyped_ref + } + } + if (ixTag == CONSTANT_None) + { + unpack_abort("bad reference layout"); + break; + } + bool nullOK = false; + if (*lp == 'N') + { + nullOK = true; + lp++; + } + lp = parseIntLayout(lp, b, EK_REF); + b->defc = coding::findBySpec(UNSIGNED5_spec); + b->initRef(ixTag, nullOK); + } + break; + case '[': + { + // [callable1][callable2]... + if (!top_level) + { + unpack_abort("bad nested callable"); + break; + } + curCble += 1; + band &cble = *U_NEW(band, 1); + band_stack.add(&cble); + cble.le_kind = EK_CBLE; + cble.bn = bands_made++; + lp = parseLayout(lp, cble.le_body, curCble); + } + break; + case ']': + // Hit a closing brace. This ends whatever body we were in. + done = true; + break; + case '\0': + // Hit a nullptr. Also ends the (top-level) body. + --lp; // back up, so caller can see the nullptr also + done = true; + break; + default: + unpack_abort("bad layout"); + } + } + + // Return the accumulated bands: + res = popBody(bs_base); + return lp; +} + +void unpacker::read_attr_defs() +{ + int i; + + // Tell each AD which attrc it is and where its fixed flags are: + attr_defs[ATTR_CONTEXT_CLASS].attrc = ATTR_CONTEXT_CLASS; + attr_defs[ATTR_CONTEXT_CLASS].xxx_flags_hi_bn = e_class_flags_hi; + attr_defs[ATTR_CONTEXT_FIELD].attrc = ATTR_CONTEXT_FIELD; + attr_defs[ATTR_CONTEXT_FIELD].xxx_flags_hi_bn = e_field_flags_hi; + attr_defs[ATTR_CONTEXT_METHOD].attrc = ATTR_CONTEXT_METHOD; + attr_defs[ATTR_CONTEXT_METHOD].xxx_flags_hi_bn = e_method_flags_hi; + attr_defs[ATTR_CONTEXT_CODE].attrc = ATTR_CONTEXT_CODE; + attr_defs[ATTR_CONTEXT_CODE].xxx_flags_hi_bn = e_code_flags_hi; + + // Decide whether bands for the optional high flag words are present. + attr_defs[ATTR_CONTEXT_CLASS] + .setHaveLongFlags((archive_options & AO_HAVE_CLASS_FLAGS_HI) != 0); + attr_defs[ATTR_CONTEXT_FIELD] + .setHaveLongFlags((archive_options & AO_HAVE_FIELD_FLAGS_HI) != 0); + attr_defs[ATTR_CONTEXT_METHOD] + .setHaveLongFlags((archive_options & AO_HAVE_METHOD_FLAGS_HI) != 0); + attr_defs[ATTR_CONTEXT_CODE] + .setHaveLongFlags((archive_options & AO_HAVE_CODE_FLAGS_HI) != 0); + + // Set up built-in attrs. + // (The simple ones are hard-coded. The metadata layouts are not.) + const char *md_layout = ( +// parameter annotations: +#define MDL0 "[NB[(1)]]" + MDL0 +// annotations: +#define MDL1 \ + "[NH[(1)]]" \ + "[RSHNH[RUH(1)]]" + MDL1 + // member_value: + "[TB" + "(66,67,73,83,90)[KIH]" + "(68)[KDH]" + "(70)[KFH]" + "(74)[KJH]" + "(99)[RSH]" + "(101)[RSHRUH]" + "(115)[RUH]" + "(91)[NH[(0)]]" + "(64)[" + // nested annotation: + "RSH" + "NH[RUH(0)]" + "]" + "()[]" + "]"); + + const char *md_layout_P = md_layout; + const char *md_layout_A = md_layout + strlen(MDL0); + const char *md_layout_V = md_layout + strlen(MDL0 MDL1); + assert(0 == strncmp(&md_layout_A[-3], ")]][", 4)); + assert(0 == strncmp(&md_layout_V[-3], ")]][", 4)); + + for (i = 0; i < ATTR_CONTEXT_LIMIT; i++) + { + attr_definitions &ad = attr_defs[i]; + ad.defineLayout(X_ATTR_RuntimeVisibleAnnotations, "RuntimeVisibleAnnotations", + md_layout_A); + ad.defineLayout(X_ATTR_RuntimeInvisibleAnnotations, "RuntimeInvisibleAnnotations", + md_layout_A); + if (i != ATTR_CONTEXT_METHOD) + continue; + ad.defineLayout(METHOD_ATTR_RuntimeVisibleParameterAnnotations, + "RuntimeVisibleParameterAnnotations", md_layout_P); + ad.defineLayout(METHOD_ATTR_RuntimeInvisibleParameterAnnotations, + "RuntimeInvisibleParameterAnnotations", md_layout_P); + ad.defineLayout(METHOD_ATTR_AnnotationDefault, "AnnotationDefault", md_layout_V); + } + + attr_definition_headers.readData(attr_definition_count); + attr_definition_name.readData(attr_definition_count); + attr_definition_layout.readData(attr_definition_count); + +// Initialize correct predef bits, to distinguish predefs from new defs. +#define ORBIT(n, s) | ((uint64_t)1 << n) + attr_defs[ATTR_CONTEXT_CLASS].predef = (0 X_ATTR_DO(ORBIT) CLASS_ATTR_DO(ORBIT)); + attr_defs[ATTR_CONTEXT_FIELD].predef = (0 X_ATTR_DO(ORBIT) FIELD_ATTR_DO(ORBIT)); + attr_defs[ATTR_CONTEXT_METHOD].predef = (0 X_ATTR_DO(ORBIT) METHOD_ATTR_DO(ORBIT)); + attr_defs[ATTR_CONTEXT_CODE].predef = (0 O_ATTR_DO(ORBIT) CODE_ATTR_DO(ORBIT)); +#undef ORBIT + // Clear out the redef bits, folding them back into predef. + for (i = 0; i < ATTR_CONTEXT_LIMIT; i++) + { + attr_defs[i].predef |= attr_defs[i].redef; + attr_defs[i].redef = 0; + } + + // Now read the transmitted locally defined attrs. + // This will set redef bits again. + for (i = 0; i < attr_definition_count; i++) + { + int header = attr_definition_headers.getByte(); + int attrc = ADH_BYTE_CONTEXT(header); + int idx = ADH_BYTE_INDEX(header); + entry *name = attr_definition_name.getRef(); + entry *layout = attr_definition_layout.getRef(); + attr_defs[attrc].defineLayout(idx, name, layout->value.b.strval()); + } +} + +#define NO_ENTRY_YET ((entry *)-1) + +static bool isDigitString(bytes &x, int beg, int end) +{ + if (beg == end) + return false; // nullptr string + byte *xptr = x.ptr; + for (int i = beg; i < end; i++) + { + char ch = xptr[i]; + if (!(ch >= '0' && ch <= '9')) + return false; + } + return true; +} + +enum +{ // constants for parsing class names + SLASH_MIN = '.', + SLASH_MAX = '/', + DOLLAR_MIN = 0, + DOLLAR_MAX = '-'}; + +static int lastIndexOf(int chmin, int chmax, bytes &x, int pos) +{ + byte *ptr = x.ptr; + for (byte *cp = ptr + pos; --cp >= ptr;) + { + assert(x.inBounds(cp)); + if (*cp >= chmin && *cp <= chmax) + return (int)(cp - ptr); + } + return -1; +} + +inner_class *constant_pool::getIC(entry *inner) +{ + if (inner == nullptr) + return nullptr; + assert(inner->tag == CONSTANT_Class); + if (inner->inord == NO_INORD) + return nullptr; + inner_class *ic = ic_index[inner->inord]; + assert(ic == nullptr || ic->inner == inner); + return ic; +} + +inner_class *constant_pool::getFirstChildIC(entry *outer) +{ + if (outer == nullptr) + return nullptr; + assert(outer->tag == CONSTANT_Class); + if (outer->inord == NO_INORD) + return nullptr; + inner_class *ic = ic_child_index[outer->inord]; + assert(ic == nullptr || ic->outer == outer); + return ic; +} + +inner_class *constant_pool::getNextChildIC(inner_class *child) +{ + inner_class *ic = child->next_sibling; + assert(ic == nullptr || ic->outer == child->outer); + return ic; +} + +void unpacker::read_ics() +{ + int i; + int index_size = cp.tag_count[CONSTANT_Class]; + inner_class **ic_index = U_NEW(inner_class *, index_size); + inner_class **ic_child_index = U_NEW(inner_class *, index_size); + cp.ic_index = ic_index; + cp.ic_child_index = ic_child_index; + ics = U_NEW(inner_class, ic_count); + ic_this_class.readData(ic_count); + ic_flags.readData(ic_count); + // Scan flags to get count of long-form bands. + int long_forms = 0; + for (i = 0; i < ic_count; i++) + { + int flags = ic_flags.getInt(); // may be long form! + if ((flags & ACC_IC_LONG_FORM) != 0) + { + long_forms += 1; + ics[i].name = NO_ENTRY_YET; + } + flags &= ~ACC_IC_LONG_FORM; + entry *inner = ic_this_class.getRef(); + uint32_t inord = inner->inord; + assert(inord < (uint32_t)cp.tag_count[CONSTANT_Class]); + if (ic_index[inord] != nullptr) + { + unpack_abort("identical inner class"); + break; + } + ic_index[inord] = &ics[i]; + ics[i].inner = inner; + ics[i].flags = flags; + assert(cp.getIC(inner) == &ics[i]); + } + // ic_this_class.done(); + // ic_flags.done(); + ic_outer_class.readData(long_forms); + ic_name.readData(long_forms); + for (i = 0; i < ic_count; i++) + { + if (ics[i].name == NO_ENTRY_YET) + { + // Long form. + ics[i].outer = ic_outer_class.getRefN(); + ics[i].name = ic_name.getRefN(); + } + else + { + // Fill in outer and name based on inner. + bytes &n = ics[i].inner->value.b; + bytes pkgOuter; + bytes number; + bytes name; + // Parse n into pkgOuter and name (and number). + int dollar1, dollar2; // pointers to $ in the pattern + // parse n = (<pkg>/)*<outer>($<number>)?($<name>)? + int nlen = (int)n.len; + int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, nlen) + 1; + dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, nlen); + if (dollar2 < 0) + { + unpack_abort(); + } + assert(dollar2 >= pkglen); + if (isDigitString(n, dollar2 + 1, nlen)) + { + // n = (<pkg>/)*<outer>$<number> + number = n.slice(dollar2 + 1, nlen); + name.set(nullptr, 0); + dollar1 = dollar2; + } + else if (pkglen < (dollar1 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, dollar2 - 1)) && + isDigitString(n, dollar1 + 1, dollar2)) + { + // n = (<pkg>/)*<outer>$<number>$<name> + number = n.slice(dollar1 + 1, dollar2); + name = n.slice(dollar2 + 1, nlen); + } + else + { + // n = (<pkg>/)*<outer>$<name> + dollar1 = dollar2; + number.set(nullptr, 0); + name = n.slice(dollar2 + 1, nlen); + } + if (number.ptr == nullptr) + pkgOuter = n.slice(0, dollar1); + else + pkgOuter.set(nullptr, 0); + + if (pkgOuter.ptr != nullptr) + ics[i].outer = cp.ensureClass(pkgOuter); + + if (name.ptr != nullptr) + ics[i].name = cp.ensureUtf8(name); + } + + // update child/sibling list + if (ics[i].outer != nullptr) + { + uint32_t outord = ics[i].outer->inord; + if (outord != NO_INORD) + { + assert(outord < (uint32_t)cp.tag_count[CONSTANT_Class]); + ics[i].next_sibling = ic_child_index[outord]; + ic_child_index[outord] = &ics[i]; + } + } + } + // ic_outer_class.done(); + // ic_name.done(); +} + +void unpacker::read_classes() +{ + class_this.readData(class_count); + class_super.readData(class_count); + class_interface_count.readData(class_count); + class_interface.readData(class_interface_count.getIntTotal()); + +#if 0 + int i; + // Make a little mark on super-classes. + for (i = 0; i < class_count; i++) { + entry* e = class_super.getRefN(); + if (e != nullptr) e->bits |= entry::EB_SUPER; + } + class_super.rewind(); +#endif + + // Members. + class_field_count.readData(class_count); + class_method_count.readData(class_count); + + int field_count = class_field_count.getIntTotal(); + int method_count = class_method_count.getIntTotal(); + + field_descr.readData(field_count); + read_attrs(ATTR_CONTEXT_FIELD, field_count); + method_descr.readData(method_count); + read_attrs(ATTR_CONTEXT_METHOD, method_count); + read_attrs(ATTR_CONTEXT_CLASS, class_count); + read_code_headers(); +} + +int unpacker::attr_definitions::predefCount(uint32_t idx) +{ + return isPredefined(idx) ? flag_count[idx] : 0; +} + +void unpacker::read_attrs(int attrc, int obj_count) +{ + attr_definitions &ad = attr_defs[attrc]; + assert(ad.attrc == attrc); + + int i, idx, count; + + bool haveLongFlags = ad.haveLongFlags(); + + band &xxx_flags_hi = ad.xxx_flags_hi(); + if (haveLongFlags) + xxx_flags_hi.readData(obj_count); + + band &xxx_flags_lo = ad.xxx_flags_lo(); + xxx_flags_lo.readData(obj_count); + + // pre-scan flags, counting occurrences of each index bit + uint64_t indexMask = ad.flagIndexMask(); // which flag bits are index bits? + for (i = 0; i < obj_count; i++) + { + uint64_t indexBits = xxx_flags_hi.getLong(xxx_flags_lo, haveLongFlags); + if ((indexBits & ~indexMask) > (ushort) - 1) + { + unpack_abort("undefined attribute flag bit"); + return; + } + indexBits &= indexMask; // ignore classfile flag bits + for (idx = 0; indexBits != 0; idx++, indexBits >>= 1) + { + ad.flag_count[idx] += (int)(indexBits & 1); + } + } + // we'll scan these again later for output: + xxx_flags_lo.rewind(); + xxx_flags_hi.rewind(); + + band &xxx_attr_count = ad.xxx_attr_count(); + // There is one count element for each 1<<16 bit set in flags: + xxx_attr_count.readData(ad.predefCount(X_ATTR_OVERFLOW)); + + band &xxx_attr_indexes = ad.xxx_attr_indexes(); + int overflowIndexCount = xxx_attr_count.getIntTotal(); + xxx_attr_indexes.readData(overflowIndexCount); + // pre-scan attr indexes, counting occurrences of each value + for (i = 0; i < overflowIndexCount; i++) + { + idx = xxx_attr_indexes.getInt(); + if (!ad.isIndex(idx)) + { + unpack_abort("attribute index out of bounds"); + return; + } + ad.getCount(idx) += 1; + } + xxx_attr_indexes.rewind(); // we'll scan it again later for output + + // We will need a backward call count for each used backward callable. + int backwardCounts = 0; + for (idx = 0; idx < ad.layouts.length(); idx++) + { + layout_definition *lo = ad.getLayout(idx); + if (lo != nullptr && ad.getCount(idx) != 0) + { + // Build the bands lazily, only when they are used. + band **bands = ad.buildBands(lo); + if (lo->hasCallables()) + { + for (i = 0; bands[i] != nullptr; i++) + { + if (bands[i]->le_back) + { + assert(bands[i]->le_kind == EK_CBLE); + backwardCounts += 1; + } + } + } + } + } + ad.xxx_attr_calls().readData(backwardCounts); + + // Read built-in bands. + // Mostly, these are hand-coded equivalents to readBandData(). + switch (attrc) + { + case ATTR_CONTEXT_CLASS: + + count = ad.predefCount(CLASS_ATTR_SourceFile); + class_SourceFile_RUN.readData(count); + + count = ad.predefCount(CLASS_ATTR_EnclosingMethod); + class_EnclosingMethod_RC.readData(count); + class_EnclosingMethod_RDN.readData(count); + + count = ad.predefCount(X_ATTR_Signature); + class_Signature_RS.readData(count); + + ad.readBandData(X_ATTR_RuntimeVisibleAnnotations); + ad.readBandData(X_ATTR_RuntimeInvisibleAnnotations); + + count = ad.predefCount(CLASS_ATTR_InnerClasses); + class_InnerClasses_N.readData(count); + + count = class_InnerClasses_N.getIntTotal(); + class_InnerClasses_RC.readData(count); + class_InnerClasses_F.readData(count); + + // Drop remaining columns wherever flags are zero: + count -= class_InnerClasses_F.getIntCount(0); + class_InnerClasses_outer_RCN.readData(count); + class_InnerClasses_name_RUN.readData(count); + + count = ad.predefCount(CLASS_ATTR_ClassFile_version); + class_ClassFile_version_minor_H.readData(count); + class_ClassFile_version_major_H.readData(count); + break; + + case ATTR_CONTEXT_FIELD: + + count = ad.predefCount(FIELD_ATTR_ConstantValue); + field_ConstantValue_KQ.readData(count); + + count = ad.predefCount(X_ATTR_Signature); + field_Signature_RS.readData(count); + + ad.readBandData(X_ATTR_RuntimeVisibleAnnotations); + ad.readBandData(X_ATTR_RuntimeInvisibleAnnotations); + break; + + case ATTR_CONTEXT_METHOD: + + code_count = ad.predefCount(METHOD_ATTR_Code); + // Code attrs are handled very specially below... + + count = ad.predefCount(METHOD_ATTR_Exceptions); + method_Exceptions_N.readData(count); + count = method_Exceptions_N.getIntTotal(); + method_Exceptions_RC.readData(count); + + count = ad.predefCount(X_ATTR_Signature); + method_Signature_RS.readData(count); + + ad.readBandData(X_ATTR_RuntimeVisibleAnnotations); + ad.readBandData(X_ATTR_RuntimeInvisibleAnnotations); + ad.readBandData(METHOD_ATTR_RuntimeVisibleParameterAnnotations); + ad.readBandData(METHOD_ATTR_RuntimeInvisibleParameterAnnotations); + ad.readBandData(METHOD_ATTR_AnnotationDefault); + break; + + case ATTR_CONTEXT_CODE: + // (keep this code aligned with its brother in unpacker::write_attrs) + count = ad.predefCount(CODE_ATTR_StackMapTable); + // disable this feature in old archives! + if (count != 0 && majver < JAVA6_PACKAGE_MAJOR_VERSION) + { + unpack_abort("undefined StackMapTable attribute (old archive format)"); + return; + } + code_StackMapTable_N.readData(count); + count = code_StackMapTable_N.getIntTotal(); + code_StackMapTable_frame_T.readData(count); + // the rest of it depends in a complicated way on frame tags + { + int fat_frame_count = 0; + int offset_count = 0; + int type_count = 0; + for (int k = 0; k < count; k++) + { + int tag = code_StackMapTable_frame_T.getByte(); + if (tag <= 127) + { + // (64-127) [(2)] + if (tag >= 64) + type_count++; + } + else if (tag <= 251) + { + // (247) [(1)(2)] + // (248-251) [(1)] + if (tag >= 247) + offset_count++; + if (tag == 247) + type_count++; + } + else if (tag <= 254) + { + // (252) [(1)(2)] + // (253) [(1)(2)(2)] + // (254) [(1)(2)(2)(2)] + offset_count++; + type_count += (tag - 251); + } + else + { + // (255) [(1)NH[(2)]NH[(2)]] + fat_frame_count++; + } + } + + // done pre-scanning frame tags: + code_StackMapTable_frame_T.rewind(); + + // deal completely with fat frames: + offset_count += fat_frame_count; + code_StackMapTable_local_N.readData(fat_frame_count); + type_count += code_StackMapTable_local_N.getIntTotal(); + code_StackMapTable_stack_N.readData(fat_frame_count); + type_count += code_StackMapTable_stack_N.getIntTotal(); + // read the rest: + code_StackMapTable_offset.readData(offset_count); + code_StackMapTable_T.readData(type_count); + // (7) [RCH] + count = code_StackMapTable_T.getIntCount(7); + code_StackMapTable_RC.readData(count); + // (8) [PH] + count = code_StackMapTable_T.getIntCount(8); + code_StackMapTable_P.readData(count); + } + + count = ad.predefCount(CODE_ATTR_LineNumberTable); + code_LineNumberTable_N.readData(count); + count = code_LineNumberTable_N.getIntTotal(); + code_LineNumberTable_bci_P.readData(count); + code_LineNumberTable_line.readData(count); + + count = ad.predefCount(CODE_ATTR_LocalVariableTable); + code_LocalVariableTable_N.readData(count); + count = code_LocalVariableTable_N.getIntTotal(); + code_LocalVariableTable_bci_P.readData(count); + code_LocalVariableTable_span_O.readData(count); + code_LocalVariableTable_name_RU.readData(count); + code_LocalVariableTable_type_RS.readData(count); + code_LocalVariableTable_slot.readData(count); + + count = ad.predefCount(CODE_ATTR_LocalVariableTypeTable); + code_LocalVariableTypeTable_N.readData(count); + count = code_LocalVariableTypeTable_N.getIntTotal(); + code_LocalVariableTypeTable_bci_P.readData(count); + code_LocalVariableTypeTable_span_O.readData(count); + code_LocalVariableTypeTable_name_RU.readData(count); + code_LocalVariableTypeTable_type_RS.readData(count); + code_LocalVariableTypeTable_slot.readData(count); + break; + } + + // Read compressor-defined bands. + for (idx = 0; idx < ad.layouts.length(); idx++) + { + if (ad.getLayout(idx) == nullptr) + continue; // none at this fixed index <32 + if (idx < (int)ad.flag_limit && ad.isPredefined(idx)) + continue; // already handled + if (ad.getCount(idx) == 0) + continue; // no attributes of this type (then why transmit layouts?) + ad.readBandData(idx); + } +} + +void unpacker::attr_definitions::readBandData(int idx) +{ + int j; + uint32_t count = getCount(idx); + if (count == 0) + return; + layout_definition *lo = getLayout(idx); + bool hasCallables = lo->hasCallables(); + band **bands = lo->bands(); + if (!hasCallables) + { + // Read through the rest of the bands in a regular way. + readBandData(bands, count); + } + else + { + // Deal with the callables. + // First set up the forward entry count for each callable. + // This is stored on band::length of the callable. + bands[0]->expectMoreLength(count); + for (j = 0; bands[j] != nullptr; j++) + { + band &j_cble = *bands[j]; + assert(j_cble.le_kind == EK_CBLE); + if (j_cble.le_back) + { + // Add in the predicted effects of backward calls, too. + int back_calls = xxx_attr_calls().getInt(); + j_cble.expectMoreLength(back_calls); + // In a moment, more forward calls may increment j_cble.length. + } + } + // Now consult whichever callables have non-zero entry counts. + readBandData(bands, (uint32_t) - 1); + } +} + +// Recursive helper to the previous function: +void unpacker::attr_definitions::readBandData(band **body, uint32_t count) +{ + int j, k; + for (j = 0; body[j] != nullptr; j++) + { + band &b = *body[j]; + if (b.defc != nullptr) + { + // It has data, so read it. + b.readData(count); + } + switch (b.le_kind) + { + case EK_REPL: + { + int reps = b.getIntTotal(); + readBandData(b.le_body, reps); + } + break; + case EK_UN: + { + int remaining = count; + for (k = 0; b.le_body[k] != nullptr; k++) + { + band &k_case = *b.le_body[k]; + int k_count = 0; + if (k_case.le_casetags == nullptr) + { + k_count = remaining; // last (empty) case + } + else + { + int *tags = k_case.le_casetags; + int ntags = *tags++; // 1st element is length (why not?) + while (ntags-- > 0) + { + int tag = *tags++; + k_count += b.getIntCount(tag); + } + } + readBandData(k_case.le_body, k_count); + remaining -= k_count; + } + assert(remaining == 0); + } + break; + case EK_CALL: + // Push the count forward, if it is not a backward call. + if (!b.le_back) + { + band &cble = *b.le_body[0]; + assert(cble.le_kind == EK_CBLE); + cble.expectMoreLength(count); + } + break; + case EK_CBLE: + assert((int)count == -1); // incoming count is meaningless + k = b.length; + assert(k >= 0); + // This is intended and required for non production mode. + assert((b.length = -1)); // make it unable to accept more calls now. + readBandData(b.le_body, k); + break; + } + } +} + +static inline band **findMatchingCase(int matchTag, band **cases) +{ + for (int k = 0; cases[k] != nullptr; k++) + { + band &k_case = *cases[k]; + if (k_case.le_casetags != nullptr) + { + // If it has tags, it must match a tag. + int *tags = k_case.le_casetags; + int ntags = *tags++; // 1st element is length + for (; ntags > 0; ntags--) + { + int tag = *tags++; + if (tag == matchTag) + break; + } + if (ntags == 0) + continue; // does not match + } + return k_case.le_body; + } + return nullptr; +} + +// write attribute band data: +void unpacker::putlayout(band **body) +{ + int i; + int prevBII = -1; + int prevBCI = -1; + if (body == NULL) + { + unpack_abort("putlayout: unexpected NULL for body"); + return; + } + for (i = 0; body[i] != nullptr; i++) + { + band &b = *body[i]; + byte le_kind = b.le_kind; + + // Handle scalar part, if any. + int x = 0; + entry *e = nullptr; + if (b.defc != nullptr) + { + // It has data, so unparse an element. + if (b.ixTag != CONSTANT_None) + { + assert(le_kind == EK_REF); + if (b.ixTag == CONSTANT_Literal) + e = b.getRefUsing(cp.getKQIndex()); + else + e = b.getRefN(); + switch (b.le_len) + { + case 0: + break; + case 1: + putu1ref(e); + break; + case 2: + putref(e); + break; + case 4: + putu2(0); + putref(e); + break; + default: + assert(false); + } + } + else + { + assert(le_kind == EK_INT || le_kind == EK_REPL || le_kind == EK_UN); + x = b.getInt(); + + assert(!b.le_bci || prevBCI == (int)to_bci(prevBII)); + switch (b.le_bci) + { + case EK_BCI: // PH: transmit R(bci), store bci + x = to_bci(prevBII = x); + prevBCI = x; + break; + case EK_BCID: // POH: transmit D(R(bci)), store bci + x = to_bci(prevBII += x); + prevBCI = x; + break; + case EK_BCO: // OH: transmit D(R(bci)), store D(bci) + x = to_bci(prevBII += x) - prevBCI; + prevBCI += x; + break; + } + assert(!b.le_bci || prevBCI == (int)to_bci(prevBII)); + + switch (b.le_len) + { + case 0: + break; + case 1: + putu1(x); + break; + case 2: + putu2(x); + break; + case 4: + putu4(x); + break; + default: + assert(false); + } + } + } + + // Handle subparts, if any. + switch (le_kind) + { + case EK_REPL: + // x is the repeat count + while (x-- > 0) + { + putlayout(b.le_body); + } + break; + case EK_UN: + // x is the tag + putlayout(findMatchingCase(x, b.le_body)); + break; + case EK_CALL: + { + band &cble = *b.le_body[0]; + assert(cble.le_kind == EK_CBLE); + // FIXME: hit this one + // assert(cble.le_len == b.le_len); + putlayout(cble.le_body); + } + break; + + case EK_CBLE: + case EK_CASE: + assert(false); // should not reach here + } + } +} + +void unpacker::read_files() +{ + file_name.readData(file_count); + if ((archive_options & AO_HAVE_FILE_SIZE_HI) != 0) + file_size_hi.readData(file_count); + file_size_lo.readData(file_count); + if ((archive_options & AO_HAVE_FILE_MODTIME) != 0) + file_modtime.readData(file_count); + int allFiles = file_count + class_count; + if ((archive_options & AO_HAVE_FILE_OPTIONS) != 0) + { + file_options.readData(file_count); + // FO_IS_CLASS_STUB might be set, causing overlap between classes and files + for (int i = 0; i < file_count; i++) + { + if ((file_options.getInt() & FO_IS_CLASS_STUB) != 0) + { + allFiles -= 1; // this one counts as both class and file + } + } + file_options.rewind(); + } + assert((default_file_options & FO_IS_CLASS_STUB) == 0); + files_remaining = allFiles; +} + +void unpacker::get_code_header(int &max_stack, int &max_na_locals, int &handler_count, + int &cflags) +{ + int sc = code_headers.getByte(); + if (sc == 0) + { + max_stack = max_na_locals = handler_count = cflags = -1; + return; + } + // Short code header is the usual case: + int nh; + int mod; + if (sc < 1 + 12 * 12) + { + sc -= 1; + nh = 0; + mod = 12; + } + else if (sc < 1 + 12 * 12 + 8 * 8) + { + sc -= 1 + 12 * 12; + nh = 1; + mod = 8; + } + else + { + assert(sc < 1 + 12 * 12 + 8 * 8 + 7 * 7); + sc -= 1 + 12 * 12 + 8 * 8; + nh = 2; + mod = 7; + } + max_stack = sc % mod; + max_na_locals = sc / mod; // caller must add static, siglen + handler_count = nh; + if ((archive_options & AO_HAVE_ALL_CODE_FLAGS) != 0) + cflags = -1; + else + cflags = 0; // this one has no attributes +} + +// Cf. PackageReader.readCodeHeaders +void unpacker::read_code_headers() +{ + code_headers.readData(code_count); + int totalHandlerCount = 0; + int totalFlagsCount = 0; + for (int i = 0; i < code_count; i++) + { + int max_stack, max_locals, handler_count, cflags; + get_code_header(max_stack, max_locals, handler_count, cflags); + if (max_stack < 0) + code_max_stack.expectMoreLength(1); + if (max_locals < 0) + code_max_na_locals.expectMoreLength(1); + if (handler_count < 0) + code_handler_count.expectMoreLength(1); + else + totalHandlerCount += handler_count; + if (cflags < 0) + totalFlagsCount += 1; + } + code_headers.rewind(); // replay later during writing + + code_max_stack.readData(); + code_max_na_locals.readData(); + code_handler_count.readData(); + totalHandlerCount += code_handler_count.getIntTotal(); + + // Read handler specifications. + // Cf. PackageReader.readCodeHandlers. + code_handler_start_P.readData(totalHandlerCount); + code_handler_end_PO.readData(totalHandlerCount); + code_handler_catch_PO.readData(totalHandlerCount); + code_handler_class_RCN.readData(totalHandlerCount); + + read_attrs(ATTR_CONTEXT_CODE, totalFlagsCount); +} + +static inline bool is_in_range(uint32_t n, uint32_t min, uint32_t max) +{ + return n - min <= max - min; // unsigned arithmetic! +} +static inline bool is_field_op(int bc) +{ + return is_in_range(bc, bc_getstatic, bc_putfield); +} +static inline bool is_invoke_init_op(int bc) +{ + return is_in_range(bc, _invokeinit_op, _invokeinit_limit - 1); +} +static inline bool is_self_linker_op(int bc) +{ + return is_in_range(bc, _self_linker_op, _self_linker_limit - 1); +} +static bool is_branch_op(int bc) +{ + return is_in_range(bc, bc_ifeq, bc_jsr) || is_in_range(bc, bc_ifnull, bc_jsr_w); +} +static bool is_local_slot_op(int bc) +{ + return is_in_range(bc, bc_iload, bc_aload) || is_in_range(bc, bc_istore, bc_astore) || + bc == bc_iinc || bc == bc_ret; +} +band *unpacker::ref_band_for_op(int bc) +{ + switch (bc) + { + case bc_ildc: + case bc_ildc_w: + return &bc_intref; + case bc_fldc: + case bc_fldc_w: + return &bc_floatref; + case bc_lldc2_w: + return &bc_longref; + case bc_dldc2_w: + return &bc_doubleref; + case bc_aldc: + case bc_aldc_w: + return &bc_stringref; + case bc_cldc: + case bc_cldc_w: + return &bc_classref; + + case bc_getstatic: + case bc_putstatic: + case bc_getfield: + case bc_putfield: + return &bc_fieldref; + + case bc_invokevirtual: + case bc_invokespecial: + case bc_invokestatic: + return &bc_methodref; + case bc_invokeinterface: + return &bc_imethodref; + + case bc_new: + case bc_anewarray: + case bc_checkcast: + case bc_instanceof: + case bc_multianewarray: + return &bc_classref; + } + return nullptr; +} + +band *unpacker::ref_band_for_self_op(int bc, bool &isAloadVar, int &origBCVar) +{ + if (!is_self_linker_op(bc)) + return nullptr; + int idx = (bc - _self_linker_op); + bool isSuper = (idx >= _self_linker_super_flag); + if (isSuper) + idx -= _self_linker_super_flag; + bool isAload = (idx >= _self_linker_aload_flag); + if (isAload) + idx -= _self_linker_aload_flag; + int origBC = _first_linker_op + idx; + bool isField = is_field_op(origBC); + isAloadVar = isAload; + origBCVar = _first_linker_op + idx; + if (!isSuper) + return isField ? &bc_thisfield : &bc_thismethod; + else + return isField ? &bc_superfield : &bc_supermethod; +} + +// Cf. PackageReader.readByteCodes +inline // called exactly once => inline + void +unpacker::read_bcs() +{ + // read from bc_codes and bc_case_count + fillbytes all_switch_ops; + all_switch_ops.init(); + + // Read directly from rp/rplimit. + // Do this later: bc_codes.readData(...) + byte *rp0 = rp; + + band *bc_which; + byte *opptr = rp; + byte *oplimit = rplimit; + + bool isAload; // passed by ref and then ignored + int junkBC; // passed by ref and then ignored + for (int k = 0; k < code_count; k++) + { + // Scan one method: + for (;;) + { + if (opptr + 2 > oplimit) + { + rp = opptr; + ensure_input(2); + oplimit = rplimit; + rp = rp0; // back up + } + if (opptr == oplimit) + { + unpack_abort(); + } + int bc = *opptr++ & 0xFF; + bool isWide = false; + if (bc == bc_wide) + { + if (opptr == oplimit) + { + unpack_abort(); + } + bc = *opptr++ & 0xFF; + isWide = true; + } + // Adjust expectations of various band sizes. + switch (bc) + { + case bc_tableswitch: + case bc_lookupswitch: + all_switch_ops.addByte(bc); + break; + case bc_iinc: + bc_local.expectMoreLength(1); + bc_which = isWide ? &bc_short : &bc_byte; + bc_which->expectMoreLength(1); + break; + case bc_sipush: + bc_short.expectMoreLength(1); + break; + case bc_bipush: + bc_byte.expectMoreLength(1); + break; + case bc_newarray: + bc_byte.expectMoreLength(1); + break; + case bc_multianewarray: + assert(ref_band_for_op(bc) == &bc_classref); + bc_classref.expectMoreLength(1); + bc_byte.expectMoreLength(1); + break; + case bc_ref_escape: + bc_escrefsize.expectMoreLength(1); + bc_escref.expectMoreLength(1); + break; + case bc_byte_escape: + bc_escsize.expectMoreLength(1); + // bc_escbyte will have to be counted too + break; + default: + if (is_invoke_init_op(bc)) + { + bc_initref.expectMoreLength(1); + break; + } + bc_which = ref_band_for_self_op(bc, isAload, junkBC); + if (bc_which != nullptr) + { + bc_which->expectMoreLength(1); + break; + } + if (is_branch_op(bc)) + { + bc_label.expectMoreLength(1); + break; + } + bc_which = ref_band_for_op(bc); + if (bc_which != nullptr) + { + bc_which->expectMoreLength(1); + assert(bc != bc_multianewarray); // handled elsewhere + break; + } + if (is_local_slot_op(bc)) + { + bc_local.expectMoreLength(1); + break; + } + break; + case bc_end_marker: + // Increment k and test against code_count. + goto doneScanningMethod; + } + } + doneScanningMethod: + { + } + } + + // Go through the formality, so we can use it in a regular fashion later: + assert(rp == rp0); + bc_codes.readData((int)(opptr - rp)); + + int i = 0; + + // To size instruction bands correctly, we need info on switches: + bc_case_count.readData((int)all_switch_ops.size()); + for (i = 0; i < (int)all_switch_ops.size(); i++) + { + int caseCount = bc_case_count.getInt(); + int bc = all_switch_ops.getByte(i); + bc_label.expectMoreLength(1 + caseCount); // default label + cases + bc_case_value.expectMoreLength(bc == bc_tableswitch ? 1 : caseCount); + } + bc_case_count.rewind(); // uses again for output + + all_switch_ops.free(); + + for (i = e_bc_case_value; i <= e_bc_escsize; i++) + { + all_bands[i].readData(); + } + + // The bc_escbyte band is counted by the immediately previous band. + bc_escbyte.readData(bc_escsize.getIntTotal()); +} + +void unpacker::read_bands() +{ + read_file_header(); + + if (cp.nentries == 0) + { + // read_file_header failed to read a CP, because it copied a JAR. + return; + } + + // Do this after the file header has been read: + check_options(); + + read_cp(); + read_attr_defs(); + read_ics(); + read_classes(); + read_bcs(); + read_files(); +} + +/// CP routines + +entry *&constant_pool::hashTabRef(byte tag, bytes &b) +{ + uint32_t hash = tag + (int)b.len; + for (int i = 0; i < (int)b.len; i++) + { + hash = hash * 31 + (0xFF & b.ptr[i]); + } + entry **ht = hashTab; + int hlen = hashTabLength; + assert((hlen & (hlen - 1)) == 0); // must be power of 2 + uint32_t hash1 = hash & (hlen - 1); // == hash % hlen + uint32_t hash2 = 0; // lazily computed (requires mod op.) + int probes = 0; + while (ht[hash1] != nullptr) + { + entry &e = *ht[hash1]; + if (e.value.b.equals(b) && e.tag == tag) + break; + if (hash2 == 0) + // Note: hash2 must be relatively prime to hlen, hence the "|1". + hash2 = (((hash % 499) & (hlen - 1)) | 1); + hash1 += hash2; + if (hash1 >= (uint32_t)hlen) + hash1 -= hlen; + assert(hash1 < (uint32_t)hlen); + assert(++probes < hlen); + } + return ht[hash1]; +} + +static void insert_extra(entry *e, ptrlist &extras) +{ + // This ordering helps implement the Pack200 requirement + // of a predictable CP order in the class files produced. + e->inord = NO_INORD; // mark as an "extra" + extras.add(e); + // Note: We will sort the list (by string-name) later. +} + +entry *constant_pool::ensureUtf8(bytes &b) +{ + entry *&ix = hashTabRef(CONSTANT_Utf8, b); + if (ix != nullptr) + return ix; + // Make one. + if (nentries == maxentries) + { + unpack_abort("cp utf8 overflow"); + return &entries[tag_base[CONSTANT_Utf8]]; // return something + } + entry &e = entries[nentries++]; + e.tag = CONSTANT_Utf8; + u->saveTo(e.value.b, b); + assert(&e >= first_extra_entry); + insert_extra(&e, tag_extras[CONSTANT_Utf8]); + return ix = &e; +} + +entry *constant_pool::ensureClass(bytes &b) +{ + entry *&ix = hashTabRef(CONSTANT_Class, b); + if (ix != nullptr) + return ix; + // Make one. + if (nentries == maxentries) + { + unpack_abort("cp class overflow"); + return &entries[tag_base[CONSTANT_Class]]; // return something + } + entry &e = entries[nentries++]; + e.tag = CONSTANT_Class; + e.nrefs = 1; + e.refs = U_NEW(entry *, 1); + ix = &e; // hold my spot in the index + entry *utf = ensureUtf8(b); + e.refs[0] = utf; + e.value.b = utf->value.b; + assert(&e >= first_extra_entry); + insert_extra(&e, tag_extras[CONSTANT_Class]); + return &e; +} + +void constant_pool::expandSignatures() +{ + int i; + int nsigs = 0; + int nreused = 0; + int first_sig = tag_base[CONSTANT_Signature]; + int sig_limit = tag_count[CONSTANT_Signature] + first_sig; + fillbytes buf; + buf.init(1 << 10); + for (i = first_sig; i < sig_limit; i++) + { + entry &e = entries[i]; + assert(e.tag == CONSTANT_Signature); + int refnum = 0; + bytes form = e.refs[refnum++]->asUtf8(); + buf.empty(); + for (int j = 0; j < (int)form.len; j++) + { + int c = form.ptr[j]; + buf.addByte(c); + if (c == 'L') + { + entry *cls = e.refs[refnum++]; + buf.append(cls->className()->asUtf8()); + } + } + assert(refnum == e.nrefs); + bytes &sig = buf.b; + + // try to find a pre-existing Utf8: + entry *&e2 = hashTabRef(CONSTANT_Utf8, sig); + if (e2 != nullptr) + { + assert(e2->isUtf8(sig)); + e.value.b = e2->value.b; + e.refs[0] = e2; + e.nrefs = 1; + nreused++; + } + else + { + // there is no other replacement; reuse this CP entry as a Utf8 + u->saveTo(e.value.b, sig); + e.tag = CONSTANT_Utf8; + e.nrefs = 0; + e2 = &e; + } + nsigs++; + } + buf.free(); + + // go expunge all references to remaining signatures: + for (i = 0; i < (int)nentries; i++) + { + entry &e = entries[i]; + for (int j = 0; j < e.nrefs; j++) + { + entry *&e2 = e.refs[j]; + if (e2 != nullptr && e2->tag == CONSTANT_Signature) + e2 = e2->refs[0]; + } + } +} + +void constant_pool::initMemberIndexes() +{ + // This function does NOT refer to any class schema. + // It is totally internal to the cpool. + int i, j; + + // Get the pre-existing indexes: + int nclasses = tag_count[CONSTANT_Class]; + // entry *classes = tag_base[CONSTANT_Class] + entries; // UNUSED + int nfields = tag_count[CONSTANT_Fieldref]; + entry *fields = tag_base[CONSTANT_Fieldref] + entries; + int nmethods = tag_count[CONSTANT_Methodref]; + entry *methods = tag_base[CONSTANT_Methodref] + entries; + + int *field_counts = T_NEW(int, nclasses); + int *method_counts = T_NEW(int, nclasses); + cpindex *all_indexes = U_NEW(cpindex, nclasses * 2); + entry **field_ix = U_NEW(entry *, add_size(nfields, nclasses)); + entry **method_ix = U_NEW(entry *, add_size(nmethods, nclasses)); + + for (j = 0; j < nfields; j++) + { + entry &f = fields[j]; + i = f.memberClass()->inord; + assert(i < nclasses); + field_counts[i]++; + } + for (j = 0; j < nmethods; j++) + { + entry &m = methods[j]; + i = m.memberClass()->inord; + assert(i < nclasses); + method_counts[i]++; + } + + int fbase = 0, mbase = 0; + for (i = 0; i < nclasses; i++) + { + int fc = field_counts[i]; + int mc = method_counts[i]; + all_indexes[i * 2 + 0].init(fc, field_ix + fbase, CONSTANT_Fieldref + SUBINDEX_BIT); + all_indexes[i * 2 + 1].init(mc, method_ix + mbase, CONSTANT_Methodref + SUBINDEX_BIT); + // reuse field_counts and member_counts as fill pointers: + field_counts[i] = fbase; + method_counts[i] = mbase; + fbase += fc + 1; + mbase += mc + 1; + // (the +1 leaves a space between every subarray) + } + assert(fbase == nfields + nclasses); + assert(mbase == nmethods + nclasses); + + for (j = 0; j < nfields; j++) + { + entry &f = fields[j]; + i = f.memberClass()->inord; + field_ix[field_counts[i]++] = &f; + } + for (j = 0; j < nmethods; j++) + { + entry &m = methods[j]; + i = m.memberClass()->inord; + method_ix[method_counts[i]++] = &m; + } + + member_indexes = all_indexes; + + // Free intermediate buffers. + u->free_temps(); +} + +void entry::requestOutputIndex(constant_pool &cp, int req) +{ + assert(outputIndex <= NOT_REQUESTED); // must not have assigned indexes yet + if (tag == CONSTANT_Signature) + { + ref(0)->requestOutputIndex(cp, req); + return; + } + assert(req == REQUESTED || req == REQUESTED_LDC); + if (outputIndex != NOT_REQUESTED) + { + if (req == REQUESTED_LDC) + outputIndex = req; // this kind has precedence + return; + } + outputIndex = req; + // assert(!cp.outputEntries.contains(this)); + assert(tag != CONSTANT_Signature); + cp.outputEntries.add(this); + for (int j = 0; j < nrefs; j++) + { + ref(j)->requestOutputIndex(cp); + } +} + +void constant_pool::resetOutputIndexes() +{ + int i; + int noes = outputEntries.length(); + entry **oes = (entry **)outputEntries.base(); + for (i = 0; i < noes; i++) + { + entry &e = *oes[i]; + e.outputIndex = NOT_REQUESTED; + } + outputIndexLimit = 0; + outputEntries.empty(); +} + +static const byte TAG_ORDER[CONSTANT_Limit] = {0, 1, 0, 2, 3, 4, 5, 7, 6, 10, 11, 12, 9, 8}; + +extern "C" int outputEntry_cmp(const void *e1p, const void *e2p) +{ + // Sort entries according to the Pack200 rules for deterministic + // constant pool ordering. + // + // The four sort keys as follows, in order of decreasing importance: + // 1. ldc first, then non-ldc guys + // 2. normal cp_All entries by input order (i.e., address order) + // 3. after that, extra entries by lexical order (as in tag_extras[*]) + entry &e1 = *(entry *)*(void **)e1p; + entry &e2 = *(entry *)*(void **)e2p; + int oi1 = e1.outputIndex; + int oi2 = e2.outputIndex; + assert(oi1 == REQUESTED || oi1 == REQUESTED_LDC); + assert(oi2 == REQUESTED || oi2 == REQUESTED_LDC); + if (oi1 != oi2) + { + if (oi1 == REQUESTED_LDC) + return 0 - 1; + if (oi2 == REQUESTED_LDC) + return 1 - 0; + // Else fall through; neither is an ldc request. + } + if (e1.inord != NO_INORD || e2.inord != NO_INORD) + { + // One or both is normal. Use input order. + if (&e1 > &e2) + return 1 - 0; + if (&e1 < &e2) + return 0 - 1; + return 0; // equal pointers + } + // Both are extras. Sort by tag and then by value. + if (e1.tag != e2.tag) + { + return TAG_ORDER[e1.tag] - TAG_ORDER[e2.tag]; + } + // If the tags are the same, use string comparison. + return compare_Utf8_chars(e1.value.b, e2.value.b); +} + +void constant_pool::computeOutputIndexes() +{ + int i; + + int noes = outputEntries.length(); + entry **oes = (entry **)outputEntries.base(); + + // Sort the output constant pool into the order required by Pack200. + PTRLIST_QSORT(outputEntries, outputEntry_cmp); + + // Allocate a new index for each entry that needs one. + // We do this in two passes, one for LDC entries and one for the rest. + int nextIndex = 1; // always skip index #0 in output cpool + for (i = 0; i < noes; i++) + { + entry &e = *oes[i]; + assert(e.outputIndex == REQUESTED || e.outputIndex == REQUESTED_LDC); + e.outputIndex = nextIndex++; + if (e.isDoubleWord()) + nextIndex++; // do not use the next index + } + outputIndexLimit = nextIndex; +} + +// Unpacker Start +// Deallocate all internal storage and reset to a clean state. +// Do not disturb any input or output connections, including +// infileptr, inbytes, read_input_fn, jarout, or errstrm. +// Do not reset any unpack options. +void unpacker::reset() +{ + bytes_read_before_reset += bytes_read; + bytes_written_before_reset += bytes_written; + files_written_before_reset += files_written; + classes_written_before_reset += classes_written; + segments_read_before_reset += 1; + if (verbose >= 2) + { + fprintf(stderr, "After segment %d, " LONG_LONG_FORMAT + " bytes read and " LONG_LONG_FORMAT " bytes written.\n", + segments_read_before_reset - 1, bytes_read_before_reset, + bytes_written_before_reset); + fprintf(stderr, + "After segment %d, %d files (of which %d are classes) written to output.\n", + segments_read_before_reset - 1, files_written_before_reset, + classes_written_before_reset); + if (archive_next_count != 0) + { + fprintf(stderr, "After segment %d, %d segment%s remaining (estimated).\n", + segments_read_before_reset - 1, archive_next_count, + archive_next_count == 1 ? "" : "s"); + } + } + + unpacker save_u = (*this); // save bytewise image + infileptr = nullptr; // make asserts happy + jarout = nullptr; // do not close the output jar + gzin = nullptr; // do not close the input gzip stream + this->free(); + this->init(read_input_fn); + + // restore selected interface state: + infileptr = save_u.infileptr; + inbytes = save_u.inbytes; + jarout = save_u.jarout; + gzin = save_u.gzin; + verbose = save_u.verbose; + deflate_hint_or_zero = save_u.deflate_hint_or_zero; + modification_time_or_zero = save_u.modification_time_or_zero; + bytes_read_before_reset = save_u.bytes_read_before_reset; + bytes_written_before_reset = save_u.bytes_written_before_reset; + files_written_before_reset = save_u.files_written_before_reset; + classes_written_before_reset = save_u.classes_written_before_reset; + segments_read_before_reset = save_u.segments_read_before_reset; + // Note: If we use strip_names, watch out: They get nuked here. +} + +void unpacker::init(read_input_fn_t input_fn) +{ + int i; + BYTES_OF(*this).clear(); + this->u = this; // self-reference for U_NEW macro + read_input_fn = input_fn; + all_bands = band::makeBands(this); + // Make a default jar buffer; caller may safely overwrite it. + jarout = U_NEW(jar, 1); + jarout->init(this); + for (i = 0; i < ATTR_CONTEXT_LIMIT; i++) + attr_defs[i].u = u; // set up outer ptr +} + +// Usage: unpack a byte buffer +// packptr is a reference to byte buffer containing a +// packed file and len is the length of the buffer. +// If nullptr, the callback is used to fill an internal buffer. +void unpacker::start(void *packptr, size_t len) +{ + if (packptr != nullptr && len != 0) + { + inbytes.set((byte *)packptr, len); + } + read_bands(); +} + +void unpacker::check_options() +{ + if (deflate_hint_or_zero != 0) + { + bool force_deflate_hint = (deflate_hint_or_zero > 0); + if (force_deflate_hint) + default_file_options |= FO_DEFLATE_HINT; + else + default_file_options &= ~FO_DEFLATE_HINT; + // Turn off per-file deflate hint by force. + suppress_file_options |= FO_DEFLATE_HINT; + } + if (modification_time_or_zero != 0) + { + default_file_modtime = modification_time_or_zero; + // Turn off per-file modtime by force. + archive_options &= ~AO_HAVE_FILE_MODTIME; + } +} + +// classfile writing + +void unpacker::reset_cur_classfile() +{ + // set defaults + cur_class_minver = default_class_minver; + cur_class_majver = default_class_majver; + + // reset constant pool state + cp.resetOutputIndexes(); + + // reset fixups + class_fixup_type.empty(); + class_fixup_offset.empty(); + class_fixup_ref.empty(); + requested_ics.empty(); +} + +cpindex *constant_pool::getKQIndex() +{ + char ch = '?'; + if (u->cur_descr != nullptr) + { + entry *type = u->cur_descr->descrType(); + ch = type->value.b.ptr[0]; + } + byte tag = CONSTANT_Integer; + switch (ch) + { + case 'L': + tag = CONSTANT_String; + break; + case 'I': + tag = CONSTANT_Integer; + break; + case 'J': + tag = CONSTANT_Long; + break; + case 'F': + tag = CONSTANT_Float; + break; + case 'D': + tag = CONSTANT_Double; + break; + case 'B': + case 'S': + case 'C': + case 'Z': + tag = CONSTANT_Integer; + break; + default: + unpack_abort("bad KQ reference"); + break; + } + return getIndex(tag); +} + +uint32_t unpacker::to_bci(uint32_t bii) +{ + uint32_t len = bcimap.length(); + uint32_t *map = (uint32_t *)bcimap.base(); + assert(len > 0); // must be initialized before using to_bci + if (bii < len) + return map[bii]; + // Else it's a fractional or out-of-range BCI. + uint32_t key = bii - len; + for (int i = len;; i--) + { + if (map[i - 1] - (i - 1) <= key) + break; + else + --bii; + } + return bii; +} + +void unpacker::put_stackmap_type() +{ + int tag = code_StackMapTable_T.getByte(); + putu1(tag); + switch (tag) + { + case 7: // (7) [RCH] + putref(code_StackMapTable_RC.getRef()); + break; + case 8: // (8) [PH] + putu2(to_bci(code_StackMapTable_P.getInt())); + break; + } +} + +// Functions for writing code. + +void unpacker::put_label(int curIP, int size) +{ + code_fixup_type.addByte(size); + code_fixup_offset.add((int)put_empty(size)); + code_fixup_source.add(curIP); +} + +inline // called exactly once => inline + void +unpacker::write_bc_ops() +{ + bcimap.empty(); + code_fixup_type.empty(); + code_fixup_offset.empty(); + code_fixup_source.empty(); + + band *bc_which; + + byte *opptr = bc_codes.curRP(); + // No need for oplimit, since the codes are pre-counted. + + size_t codeBase = wpoffset(); + + bool isAload; // copy-out result + int origBC; + + entry *thisClass = cur_class; + entry *superClass = cur_super; + entry *newClass = nullptr; // class of last _new opcode + + // overwrite any prior index on these bands; it changes w/ current class: + bc_thisfield.setIndex(cp.getFieldIndex(thisClass)); + bc_thismethod.setIndex(cp.getMethodIndex(thisClass)); + if (superClass != nullptr) + { + bc_superfield.setIndex(cp.getFieldIndex(superClass)); + bc_supermethod.setIndex(cp.getMethodIndex(superClass)); + } + + for (int curIP = 0;; curIP++) + { + int curPC = (int)(wpoffset() - codeBase); + bcimap.add(curPC); + ensure_put_space(10); // covers most instrs w/o further bounds check + int bc = *opptr++ & 0xFF; + + putu1_fast(bc); + // Note: See '--wp' below for pseudo-bytecodes like bc_end_marker. + + bool isWide = false; + if (bc == bc_wide) + { + bc = *opptr++ & 0xFF; + putu1_fast(bc); + isWide = true; + } + switch (bc) + { + case bc_end_marker: + --wp; // not really part of the code + assert(opptr <= bc_codes.maxRP()); + bc_codes.curRP() = opptr; // advance over this in bc_codes + goto doneScanningMethod; + case bc_tableswitch: // apc: (df, lo, hi, (hi-lo+1)*(label)) + case bc_lookupswitch: // apc: (df, nc, nc*(case, label)) + { + int caseCount = bc_case_count.getInt(); + while (((wpoffset() - codeBase) % 4) != 0) + putu1_fast(0); + ensure_put_space(30 + caseCount * 8); + put_label(curIP, 4); // int df = bc_label.getInt(); + if (bc == bc_tableswitch) + { + int lo = bc_case_value.getInt(); + int hi = lo + caseCount - 1; + putu4(lo); + putu4(hi); + for (int j = 0; j < caseCount; j++) + { + put_label(curIP, 4); // int lVal = bc_label.getInt(); + // int cVal = lo + j; + } + } + else + { + putu4(caseCount); + for (int j = 0; j < caseCount; j++) + { + int cVal = bc_case_value.getInt(); + putu4(cVal); + put_label(curIP, 4); // int lVal = bc_label.getInt(); + } + } + assert((int)to_bci(curIP) == curPC); + continue; + } + case bc_iinc: + { + int local = bc_local.getInt(); + int delta = (isWide ? bc_short : bc_byte).getInt(); + if (isWide) + { + putu2(local); + putu2(delta); + } + else + { + putu1_fast(local); + putu1_fast(delta); + } + continue; + } + case bc_sipush: + { + int val = bc_short.getInt(); + putu2(val); + continue; + } + case bc_bipush: + case bc_newarray: + { + int val = bc_byte.getByte(); + putu1_fast(val); + continue; + } + case bc_ref_escape: + { + // Note that insnMap has one entry for this. + --wp; // not really part of the code + int size = bc_escrefsize.getInt(); + entry *ref = bc_escref.getRefN(); + switch (size) + { + case 1: + putu1ref(ref); + break; + case 2: + putref(ref); + break; + default: + assert(false); + } + continue; + } + case bc_byte_escape: + { + // Note that insnMap has one entry for all these bytes. + --wp; // not really part of the code + int size = bc_escsize.getInt(); + ensure_put_space(size); + for (int j = 0; j < size; j++) + putu1_fast(bc_escbyte.getByte()); + continue; + } + default: + if (is_invoke_init_op(bc)) + { + origBC = bc_invokespecial; + entry *classRef; + switch (bc - _invokeinit_op) + { + case _invokeinit_self_option: + classRef = thisClass; + break; + case _invokeinit_super_option: + classRef = superClass; + break; + default: + assert(bc == _invokeinit_op + _invokeinit_new_option); + case _invokeinit_new_option: + classRef = newClass; + break; + } + wp[-1] = origBC; // overwrite with origBC + int coding = bc_initref.getInt(); + // Find the nth overloading of <init> in classRef. + entry *ref = nullptr; + cpindex *ix = (classRef == nullptr) ? nullptr : cp.getMethodIndex(classRef); + for (int j = 0, which_init = 0;; j++) + { + ref = (ix == nullptr) ? nullptr : ix->get(j); + if (ref == nullptr) + break; // oops, bad input + assert(ref->tag == CONSTANT_Methodref); + if (ref->memberDescr()->descrName() == cp.sym[constant_pool::s_lt_init_gt]) + { + if (which_init++ == coding) + break; + } + } + putref(ref); + continue; + } + bc_which = ref_band_for_self_op(bc, isAload, origBC); + if (bc_which != nullptr) + { + if (!isAload) + { + wp[-1] = origBC; // overwrite with origBC + } + else + { + wp[-1] = bc_aload_0; // overwrite with _aload_0 + // Note: insnMap keeps the _aload_0 separate. + bcimap.add(++curPC); + ++curIP; + putu1_fast(origBC); + } + entry *ref = bc_which->getRef(); + putref(ref); + continue; + } + if (is_branch_op(bc)) + { + // int lVal = bc_label.getInt(); + if (bc < bc_goto_w) + { + put_label(curIP, 2); // putu2(lVal & 0xFFFF); + } + else + { + assert(bc <= bc_jsr_w); + put_label(curIP, 4); // putu4(lVal); + } + assert((int)to_bci(curIP) == curPC); + continue; + } + bc_which = ref_band_for_op(bc); + if (bc_which != nullptr) + { + entry *ref = bc_which->getRefCommon(bc_which->ix, bc_which->nullOK); + if (ref == nullptr && bc_which == &bc_classref) + { + // Shorthand for class self-references. + ref = thisClass; + } + origBC = bc; + switch (bc) + { + case bc_ildc: + case bc_cldc: + case bc_fldc: + case bc_aldc: + origBC = bc_ldc; + break; + case bc_ildc_w: + case bc_cldc_w: + case bc_fldc_w: + case bc_aldc_w: + origBC = bc_ldc_w; + break; + case bc_lldc2_w: + case bc_dldc2_w: + origBC = bc_ldc2_w; + break; + case bc_new: + newClass = ref; + break; + } + wp[-1] = origBC; // overwrite with origBC + if (origBC == bc_ldc) + { + putu1ref(ref); + } + else + { + putref(ref); + } + if (origBC == bc_multianewarray) + { + // Copy the trailing byte also. + int val = bc_byte.getByte(); + putu1_fast(val); + } + else if (origBC == bc_invokeinterface) + { + int argSize = ref->memberDescr()->descrType()->typeSize(); + putu1_fast(1 + argSize); + putu1_fast(0); + } + continue; + } + if (is_local_slot_op(bc)) + { + int local = bc_local.getInt(); + if (isWide) + { + putu2(local); + if (bc == bc_iinc) + { + int iVal = bc_short.getInt(); + putu2(iVal); + } + } + else + { + putu1_fast(local); + if (bc == bc_iinc) + { + int iVal = bc_byte.getByte(); + putu1_fast(iVal); + } + } + continue; + } + // Random bytecode. Just copy it. + assert(bc < bc_bytecode_limit); + } + } +doneScanningMethod: +{ +} + // bcimap.add(curPC); // PC limit is already also in map, from bc_end_marker + + // Armed with a bcimap, we can now fix up all the labels. + for (int i = 0; i < (int)code_fixup_type.size(); i++) + { + int type = code_fixup_type.getByte(i); + byte *bp = wp_at(code_fixup_offset.get(i)); + int curIP = code_fixup_source.get(i); + int destIP = curIP + bc_label.getInt(); + int span = to_bci(destIP) - to_bci(curIP); + switch (type) + { + case 2: + putu2_at(bp, (ushort)span); + break; + case 4: + putu4_at(bp, span); + break; + default: + assert(false); + } + } +} + +inline // called exactly once => inline + void +unpacker::write_code() +{ + int j; + + int max_stack, max_locals, handler_count, cflags; + get_code_header(max_stack, max_locals, handler_count, cflags); + + if (max_stack < 0) + max_stack = code_max_stack.getInt(); + if (max_locals < 0) + max_locals = code_max_na_locals.getInt(); + if (handler_count < 0) + handler_count = code_handler_count.getInt(); + + int siglen = cur_descr->descrType()->typeSize(); + if ((cur_descr_flags & ACC_STATIC) == 0) + siglen++; + max_locals += siglen; + + putu2(max_stack); + putu2(max_locals); + size_t bcbase = put_empty(4); + + // Write the bytecodes themselves. + write_bc_ops(); + + byte *bcbasewp = wp_at(bcbase); + putu4_at(bcbasewp, (int)(wp - (bcbasewp + 4))); // size of code attr + + putu2(handler_count); + for (j = 0; j < handler_count; j++) + { + int bii = code_handler_start_P.getInt(); + putu2(to_bci(bii)); + bii += code_handler_end_PO.getInt(); + putu2(to_bci(bii)); + bii += code_handler_catch_PO.getInt(); + putu2(to_bci(bii)); + putref(code_handler_class_RCN.getRefN()); + } + + uint64_t indexBits = cflags; + if (cflags < 0) + { + bool haveLongFlags = attr_defs[ATTR_CONTEXT_CODE].haveLongFlags(); + indexBits = code_flags_hi.getLong(code_flags_lo, haveLongFlags); + } + write_attrs(ATTR_CONTEXT_CODE, indexBits); +} + +int unpacker::write_attrs(int attrc, uint64_t indexBits) +{ + if (indexBits == 0) + { + // Quick short-circuit. + putu2(0); + return 0; + } + + attr_definitions &ad = attr_defs[attrc]; + + int i, j, j2, idx, count; + + int oiCount = 0; + if (ad.isPredefined(X_ATTR_OVERFLOW) && (indexBits & ((uint64_t)1 << X_ATTR_OVERFLOW)) != 0) + { + indexBits -= ((uint64_t)1 << X_ATTR_OVERFLOW); + oiCount = ad.xxx_attr_count().getInt(); + } + + int bitIndexes[X_ATTR_LIMIT_FLAGS_HI]; + int biCount = 0; + + // Fill bitIndexes with index bits, in order. + for (idx = 0; indexBits != 0; idx++, indexBits >>= 1) + { + if ((indexBits & 1) != 0) + bitIndexes[biCount++] = idx; + } + assert(biCount <= (int)lengthof(bitIndexes)); + + // Write a provisional attribute count, perhaps to be corrected later. + int naOffset = (int)wpoffset(); + int na0 = biCount + oiCount; + putu2(na0); + + int na = 0; + for (i = 0; i < na0; i++) + { + if (i < biCount) + idx = bitIndexes[i]; + else + idx = ad.xxx_attr_indexes().getInt(); + assert(ad.isIndex(idx)); + entry *aname = nullptr; + entry *ref; // scratch + size_t abase = put_empty(2 + 4); + if (idx < (int)ad.flag_limit && ad.isPredefined(idx)) + { + // Switch on the attrc and idx simultaneously. + switch (ADH_BYTE(attrc, idx)) + { + + case ADH_BYTE(ATTR_CONTEXT_CLASS, X_ATTR_OVERFLOW) : + case ADH_BYTE(ATTR_CONTEXT_FIELD, X_ATTR_OVERFLOW) : + case ADH_BYTE(ATTR_CONTEXT_METHOD, X_ATTR_OVERFLOW) : + case ADH_BYTE(ATTR_CONTEXT_CODE, X_ATTR_OVERFLOW) : + // no attribute at all, so back up on this one + wp = wp_at(abase); + continue; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_ClassFile_version) : + cur_class_minver = class_ClassFile_version_minor_H.getInt(); + cur_class_majver = class_ClassFile_version_major_H.getInt(); + // back up; not a real attribute + wp = wp_at(abase); + continue; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_InnerClasses) : + // note the existence of this attr, but save for later + if (cur_class_has_local_ics) + unpack_abort("too many InnerClasses attrs"); + cur_class_has_local_ics = true; + wp = wp_at(abase); + continue; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_SourceFile) : + aname = cp.sym[constant_pool::s_SourceFile]; + ref = class_SourceFile_RUN.getRefN(); + if (ref == nullptr) + { + bytes &n = cur_class->ref(0)->value.b; + // parse n = (<pkg>/)*<outer>?($<id>)* + int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, (int)n.len) + 1; + bytes prefix = n.slice(pkglen, n.len); + for (;;) + { + // Work backwards, finding all '$', '#', etc. + int dollar = + lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, prefix, (int)prefix.len); + if (dollar < 0) + break; + prefix = prefix.slice(0, dollar); + } + const char *suffix = ".java"; + int len = (int)(prefix.len + strlen(suffix)); + bytes name; + name.set(T_NEW(byte, add_size(len, 1)), len); + name.strcat(prefix).strcat(suffix); + ref = cp.ensureUtf8(name); + } + putref(ref); + break; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_EnclosingMethod) : + aname = cp.sym[constant_pool::s_EnclosingMethod]; + putref(class_EnclosingMethod_RC.getRefN()); + putref(class_EnclosingMethod_RDN.getRefN()); + break; + + case ADH_BYTE(ATTR_CONTEXT_FIELD, FIELD_ATTR_ConstantValue) : + aname = cp.sym[constant_pool::s_ConstantValue]; + putref(field_ConstantValue_KQ.getRefUsing(cp.getKQIndex())); + break; + + case ADH_BYTE(ATTR_CONTEXT_METHOD, METHOD_ATTR_Code) : + aname = cp.sym[constant_pool::s_Code]; + write_code(); + break; + + case ADH_BYTE(ATTR_CONTEXT_METHOD, METHOD_ATTR_Exceptions) : + aname = cp.sym[constant_pool::s_Exceptions]; + putu2(count = method_Exceptions_N.getInt()); + for (j = 0; j < count; j++) + { + putref(method_Exceptions_RC.getRefN()); + } + break; + + case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_StackMapTable) : + aname = cp.sym[constant_pool::s_StackMapTable]; + // (keep this code aligned with its brother in unpacker::read_attrs) + putu2(count = code_StackMapTable_N.getInt()); + for (j = 0; j < count; j++) + { + int tag = code_StackMapTable_frame_T.getByte(); + putu1(tag); + if (tag <= 127) + { + // (64-127) [(2)] + if (tag >= 64) + put_stackmap_type(); + } + else if (tag <= 251) + { + // (247) [(1)(2)] + // (248-251) [(1)] + if (tag >= 247) + putu2(code_StackMapTable_offset.getInt()); + if (tag == 247) + put_stackmap_type(); + } + else if (tag <= 254) + { + // (252) [(1)(2)] + // (253) [(1)(2)(2)] + // (254) [(1)(2)(2)(2)] + putu2(code_StackMapTable_offset.getInt()); + for (int k = (tag - 251); k > 0; k--) + { + put_stackmap_type(); + } + } + else + { + // (255) [(1)NH[(2)]NH[(2)]] + putu2(code_StackMapTable_offset.getInt()); + putu2(j2 = code_StackMapTable_local_N.getInt()); + while (j2-- > 0) + put_stackmap_type(); + putu2(j2 = code_StackMapTable_stack_N.getInt()); + while (j2-- > 0) + put_stackmap_type(); + } + } + break; + + case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_LineNumberTable) : + aname = cp.sym[constant_pool::s_LineNumberTable]; + putu2(count = code_LineNumberTable_N.getInt()); + for (j = 0; j < count; j++) + { + putu2(to_bci(code_LineNumberTable_bci_P.getInt())); + putu2(code_LineNumberTable_line.getInt()); + } + break; + + case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_LocalVariableTable) : + aname = cp.sym[constant_pool::s_LocalVariableTable]; + putu2(count = code_LocalVariableTable_N.getInt()); + for (j = 0; j < count; j++) + { + int bii = code_LocalVariableTable_bci_P.getInt(); + int bci = to_bci(bii); + putu2(bci); + bii += code_LocalVariableTable_span_O.getInt(); + putu2(to_bci(bii) - bci); + putref(code_LocalVariableTable_name_RU.getRefN()); + putref(code_LocalVariableTable_type_RS.getRefN()); + putu2(code_LocalVariableTable_slot.getInt()); + } + break; + + case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_LocalVariableTypeTable) : + aname = cp.sym[constant_pool::s_LocalVariableTypeTable]; + putu2(count = code_LocalVariableTypeTable_N.getInt()); + for (j = 0; j < count; j++) + { + int bii = code_LocalVariableTypeTable_bci_P.getInt(); + int bci = to_bci(bii); + putu2(bci); + bii += code_LocalVariableTypeTable_span_O.getInt(); + putu2(to_bci(bii) - bci); + putref(code_LocalVariableTypeTable_name_RU.getRefN()); + putref(code_LocalVariableTypeTable_type_RS.getRefN()); + putu2(code_LocalVariableTypeTable_slot.getInt()); + } + break; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, X_ATTR_Signature) : + aname = cp.sym[constant_pool::s_Signature]; + putref(class_Signature_RS.getRefN()); + break; + + case ADH_BYTE(ATTR_CONTEXT_FIELD, X_ATTR_Signature) : + aname = cp.sym[constant_pool::s_Signature]; + putref(field_Signature_RS.getRefN()); + break; + + case ADH_BYTE(ATTR_CONTEXT_METHOD, X_ATTR_Signature) : + aname = cp.sym[constant_pool::s_Signature]; + putref(method_Signature_RS.getRefN()); + break; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, X_ATTR_Deprecated) : + case ADH_BYTE(ATTR_CONTEXT_FIELD, X_ATTR_Deprecated) : + case ADH_BYTE(ATTR_CONTEXT_METHOD, X_ATTR_Deprecated) : + aname = cp.sym[constant_pool::s_Deprecated]; + // no data + break; + } + } + + if (aname == nullptr) + { + // Unparse a compressor-defined attribute. + layout_definition *lo = ad.getLayout(idx); + if (lo == nullptr) + { + unpack_abort("bad layout index"); + break; + } + assert((int)lo->idx == idx); + aname = lo->nameEntry; + if (aname == nullptr) + { + bytes nameb; + nameb.set(lo->name); + aname = cp.ensureUtf8(nameb); + // Cache the name entry for next time. + lo->nameEntry = aname; + } + // Execute all the layout elements. + band **bands = lo->bands(); + if (lo->hasCallables()) + { + band &cble = *bands[0]; + assert(cble.le_kind == EK_CBLE); + bands = cble.le_body; + } + putlayout(bands); + } + + if (aname == nullptr) + unpack_abort("bad attribute index"); + + byte *wp1 = wp; + wp = wp_at(abase); + + // DTRT if this attr is on the strip-list. + // (Note that we emptied the data out of the band first.) + if (ad.strip_names.contains(aname)) + { + continue; + } + + // patch the name and length + putref(aname); + putu4((int)(wp1 - (wp + 4))); // put the attr size + wp = wp1; + na++; // count the attrs actually written + } + + if (na != na0) + // Refresh changed count. + putu2_at(wp_at(naOffset), na); + return na; +} + +void unpacker::write_members(int num, int attrc) +{ + attr_definitions &ad = attr_defs[attrc]; + band &member_flags_hi = ad.xxx_flags_hi(); + band &member_flags_lo = ad.xxx_flags_lo(); + band &member_descr = (&member_flags_hi)[e_field_descr - e_field_flags_hi]; + bool haveLongFlags = ad.haveLongFlags(); + + putu2(num); + uint64_t indexMask = attr_defs[attrc].flagIndexMask(); + for (int i = 0; i < num; i++) + { + uint64_t mflags = member_flags_hi.getLong(member_flags_lo, haveLongFlags); + entry *mdescr = member_descr.getRef(); + cur_descr = mdescr; + putu2(cur_descr_flags = (ushort)(mflags & ~indexMask)); + putref(mdescr->descrName()); + putref(mdescr->descrType()); + write_attrs(attrc, (mflags & indexMask)); + } + cur_descr = nullptr; +} + +extern "C" int raw_address_cmp(const void *p1p, const void *p2p) +{ + void *p1 = *(void **)p1p; + void *p2 = *(void **)p2p; + return (p1 > p2) ? 1 : (p1 < p2) ? -1 : 0; +} + +void unpacker::write_classfile_tail() +{ + cur_classfile_tail.empty(); + set_output(&cur_classfile_tail); + + int i, num; + + attr_definitions &ad = attr_defs[ATTR_CONTEXT_CLASS]; + + bool haveLongFlags = ad.haveLongFlags(); + uint64_t kflags = class_flags_hi.getLong(class_flags_lo, haveLongFlags); + uint64_t indexMask = ad.flagIndexMask(); + + cur_class = class_this.getRef(); + cur_super = class_super.getRef(); + + if (cur_super == cur_class) + cur_super = nullptr; + // special representation for java/lang/Object + + putu2((ushort)(kflags & ~indexMask)); + putref(cur_class); + putref(cur_super); + + putu2(num = class_interface_count.getInt()); + for (i = 0; i < num; i++) + { + putref(class_interface.getRef()); + } + + write_members(class_field_count.getInt(), ATTR_CONTEXT_FIELD); + write_members(class_method_count.getInt(), ATTR_CONTEXT_METHOD); + + cur_class_has_local_ics = false; // may be set true by write_attrs + + int naOffset = (int)wpoffset(); + int na = write_attrs(ATTR_CONTEXT_CLASS, (kflags & indexMask)); + +// at the very last, choose which inner classes (if any) pertain to k: +#ifdef ASSERT + for (i = 0; i < ic_count; i++) + { + assert(!ics[i].requested); + } +#endif + // First, consult the global table and the local constant pool, + // and decide on the globally implied inner classes. + // (Note that we read the cpool's outputIndex fields, but we + // do not yet write them, since the local IC attribute might + // reverse a global decision to declare an IC.) + assert(requested_ics.length() == 0); // must start out empty + // Always include all members of the current class. + for (inner_class *child = cp.getFirstChildIC(cur_class); child != nullptr; + child = cp.getNextChildIC(child)) + { + child->requested = true; + requested_ics.add(child); + } + // And, for each inner class mentioned in the constant pool, + // include it and all its outers. + int noes = cp.outputEntries.length(); + entry **oes = (entry **)cp.outputEntries.base(); + for (i = 0; i < noes; i++) + { + entry &e = *oes[i]; + if (e.tag != CONSTANT_Class) + continue; // wrong sort + for (inner_class *ic = cp.getIC(&e); ic != nullptr; ic = cp.getIC(ic->outer)) + { + if (ic->requested) + break; // already processed + ic->requested = true; + requested_ics.add(ic); + } + } + int local_ics = requested_ics.length(); + // Second, consult a local attribute (if any) and adjust the global set. + inner_class *extra_ics = nullptr; + int num_extra_ics = 0; + if (cur_class_has_local_ics) + { + // adjust the set of ICs by symmetric set difference w/ the locals + num_extra_ics = class_InnerClasses_N.getInt(); + if (num_extra_ics == 0) + { + // Explicit zero count has an irregular meaning: It deletes the attr. + local_ics = 0; // (short-circuit all tests of requested bits) + } + else + { + extra_ics = T_NEW(inner_class, num_extra_ics); + // Note: extra_ics will be freed up by next call to get_next_file(). + } + } + for (i = 0; i < num_extra_ics; i++) + { + inner_class &extra_ic = extra_ics[i]; + extra_ic.inner = class_InnerClasses_RC.getRef(); + // Find the corresponding equivalent global IC: + inner_class *global_ic = cp.getIC(extra_ic.inner); + int flags = class_InnerClasses_F.getInt(); + if (flags == 0) + { + // The extra IC is simply a copy of a global IC. + if (global_ic == nullptr) + { + unpack_abort("bad reference to inner class"); + break; + } + extra_ic = (*global_ic); // fill in rest of fields + } + else + { + flags &= ~ACC_IC_LONG_FORM; // clear high bit if set to get clean zero + extra_ic.flags = flags; + extra_ic.outer = class_InnerClasses_outer_RCN.getRefN(); + extra_ic.name = class_InnerClasses_name_RUN.getRefN(); + // Detect if this is an exact copy of the global tuple. + if (global_ic != nullptr) + { + if (global_ic->flags != extra_ic.flags || global_ic->outer != extra_ic.outer || + global_ic->name != extra_ic.name) + { + global_ic = nullptr; // not really the same, so break the link + } + } + } + if (global_ic != nullptr && global_ic->requested) + { + // This local repetition reverses the globally implied request. + global_ic->requested = false; + extra_ic.requested = false; + local_ics -= 1; + } + else + { + // The global either does not exist, or is not yet requested. + extra_ic.requested = true; + local_ics += 1; + } + } + // Finally, if there are any that survived, put them into an attribute. + // (Note that a zero-count attribute is always deleted.) + // The putref calls below will tell the constant pool to add any + // necessary local CP references to support the InnerClasses attribute. + // This step must be the last round of additions to the local CP. + if (local_ics > 0) + { + // append the new attribute: + putref(cp.sym[constant_pool::s_InnerClasses]); + putu4(2 + 2 * 4 * local_ics); + putu2(local_ics); + PTRLIST_QSORT(requested_ics, raw_address_cmp); + int num_global_ics = requested_ics.length(); + for (i = -num_global_ics; i < num_extra_ics; i++) + { + inner_class *ic; + if (i < 0) + ic = (inner_class *)requested_ics.get(num_global_ics + i); + else + ic = &extra_ics[i]; + if (ic->requested) + { + putref(ic->inner); + putref(ic->outer); + putref(ic->name); + putu2(ic->flags); + } + } + putu2_at(wp_at(naOffset), ++na); // increment class attr count + } + + // Tidy up global 'requested' bits: + for (i = requested_ics.length(); --i >= 0;) + { + inner_class *ic = (inner_class *)requested_ics.get(i); + ic->requested = false; + } + requested_ics.empty(); + + close_output(); + + // rewrite CP references in the tail + cp.computeOutputIndexes(); + int nextref = 0; + for (i = 0; i < (int)class_fixup_type.size(); i++) + { + int type = class_fixup_type.getByte(i); + byte *fixp = wp_at(class_fixup_offset.get(i)); + entry *e = (entry *)class_fixup_ref.get(nextref++); + int idx = e->getOutputIndex(); + switch (type) + { + case 1: + putu1_at(fixp, idx); + break; + case 2: + putu2_at(fixp, idx); + break; + default: + assert(false); // should not reach here + } + } +} + +void unpacker::write_classfile_head() +{ + cur_classfile_head.empty(); + set_output(&cur_classfile_head); + + putu4(JAVA_MAGIC); + putu2(cur_class_minver); + putu2(cur_class_majver); + putu2(cp.outputIndexLimit); + + int checkIndex = 1; + int noes = cp.outputEntries.length(); + entry **oes = (entry **)cp.outputEntries.base(); + for (int i = 0; i < noes; i++) + { + entry &e = *oes[i]; + assert(e.getOutputIndex() == checkIndex++); + byte tag = e.tag; + assert(tag != CONSTANT_Signature); + putu1(tag); + switch (tag) + { + case CONSTANT_Utf8: + putu2((int)e.value.b.len); + put_bytes(e.value.b); + break; + case CONSTANT_Integer: + case CONSTANT_Float: + putu4(e.value.i); + break; + case CONSTANT_Long: + case CONSTANT_Double: + putu8(e.value.l); + assert(checkIndex++); + break; + case CONSTANT_Class: + case CONSTANT_String: + // just write the ref + putu2(e.refs[0]->getOutputIndex()); + break; + case CONSTANT_Fieldref: + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: + case CONSTANT_NameandType: + putu2(e.refs[0]->getOutputIndex()); + putu2(e.refs[1]->getOutputIndex()); + break; + default: + unpack_abort(ERROR_INTERNAL); + } + } + close_output(); +} + +unpacker::file *unpacker::get_next_file() +{ + free_temps(); + if (files_remaining == 0) + { + // Leave a clue that we're exhausted. + cur_file.name = nullptr; + cur_file.size = 0; + if (archive_size != 0) + { + uint64_t predicted_size = unsized_bytes_read + archive_size; + if (predicted_size != bytes_read) + unpack_abort("archive header had incorrect size"); + } + return nullptr; + } + files_remaining -= 1; + assert(files_written < file_count || classes_written < class_count); + cur_file.name = ""; + cur_file.size = 0; + cur_file.modtime = default_file_modtime; + cur_file.options = default_file_options; + cur_file.data[0].set(nullptr, 0); + cur_file.data[1].set(nullptr, 0); + if (files_written < file_count) + { + entry *e = file_name.getRef(); + cur_file.name = e->utf8String(); + bool haveLongSize = ((archive_options & AO_HAVE_FILE_SIZE_HI) != 0); + cur_file.size = file_size_hi.getLong(file_size_lo, haveLongSize); + if ((archive_options & AO_HAVE_FILE_MODTIME) != 0) + cur_file.modtime += file_modtime.getInt(); // relative to archive modtime + if ((archive_options & AO_HAVE_FILE_OPTIONS) != 0) + cur_file.options |= file_options.getInt() & ~suppress_file_options; + } + else if (classes_written < class_count) + { + // there is a class for a missing file record + cur_file.options |= FO_IS_CLASS_STUB; + } + if ((cur_file.options & FO_IS_CLASS_STUB) != 0) + { + assert(classes_written < class_count); + classes_written += 1; + if (cur_file.size != 0) + { + unpack_abort("class file size transmitted"); + } + reset_cur_classfile(); + + // write the meat of the classfile: + write_classfile_tail(); + cur_file.data[1] = cur_classfile_tail.b; + + // write the CP of the classfile, second: + write_classfile_head(); + cur_file.data[0] = cur_classfile_head.b; + + cur_file.size += cur_file.data[0].len; + cur_file.size += cur_file.data[1].len; + if (cur_file.name[0] == '\0') + { + bytes &prefix = cur_class->ref(0)->value.b; + const char *suffix = ".class"; + int len = (int)(prefix.len + strlen(suffix)); + bytes name; + name.set(T_NEW(byte, add_size(len, 1)), len); + cur_file.name = name.strcat(prefix).strcat(suffix).strval(); + } + } + else + { + // If there is buffered file data, produce a pointer to it. + if (cur_file.size != (size_t)cur_file.size) + { + // Silly size specified. + unpack_abort("resource file too large"); + } + size_t rpleft = input_remaining(); + if (rpleft > 0) + { + if (rpleft > cur_file.size) + rpleft = (size_t)cur_file.size; + cur_file.data[0].set(rp, rpleft); + rp += rpleft; + } + if (rpleft < cur_file.size) + { + // Caller must read the rest. + size_t fleft = (size_t)cur_file.size - rpleft; + bytes_read += fleft; // Credit it to the overall archive size. + } + } + bytes_written += cur_file.size; + files_written += 1; + return &cur_file; +} + +// Write a file to jarout. +void unpacker::write_file_to_jar(unpacker::file *f) +{ + size_t htsize = f->data[0].len + f->data[1].len; + uint64_t fsize = f->size; + if (htsize == fsize) + { + jarout->addJarEntry(f->name, f->deflate_hint(), f->modtime, f->data[0], f->data[1]); + } + else + { + assert(input_remaining() == 0); + bytes part1, part2; + part1.len = f->data[0].len; + part1.set(T_NEW(byte, part1.len), part1.len); + part1.copyFrom(f->data[0]); + assert(f->data[1].len == 0); + part2.set(nullptr, 0); + size_t fleft = (size_t)fsize - part1.len; + assert(bytes_read > fleft); // part2 already credited by get_next_file + bytes_read -= fleft; + if (fleft > 0) + { + // Must read some more. + if (live_input) + { + // Stop using the input buffer. Make a new one: + if (free_input) + input.free(); + input.init(fleft > (1 << 12) ? fleft : (1 << 12)); + free_input = true; + live_input = false; + } + else + { + // Make it large enough. + assert(free_input); // must be reallocable + input.ensureSize(fleft); + } + rplimit = rp = input.base(); + input.setLimit(rp + fleft); + if (!ensure_input(fleft)) + unpack_abort("EOF reading resource file"); + part2.ptr = input_scan(); + part2.len = input_remaining(); + rplimit = rp = input.base(); + } + jarout->addJarEntry(f->name, f->deflate_hint(), f->modtime, part1, part2); + } + if (verbose >= 3) + { + fprintf(stderr, "Wrote " LONG_LONG_FORMAT " bytes to: %s\n", fsize, f->name); + } +} diff --git a/depends/pack200/src/unpack.h b/depends/pack200/src/unpack.h new file mode 100644 index 00000000..0100700d --- /dev/null +++ b/depends/pack200/src/unpack.h @@ -0,0 +1,547 @@ +/* + * Copyright (c) 2002, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// Global Structures +struct jar; +struct gunzip; +struct band; +struct constant_pool; +struct entry; +struct cpindex; +struct inner_class; +struct value_stream; + +struct cpindex +{ + uint32_t len; + entry *base1; // base of primary index + entry **base2; // base of secondary index + byte ixTag; // type of entries (!= CONSTANT_None), plus 64 if sub-index + enum + { + SUB_TAG = 64 + }; + + entry *get(uint32_t i); + + void init(int len_, entry *base1_, int ixTag_) + { + len = len_; + base1 = base1_; + base2 = nullptr; + ixTag = ixTag_; + } + void init(int len_, entry **base2_, int ixTag_) + { + len = len_; + base1 = nullptr; + base2 = base2_; + ixTag = ixTag_; + } +}; + +struct constant_pool +{ + uint32_t nentries; + entry *entries; + entry *first_extra_entry; + uint32_t maxentries; // total allocated size of entries + + // Position and size of each homogeneous subrange: + int tag_count[CONSTANT_Limit]; + int tag_base[CONSTANT_Limit]; + cpindex tag_index[CONSTANT_Limit]; + ptrlist tag_extras[CONSTANT_Limit]; + + cpindex *member_indexes; // indexed by 2*CONSTANT_Class.inord + cpindex *getFieldIndex(entry *classRef); + cpindex *getMethodIndex(entry *classRef); + + inner_class **ic_index; + inner_class **ic_child_index; + inner_class *getIC(entry *inner); + inner_class *getFirstChildIC(entry *outer); + inner_class *getNextChildIC(inner_class *child); + + int outputIndexLimit; // index limit after renumbering + ptrlist outputEntries; // list of entry* needing output idx assigned + + entry **hashTab; + uint32_t hashTabLength; + entry *&hashTabRef(byte tag, bytes &b); + entry *ensureUtf8(bytes &b); + entry *ensureClass(bytes &b); + + // Well-known Utf8 symbols. + enum + { +#define SNAME(n, s) s_##s, + ALL_ATTR_DO(SNAME) +#undef SNAME + s_lt_init_gt, // <init> + s_LIMIT + }; + entry *sym[s_LIMIT]; + + // read counts from hdr, allocate main arrays + enum + { + NUM_COUNTS = 12 + }; + void init(unpacker *u, int counts[NUM_COUNTS]); + + // pointer to outer unpacker, for error checks etc. + unpacker *u; + + int getCount(byte tag) + { + assert((uint32_t)tag < CONSTANT_Limit); + return tag_count[tag]; + } + cpindex *getIndex(byte tag) + { + assert((uint32_t)tag < CONSTANT_Limit); + return &tag_index[tag]; + } + cpindex *getKQIndex(); // uses cur_descr + + void expandSignatures(); + void initMemberIndexes(); + + void computeOutputOrder(); + void computeOutputIndexes(); + void resetOutputIndexes(); +}; + +/* + * The unpacker provides the entry points to the unpack engine, + * as well as maintains the state of the engine. + */ +struct unpacker +{ + // One element of the resulting JAR. + struct file + { + const char *name; + uint64_t size; + int modtime; + int options; + bytes data[2]; + // Note: If Sum(data[*].len) < size, + // remaining bytes must be read directly from the input stream. + bool deflate_hint() + { + return ((options & FO_DEFLATE_HINT) != 0); + } + }; + + // if running Unix-style, here are the inputs and outputs + FILE *infileptr; // buffered + bytes inbytes; // direct + gunzip *gzin; // gunzip filter, if any + jar *jarout; // output JAR file + + // pointer to self, for U_NEW macro + unpacker *u; + + ptrlist mallocs; // list of guys to free when we are all done + ptrlist tmallocs; // list of guys to free on next client request + fillbytes smallbuf; // supplies small alloc requests + fillbytes tsmallbuf; // supplies temporary small alloc requests + + // option management members + int verbose; // verbose level, 0 means no output + int deflate_hint_or_zero; // ==0 means not set, otherwise -1 or 1 + int modification_time_or_zero; + + // input stream + fillbytes input; // the whole block (size is predicted, has slop too) + bool live_input; // is the data in this block live? + bool free_input; // must the input buffer be freed? + byte *rp; // read pointer (< rplimit <= input.limit()) + byte *rplimit; // how much of the input block has been read? + uint64_t bytes_read; + int unsized_bytes_read; + + // callback to read at least one byte, up to available input + typedef int64_t (*read_input_fn_t)(unpacker *self, void *buf, int64_t minlen, + int64_t maxlen); + read_input_fn_t read_input_fn; + + // archive header fields + int magic, minver, majver; + size_t archive_size; + int archive_next_count, archive_options, archive_modtime; + int band_headers_size; + int file_count, attr_definition_count, ic_count, class_count; + int default_class_minver, default_class_majver; + int default_file_options, suppress_file_options; // not header fields + int default_archive_modtime, default_file_modtime; // not header fields + int code_count; // not a header field + int files_remaining; // not a header field + + // engine state + band *all_bands; // indexed by band_number + byte *meta_rp; // read-pointer into (copy of) band_headers + constant_pool cp; // all constant pool information + inner_class *ics; // InnerClasses + + // output stream + bytes output; // output block (either classfile head or tail) + byte *wp; // write pointer (< wplimit == output.limit()) + byte *wpbase; // write pointer starting address (<= wp) + byte *wplimit; // how much of the output block has been written? + + // output state + file cur_file; + entry *cur_class; // CONSTANT_Class entry + entry *cur_super; // CONSTANT_Class entry or nullptr + entry *cur_descr; // CONSTANT_NameandType entry + int cur_descr_flags; // flags corresponding to cur_descr + int cur_class_minver, cur_class_majver; + bool cur_class_has_local_ics; + fillbytes cur_classfile_head; + fillbytes cur_classfile_tail; + int files_written; // also tells which file we're working on + int classes_written; // also tells which class we're working on + uint64_t bytes_written; + intlist bcimap; + fillbytes class_fixup_type; + intlist class_fixup_offset; + ptrlist class_fixup_ref; + fillbytes code_fixup_type; // which format of branch operand? + intlist code_fixup_offset; // location of operand needing fixup + intlist code_fixup_source; // encoded ID of branch insn + ptrlist requested_ics; // which ics need output? + + // stats pertaining to multiple segments (updated on reset) + uint64_t bytes_read_before_reset; + uint64_t bytes_written_before_reset; + int files_written_before_reset; + int classes_written_before_reset; + int segments_read_before_reset; + + // attribute state + struct layout_definition + { + uint32_t idx; // index (0..31...) which identifies this layout + const char *name; // name of layout + entry *nameEntry; + const char *layout; // string of layout (not yet parsed) + band **elems; // array of top-level layout elems (or callables) + + bool hasCallables() + { + return layout[0] == '['; + } + band **bands() + { + assert(elems != nullptr); + return elems; + } + }; + struct attr_definitions + { + unpacker *u; // pointer to self, for U_NEW macro + int xxx_flags_hi_bn; // locator for flags, count, indexes, calls bands + int attrc; // ATTR_CONTEXT_CLASS, etc. + uint32_t flag_limit; // 32 or 63, depending on archive_options bit + uint64_t predef; // mask of built-in definitions + uint64_t redef; // mask of local flag definitions or redefinitions + ptrlist layouts; // local (compressor-defined) defs, in index order + int flag_count[X_ATTR_LIMIT_FLAGS_HI]; + intlist overflow_count; + ptrlist strip_names; // what attribute names are being stripped? + ptrlist band_stack; // Temp., used during layout parsing. + ptrlist calls_to_link; // (ditto) + int bands_made; // (ditto) + + void free() + { + layouts.free(); + overflow_count.free(); + strip_names.free(); + band_stack.free(); + calls_to_link.free(); + } + + // Locate the five fixed bands. + band &xxx_flags_hi(); + band &xxx_flags_lo(); + band &xxx_attr_count(); + band &xxx_attr_indexes(); + band &xxx_attr_calls(); + band &fixed_band(int e_class_xxx); + + // Register a new layout, and make bands for it. + layout_definition *defineLayout(int idx, const char *name, const char *layout); + layout_definition *defineLayout(int idx, entry *nameEntry, const char *layout); + band **buildBands(layout_definition *lo); + + // Parse a layout string or part of one, recursively if necessary. + const char *parseLayout(const char *lp, band **&res, int curCble); + const char *parseNumeral(const char *lp, int &res); + const char *parseIntLayout(const char *lp, band *&res, byte le_kind, + bool can_be_signed = false); + band **popBody(int band_stack_base); // pops a body off band_stack + + // Read data into the bands of the idx-th layout. + void readBandData(int idx); // parse layout, make bands, read data + void readBandData(band **body, uint32_t count); // recursive helper + + layout_definition *getLayout(uint32_t idx) + { + if (idx >= (uint32_t)layouts.length()) + return nullptr; + return (layout_definition *)layouts.get(idx); + } + + void setHaveLongFlags(bool z) + { + assert(flag_limit == 0); // not set up yet + flag_limit = (z ? X_ATTR_LIMIT_FLAGS_HI : X_ATTR_LIMIT_NO_FLAGS_HI); + } + bool haveLongFlags() + { + assert(flag_limit == X_ATTR_LIMIT_NO_FLAGS_HI || + flag_limit == X_ATTR_LIMIT_FLAGS_HI); + return flag_limit == X_ATTR_LIMIT_FLAGS_HI; + } + + // Return flag_count if idx is predef and not redef, else zero. + int predefCount(uint32_t idx); + + bool isRedefined(uint32_t idx) + { + if (idx >= flag_limit) + return false; + return (bool)((redef >> idx) & 1); + } + bool isPredefined(uint32_t idx) + { + if (idx >= flag_limit) + return false; + return (bool)(((predef & ~redef) >> idx) & 1); + } + uint64_t flagIndexMask() + { + return (predef | redef); + } + bool isIndex(uint32_t idx) + { + assert(flag_limit != 0); // must be set up already + if (idx < flag_limit) + return (bool)(((predef | redef) >> idx) & 1); + else + return (idx - flag_limit < (uint32_t)overflow_count.length()); + } + int &getCount(uint32_t idx) + { + assert(isIndex(idx)); + if (idx < flag_limit) + return flag_count[idx]; + else + return overflow_count.get(idx - flag_limit); + } + }; + + attr_definitions attr_defs[ATTR_CONTEXT_LIMIT]; + + // Initialization + void init(read_input_fn_t input_fn = nullptr); + // Resets to a known sane state + void reset(); + // Deallocates all storage. + void free(); + // Deallocates temporary storage (volatile after next client call). + void free_temps() + { + tsmallbuf.init(); + tmallocs.freeAll(); + } + + // Option management methods + bool set_option(const char *option, const char *value); + const char *get_option(const char *option); + + // Fetching input. + bool ensure_input(int64_t more); + byte *input_scan() + { + return rp; + } + size_t input_remaining() + { + return rplimit - rp; + } + size_t input_consumed() + { + return rp - input.base(); + } + + // Entry points to the unpack engine + static int run(int argc, char **argv); // Unix-style entry point. + void check_options(); + void start(void *packptr = nullptr, size_t len = 0); + void write_file_to_jar(file *f); + void finish(); + + // Public post unpack methods + int get_files_remaining() + { + return files_remaining; + } + int get_segments_remaining() + { + return archive_next_count; + } + file *get_next_file(); // returns nullptr on last file + + // General purpose methods + void *alloc(size_t size) + { + return alloc_heap(size, true); + } + void *temp_alloc(size_t size) + { + return alloc_heap(size, true, true); + } + void *alloc_heap(size_t size, bool smallOK = false, bool temp = false); + void saveTo(bytes &b, const char *str) + { + saveTo(b, (byte *)str, strlen(str)); + } + void saveTo(bytes &b, bytes &data) + { + saveTo(b, data.ptr, data.len); + } + void saveTo(bytes &b, byte *ptr, size_t len); //{ b.ptr = U_NEW...} + const char *saveStr(const char *str) + { + bytes buf; + saveTo(buf, str); + return buf.strval(); + } + const char *saveIntStr(int num) + { + char buf[30]; + sprintf(buf, "%d", num); + return saveStr(buf); + } + static unpacker *current(); // find current instance + + // Output management + void set_output(fillbytes *which) + { + assert(wp == nullptr); + which->ensureSize(1 << 12); // covers the average classfile + wpbase = which->base(); + wp = which->limit(); + wplimit = which->end(); + } + fillbytes *close_output(fillbytes *which = nullptr); // inverse of set_output + + // These take an implicit parameter of wp/wplimit, and resize as necessary: + byte *put_space(size_t len); // allocates space at wp, returns pointer + size_t put_empty(size_t s) + { + byte *p = put_space(s); + return p - wpbase; + } + void ensure_put_space(size_t len); + void put_bytes(bytes &b) + { + b.writeTo(put_space(b.len)); + } + void putu1(int n) + { + putu1_at(put_space(1), n); + } + void putu1_fast(int n) + { + putu1_at(wp++, n); + } + void putu2(int n); // { putu2_at(put_space(2), n); } + void putu4(int n); // { putu4_at(put_space(4), n); } + void putu8(int64_t n); // { putu8_at(put_space(8), n); } + void putref(entry *e); // { putu2_at(put_space(2), putref_index(e, 2)); } + void putu1ref(entry *e); // { putu1_at(put_space(1), putref_index(e, 1)); } + int putref_index(entry *e, int size); // size in [1..2] + void put_label(int curIP, int size); // size in {2,4} + void putlayout(band **body); + void put_stackmap_type(); + + size_t wpoffset() + { + return (size_t)(wp - wpbase); + } // (unvariant across overflow) + byte *wp_at(size_t offset) + { + return wpbase + offset; + } + uint32_t to_bci(uint32_t bii); + void get_code_header(int &max_stack, int &max_na_locals, int &handler_count, int &cflags); + band *ref_band_for_self_op(int bc, bool &isAloadVar, int &origBCVar); + band *ref_band_for_op(int bc); + + // Definitions of standard classfile int formats: + static void putu1_at(byte *wp, int n) + { + assert(n == (n & 0xFF)); + wp[0] = n; + } + static void putu2_at(byte *wp, int n); + static void putu4_at(byte *wp, int n); + static void putu8_at(byte *wp, int64_t n); + + // Private stuff + void reset_cur_classfile(); + void write_classfile_tail(); + void write_classfile_head(); + void write_code(); + void write_bc_ops(); + void write_members(int num, int attrc); // attrc=ATTR_CONTEXT_FIELD/METHOD + int write_attrs(int attrc, uint64_t indexBits); + + // The readers + void read_bands(); + void read_file_header(); + void read_cp(); + void read_cp_counts(value_stream &hdr); + void read_attr_defs(); + void read_ics(); + void read_attrs(int attrc, int obj_count); + void read_classes(); + void read_code_headers(); + void read_bcs(); + void read_bc_ops(); + void read_files(); + void read_Utf8_values(entry *cpMap, int len); + void read_single_words(band &cp_band, entry *cpMap, int len); + void read_double_words(band &cp_bands, entry *cpMap, int len); + void read_single_refs(band &cp_band, byte refTag, entry *cpMap, int len); + void read_double_refs(band &cp_band, byte ref1Tag, byte ref2Tag, entry *cpMap, int len); + void read_signature_values(entry *cpMap, int len); +}; diff --git a/depends/pack200/src/unpack200.cpp b/depends/pack200/src/unpack200.cpp new file mode 100644 index 00000000..22b7f3b0 --- /dev/null +++ b/depends/pack200/src/unpack200.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2003, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +#include <sys/types.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> +#include <assert.h> +#include <limits.h> +#include <time.h> +#include <stdint.h> + +#include "constants.h" +#include "utils.h" +#include "defines.h" +#include "bytes.h" +#include "coding.h" +#include "unpack200.h" +#include "unpack.h" +#include "zip.h" + +// Callback for fetching data, Unix style. +static int64_t read_input_via_stdio(unpacker *u, void *buf, int64_t minlen, int64_t maxlen) +{ + assert(u->infileptr != nullptr); + assert(minlen <= maxlen); // don't talk nonsense + int64_t numread = 0; + char *bufptr = (char *)buf; + while (numread < minlen) + { + // read available input, up to buf.length or maxlen + int readlen = (1 << 16); + if (readlen > (maxlen - numread)) + readlen = (int)(maxlen - numread); + int nr = 0; + + nr = (int)fread(bufptr, 1, readlen, u->infileptr); + if (nr <= 0) + { + if (errno != EINTR) + break; + nr = 0; + } + numread += nr; + bufptr += nr; + assert(numread <= maxlen); + } + return numread; +} + +enum +{ + EOF_MAGIC = 0, + BAD_MAGIC = -1 +}; + +static int read_magic(unpacker *u, char peek[], int peeklen) +{ + assert(peeklen == 4); // magic numbers are always 4 bytes + int64_t nr = (u->read_input_fn)(u, peek, peeklen, peeklen); + if (nr != peeklen) + { + return (nr == 0) ? EOF_MAGIC : BAD_MAGIC; + } + int magic = 0; + for (int i = 0; i < peeklen; i++) + { + magic <<= 8; + magic += peek[i] & 0xFF; + } + return magic; +} + +void unpack_200(FILE *input, FILE *output) +{ + unpacker u; + u.init(read_input_via_stdio); + + // initialize jar output + // the output takes ownership of the file handle + jar jarout; + jarout.init(&u); + jarout.jarfp = output; + + // the input doesn't + u.infileptr = input; + + // read the magic! + char peek[4]; + int magic; + magic = read_magic(&u, peek, (int)sizeof(peek)); + + // if it is a gzip encoded file, we need an extra gzip input filter + if ((magic & GZIP_MAGIC_MASK) == GZIP_MAGIC) + { + gunzip *gzin = NEW(gunzip, 1); + gzin->init(&u); + // FIXME: why the side effects? WHY? + u.gzin->start(magic); + u.start(); + } + else + { + // otherwise, feed the bytes to the unpacker directly + u.start(peek, sizeof(peek)); + } + + // Note: The checks to u.aborting() are necessary to gracefully + // terminate processing when the first segment throws an error. + for (;;) + { + // Each trip through this loop unpacks one segment + // and then resets the unpacker. + for (unpacker::file *filep; (filep = u.get_next_file()) != nullptr;) + { + u.write_file_to_jar(filep); + } + + // Peek ahead for more data. + magic = read_magic(&u, peek, (int)sizeof(peek)); + if (magic != (int)JAVA_PACKAGE_MAGIC) + { + // we do not feel strongly about this kind of thing... + /* + if (magic != EOF_MAGIC) + unpack_abort("garbage after end of pack archive"); + */ + break; // all done + } + + // Release all storage from parsing the old segment. + u.reset(); + // Restart, beginning with the peek-ahead. + u.start(peek, sizeof(peek)); + } + u.finish(); + u.free(); // tidy up malloc blocks + fclose(input); +} diff --git a/depends/pack200/src/utils.cpp b/depends/pack200/src/utils.cpp new file mode 100644 index 00000000..0b7d91ca --- /dev/null +++ b/depends/pack200/src/utils.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <assert.h> +#include <stdint.h> + +#include <sys/stat.h> + +#ifdef _MSC_VER +#include <direct.h> +#include <io.h> +#include <process.h> +#else +#include <unistd.h> +#endif + +#include "constants.h" +#include "defines.h" +#include "bytes.h" +#include "utils.h" + +#include "unpack.h" + +void *must_malloc(size_t size) +{ + size_t msize = size; + void *ptr = (msize > PSIZE_MAX) ? nullptr : malloc(msize); + if (ptr != nullptr) + { + memset(ptr, 0, size); + } + else + { + throw std::runtime_error(ERROR_ENOMEM); + } + return ptr; +} + +void unpack_abort(const char *msg) +{ + if (msg == nullptr) + msg = "corrupt pack file or internal error"; + throw std::runtime_error(msg); +} diff --git a/depends/pack200/src/utils.h b/depends/pack200/src/utils.h new file mode 100644 index 00000000..5a3dc8f6 --- /dev/null +++ b/depends/pack200/src/utils.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// Definitions of our util functions + +#include <stdexcept> + +void *must_malloc(size_t size); + +// overflow management +#define OVERFLOW ((size_t) - 1) +#define PSIZE_MAX (OVERFLOW / 2) /* normal size limit */ + +inline size_t scale_size(size_t size, size_t scale) +{ + return (size > PSIZE_MAX / scale) ? OVERFLOW : size * scale; +} + +inline size_t add_size(size_t size1, size_t size2) +{ + return ((size1 | size2 | (size1 + size2)) > PSIZE_MAX) ? OVERFLOW : size1 + size2; +} + +inline size_t add_size(size_t size1, size_t size2, int size3) +{ + return add_size(add_size(size1, size2), size3); +} + +struct unpacker; +/// This throws an exception! +extern void unpack_abort(const char *msg = nullptr); diff --git a/depends/pack200/src/zip.cpp b/depends/pack200/src/zip.cpp new file mode 100644 index 00000000..32e8bd50 --- /dev/null +++ b/depends/pack200/src/zip.cpp @@ -0,0 +1,589 @@ +/* + * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * Note: Lifted from uncrunch.c from jdk sources + */ +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <stdint.h> + +#include <stdlib.h> +#include <assert.h> + +#ifndef _MSC_VER +#include <strings.h> +#endif + +#include "defines.h" +#include "bytes.h" +#include "utils.h" + +#include "constants.h" +#include "unpack.h" + +#include "zip.h" + +#include "zlib.h" + +inline uint32_t jar::get_crc32(uint32_t c, uchar *ptr, uint32_t len) +{ + return crc32(c, ptr, len); +} + +// FIXME: this is bullshit. Do real endianness detection. +#ifdef sparc +#define SWAP_BYTES(a) ((((a) << 8) & 0xff00) | 0x00ff) & (((a) >> 8) | 0xff00) +#else +#define SWAP_BYTES(a) (a) +#endif + +#define GET_INT_LO(a) SWAP_BYTES(a & 0xFFFF) + +#define GET_INT_HI(a) SWAP_BYTES((a >> 16) & 0xFFFF); + +void jar::init(unpacker *u_) +{ + BYTES_OF(*this).clear(); + u = u_; + u->jarout = this; +} + +// Write data to the ZIP output stream. +void jar::write_data(void *buff, int len) +{ + while (len > 0) + { + int rc = (int)fwrite(buff, 1, len, jarfp); + if (rc <= 0) + { + fprintf(stderr, "Error: write on output file failed err=%d\n", errno); + exit(1); // Called only from the native standalone unpacker + } + output_file_offset += rc; + buff = ((char *)buff) + rc; + len -= rc; + } +} + +void jar::add_to_jar_directory(const char *fname, bool store, int modtime, int len, int clen, + uint32_t crc) +{ + uint32_t fname_length = (uint32_t)strlen(fname); + ushort header[23]; + if (modtime == 0) + modtime = default_modtime; + uint32_t dostime = get_dostime(modtime); + + header[0] = (ushort)SWAP_BYTES(0x4B50); + header[1] = (ushort)SWAP_BYTES(0x0201); + header[2] = (ushort)SWAP_BYTES(0xA); + + // required version + header[3] = (ushort)SWAP_BYTES(0xA); + + // flags 02 = maximum sub-compression flag + header[4] = (store) ? 0x0 : SWAP_BYTES(0x2); + + // Compression method 8=deflate. + header[5] = (store) ? 0x0 : SWAP_BYTES(0x08); + + // Last modified date and time. + header[6] = (ushort)GET_INT_LO(dostime); + header[7] = (ushort)GET_INT_HI(dostime); + + // CRC + header[8] = (ushort)GET_INT_LO(crc); + header[9] = (ushort)GET_INT_HI(crc); + + // Compressed length: + header[10] = (ushort)GET_INT_LO(clen); + header[11] = (ushort)GET_INT_HI(clen); + + // Uncompressed length. + header[12] = (ushort)GET_INT_LO(len); + header[13] = (ushort)GET_INT_HI(len); + + // Filename length + header[14] = (ushort)SWAP_BYTES(fname_length); + // So called "extra field" length. + header[15] = 0; + // So called "comment" length. + header[16] = 0; + // Disk number start + header[17] = 0; + // File flags => binary + header[18] = 0; + // More file flags + header[19] = 0; + header[20] = 0; + // Offset within ZIP file. + header[21] = (ushort)GET_INT_LO(output_file_offset); + header[22] = (ushort)GET_INT_HI(output_file_offset); + + // Copy the whole thing into the central directory. + central_directory.append(header, sizeof(header)); + + // Copy the fname to the header. + central_directory.append(fname, fname_length); + + central_directory_count++; +} + +void jar::write_jar_header(const char *fname, bool store, int modtime, int len, int clen, + uint32_t crc) +{ + uint32_t fname_length = (uint32_t)strlen(fname); + ushort header[15]; + if (modtime == 0) + modtime = default_modtime; + uint32_t dostime = get_dostime(modtime); + + // ZIP LOC magic. + header[0] = (ushort)SWAP_BYTES(0x4B50); + header[1] = (ushort)SWAP_BYTES(0x0403); + + // Version + header[2] = (ushort)SWAP_BYTES(0xA); + + // flags 02 = maximum sub-compression flag + header[3] = (store) ? 0x0 : SWAP_BYTES(0x2); + + // Compression method = deflate + header[4] = (store) ? 0x0 : SWAP_BYTES(0x08); + + // Last modified date and time. + header[5] = (ushort)GET_INT_LO(dostime); + header[6] = (ushort)GET_INT_HI(dostime); + + // CRC + header[7] = (ushort)GET_INT_LO(crc); + header[8] = (ushort)GET_INT_HI(crc); + + // Compressed length: + header[9] = (ushort)GET_INT_LO(clen); + header[10] = (ushort)GET_INT_HI(clen); + + // Uncompressed length. + header[11] = (ushort)GET_INT_LO(len); + header[12] = (ushort)GET_INT_HI(len); + + // Filename length + header[13] = (ushort)SWAP_BYTES(fname_length); + // So called "extra field" length. + header[14] = 0; + + // Write the LOC header to the output file. + write_data(header, (int)sizeof(header)); + + // Copy the fname to the header. + write_data((char *)fname, (int)fname_length); +} + +void jar::write_central_directory() +{ + bytes mc; + mc.set("PACK200"); + + ushort header[11]; + + // Create the End of Central Directory structure. + header[0] = (ushort)SWAP_BYTES(0x4B50); + header[1] = (ushort)SWAP_BYTES(0x0605); + // disk numbers + header[2] = 0; + header[3] = 0; + // Number of entries in central directory. + header[4] = (ushort)SWAP_BYTES(central_directory_count); + header[5] = (ushort)SWAP_BYTES(central_directory_count); + // Size of the central directory} + header[6] = (ushort)GET_INT_LO((int)central_directory.size()); + header[7] = (ushort)GET_INT_HI((int)central_directory.size()); + // Offset of central directory within disk. + header[8] = (ushort)GET_INT_LO(output_file_offset); + header[9] = (ushort)GET_INT_HI(output_file_offset); + // zipfile comment length; + header[10] = (ushort)SWAP_BYTES((int)mc.len); + + // Write the central directory. + write_data(central_directory.b); + + // Write the End of Central Directory structure. + write_data(header, (int)sizeof(header)); + + // Write the comment. + write_data(mc); +} + +// Public API + +// Open a Jar file and initialize. +void jar::openJarFile(const char *fname) +{ + if (!jarfp) + { + jarfp = fopen(fname, "wb"); + if (!jarfp) + { + fprintf(stderr, "Error: Could not open jar file: %s\n", fname); + exit(3); // Called only from the native standalone unpacker + } + } +} + +// Add a ZIP entry and copy the file data +void jar::addJarEntry(const char *fname, bool deflate_hint, int modtime, bytes &head, + bytes &tail) +{ + int len = (int)(head.len + tail.len); + int clen = 0; + + uint32_t crc = get_crc32(0, Z_NULL, 0); + if (head.len != 0) + crc = get_crc32(crc, (uchar *)head.ptr, (uint32_t)head.len); + if (tail.len != 0) + crc = get_crc32(crc, (uchar *)tail.ptr, (uint32_t)tail.len); + + bool deflate = (deflate_hint && len > 0); + + if (deflate) + { + if (deflate_bytes(head, tail) == false) + { + deflate = false; + } + } + clen = (int)((deflate) ? deflated.size() : len); + add_to_jar_directory(fname, !deflate, modtime, len, clen, crc); + write_jar_header(fname, !deflate, modtime, len, clen, crc); + + if (deflate) + { + write_data(deflated.b); + } + else + { + write_data(head); + write_data(tail); + } +} + +// Add a ZIP entry for a directory name no data +void jar::addDirectoryToJarFile(const char *dir_name) +{ + bool store = true; + add_to_jar_directory((const char *)dir_name, store, default_modtime, 0, 0, 0); + write_jar_header((const char *)dir_name, store, default_modtime, 0, 0, 0); +} + +// Write out the central directory and close the jar file. +void jar::closeJarFile(bool central) +{ + if (jarfp) + { + fflush(jarfp); + if (central) + write_central_directory(); + fflush(jarfp); + fclose(jarfp); + } + reset(); +} + +/* Convert the date y/n/d and time h:m:s to a four byte DOS date and + * time (date in high two bytes, time in low two bytes allowing magnitude + * comparison). + */ +inline uint32_t jar::dostime(int y, int n, int d, int h, int m, int s) +{ + return y < 1980 ? dostime(1980, 1, 1, 0, 0, 0) + : (((uint32_t)y - 1980) << 25) | ((uint32_t)n << 21) | ((uint32_t)d << 16) | + ((uint32_t)h << 11) | ((uint32_t)m << 5) | ((uint32_t)s >> 1); +} +/* +#ifdef _REENTRANT // solaris +extern "C" struct tm *gmtime_r(const time_t *, struct tm *); +#else +#define gmtime_r(t, s) gmtime(t) +#endif +*/ +/* + * Return the Unix time in DOS format + */ +uint32_t jar::get_dostime(int modtime) +{ + // see defines.h + if (modtime != 0 && modtime == modtime_cache) + return dostime_cache; + if (modtime != 0 && default_modtime == 0) + default_modtime = modtime; // catch a reasonable default + time_t t = modtime; + struct tm sbuf; + (void)memset((void *)&sbuf, 0, sizeof(sbuf)); + struct tm *s = gmtime_r(&t, &sbuf); + modtime_cache = modtime; + dostime_cache = + dostime(s->tm_year + 1900, s->tm_mon + 1, s->tm_mday, s->tm_hour, s->tm_min, s->tm_sec); + // printf("modtime %d => %d\n", modtime_cache, dostime_cache); + return dostime_cache; +} + +/* Returns true on success, and will set the clen to the compressed + length, the caller should verify if true and clen less than the + input data +*/ +bool jar::deflate_bytes(bytes &head, bytes &tail) +{ + int len = (int)(head.len + tail.len); + + z_stream zs; + BYTES_OF(zs).clear(); + + // NOTE: the window size should always be -MAX_WBITS normally -15. + // unzip/zipup.c and java/Deflater.c + + int error = + deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); + if (error != Z_OK) + { + /* + switch (error) + { + case Z_MEM_ERROR: + PRINTCR((2, "Error: deflate error : Out of memory \n")); + break; + case Z_STREAM_ERROR: + PRINTCR((2, "Error: deflate error : Invalid compression level \n")); + break; + case Z_VERSION_ERROR: + PRINTCR((2, "Error: deflate error : Invalid version\n")); + break; + default: + PRINTCR((2, "Error: Internal deflate error error = %d\n", error)); + } + */ + return false; + } + + deflated.empty(); + zs.next_out = (uchar *)deflated.grow(len + (len / 2)); + zs.avail_out = (int)deflated.size(); + + zs.next_in = (uchar *)head.ptr; + zs.avail_in = (int)head.len; + + bytes *first = &head; + bytes *last = &tail; + if (last->len == 0) + { + first = nullptr; + last = &head; + } + else if (first->len == 0) + { + first = nullptr; + } + + if (first != nullptr && error == Z_OK) + { + zs.next_in = (uchar *)first->ptr; + zs.avail_in = (int)first->len; + error = deflate(&zs, Z_NO_FLUSH); + } + if (error == Z_OK) + { + zs.next_in = (uchar *)last->ptr; + zs.avail_in = (int)last->len; + error = deflate(&zs, Z_FINISH); + } + if (error == Z_STREAM_END) + { + if (len > (int)zs.total_out) + { + deflated.b.len = zs.total_out; + deflateEnd(&zs); + return true; + } + deflateEnd(&zs); + return false; + } + + deflateEnd(&zs); + return false; +} + +// Callback for fetching data from a GZIP input stream +static int64_t read_input_via_gzip(unpacker *u, void *buf, int64_t minlen, int64_t maxlen) +{ + assert(minlen <= maxlen); // don't talk nonsense + int64_t numread = 0; + char *bufptr = (char *)buf; + char *inbuf = u->gzin->inbuf; + size_t inbuflen = sizeof(u->gzin->inbuf); + unpacker::read_input_fn_t read_gzin_fn = (unpacker::read_input_fn_t)u->gzin->read_input_fn; + z_stream &zs = *(z_stream *)u->gzin->zstream; + while (numread < minlen) + { + int readlen = (1 << 16); // pretty arbitrary + if (readlen > (maxlen - numread)) + readlen = (int)(maxlen - numread); + zs.next_out = (uchar *)bufptr; + zs.avail_out = readlen; + if (zs.avail_in == 0) + { + zs.avail_in = (int)read_gzin_fn(u, inbuf, 1, inbuflen); + zs.next_in = (uchar *)inbuf; + } + int error = inflate(&zs, Z_NO_FLUSH); + if (error != Z_OK && error != Z_STREAM_END) + { + unpack_abort("error inflating input"); + break; + } + int nr = readlen - zs.avail_out; + numread += nr; + bufptr += nr; + assert(numread <= maxlen); + if (error == Z_STREAM_END) + { + enum + { + TRAILER_LEN = 8 + }; + // skip 8-byte trailer + if (zs.avail_in >= TRAILER_LEN) + { + zs.avail_in -= TRAILER_LEN; + } + else + { + // Bug: 5023768,we read past the TRAILER_LEN to see if there is + // any extraneous data, as we dont support concatenated .gz + // files just yet. + int extra = (int)read_gzin_fn(u, inbuf, 1, inbuflen); + zs.avail_in += extra - TRAILER_LEN; + } + // %%% should check final CRC and length here + // %%% should check for concatenated *.gz files here + if (zs.avail_in > 0) + unpack_abort("garbage after end of deflated input stream"); + // pop this filter off: + u->gzin->free(); + break; + } + } + + // fprintf(u->errstrm, "readInputFn(%d,%d) => %d (gunzip)\n", + // (int)minlen, (int)maxlen, (int)numread); + return numread; +} + +void gunzip::init(unpacker *u_) +{ + BYTES_OF(*this).clear(); + u = u_; + assert(u->gzin == nullptr); // once only, please + read_input_fn = (void *)u->read_input_fn; + zstream = NEW(z_stream, 1); + u->gzin = this; + u->read_input_fn = read_input_via_gzip; +} + +void gunzip::start(int magic) +{ + assert((magic & GZIP_MAGIC_MASK) == GZIP_MAGIC); + int gz_flg = (magic & 0xFF); // keep "flg", discard other 3 bytes + enum + { + FHCRC = (1 << 1), + FEXTRA = (1 << 2), + FNAME = (1 << 3), + FCOMMENT = (1 << 4) + }; + char gz_mtime[4]; + char gz_xfl[1]; + char gz_os[1]; + char gz_extra_len[2]; + char gz_hcrc[2]; + char gz_ignore; + // do not save extra, name, comment + read_fixed_field(gz_mtime, sizeof(gz_mtime)); + read_fixed_field(gz_xfl, sizeof(gz_xfl)); + read_fixed_field(gz_os, sizeof(gz_os)); + if (gz_flg & FEXTRA) + { + read_fixed_field(gz_extra_len, sizeof(gz_extra_len)); + int extra_len = gz_extra_len[0] & 0xFF; + extra_len += (gz_extra_len[1] & 0xFF) << 8; + for (; extra_len > 0; extra_len--) + { + read_fixed_field(&gz_ignore, 1); + } + } + int null_terms = 0; + if (gz_flg & FNAME) + null_terms++; + if (gz_flg & FCOMMENT) + null_terms++; + for (; null_terms; null_terms--) + { + for (;;) + { + gz_ignore = 0; + read_fixed_field(&gz_ignore, 1); + if (gz_ignore == 0) + break; + } + } + if (gz_flg & FHCRC) + read_fixed_field(gz_hcrc, sizeof(gz_hcrc)); + + // now the input stream is ready to read into the inflater + int error = inflateInit2((z_stream *)zstream, -MAX_WBITS); + if (error != Z_OK) + { + unpack_abort("cannot create input"); + } +} + +void gunzip::free() +{ + assert(u->gzin == this); + u->gzin = nullptr; + u->read_input_fn = (unpacker::read_input_fn_t) this->read_input_fn; + inflateEnd((z_stream *)zstream); + ::free(zstream); + zstream = nullptr; + ::free(this); +} + +void gunzip::read_fixed_field(char *buf, size_t buflen) +{ + int64_t nr = ((unpacker::read_input_fn_t)read_input_fn)(u, buf, buflen, buflen); + if ((size_t)nr != buflen) + unpack_abort("short stream header"); +} diff --git a/depends/pack200/src/zip.h b/depends/pack200/src/zip.h new file mode 100644 index 00000000..67ec24da --- /dev/null +++ b/depends/pack200/src/zip.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +#include <stdint.h> +typedef unsigned short ushort; +typedef unsigned int uint32_t; +typedef unsigned char uchar; + +struct unpacker; + +struct jar +{ + // JAR file writer + FILE *jarfp; + int default_modtime; + + // Used by unix2dostime: + int modtime_cache; + uint32_t dostime_cache; + + // Private members + fillbytes central_directory; + ushort central_directory_count; + uint32_t output_file_offset; + fillbytes deflated; // temporary buffer + + // pointer to outer unpacker, for error checks etc. + unpacker *u; + + // Public Methods + void openJarFile(const char *fname); + void addJarEntry(const char *fname, bool deflate_hint, int modtime, bytes &head, + bytes &tail); + void addDirectoryToJarFile(const char *dir_name); + void closeJarFile(bool central); + + void init(unpacker *u_); + + void free() + { + central_directory.free(); + deflated.free(); + } + + void reset() + { + free(); + init(u); + } + + // Private Methods + void write_data(void *ptr, int len); + void write_data(bytes &b) + { + write_data(b.ptr, (int)b.len); + } + void add_to_jar_directory(const char *fname, bool store, int modtime, int len, int clen, + uint32_t crc); + void write_jar_header(const char *fname, bool store, int modtime, int len, int clen, + unsigned int crc); + void write_central_directory(); + uint32_t dostime(int y, int n, int d, int h, int m, int s); + uint32_t get_dostime(int modtime); + + // The definitions of these depend on the NO_ZLIB option: + bool deflate_bytes(bytes &head, bytes &tail); + static uint32_t get_crc32(uint32_t c, unsigned char *ptr, uint32_t len); +}; + +struct gunzip +{ + // optional gzip input stream control block + + // pointer to outer unpacker, for error checks etc. + unpacker *u; + + void *read_input_fn; // underlying \bchar\b stream + void *zstream; // inflater state + char inbuf[1 << 14]; // input buffer + + void init(unpacker *u_); // pushes new value on u->read_input_fn + + void free(); + + void start(int magic); + + // private stuff + void read_fixed_field(char *buf, size_t buflen); +}; diff --git a/depends/quazip/CMakeLists.txt b/depends/quazip/CMakeLists.txt new file mode 100644 index 00000000..76da0a59 --- /dev/null +++ b/depends/quazip/CMakeLists.txt @@ -0,0 +1,42 @@ +project(quazip) + +# Find ZLIB for quazip +# Use system zlib on unix and Qt ZLIB on Windows +IF(UNIX) + find_package(ZLIB REQUIRED) +ELSE(UNIX) + get_filename_component (ZLIB_FOUND_DIR "${Qt5Core_DIR}/../../../include/QtZlib" ABSOLUTE) + SET(ZLIB_INCLUDE_DIRS ${ZLIB_FOUND_DIR} CACHE PATH "Path to ZLIB headers of Qt") + SET(ZLIB_LIBRARIES "") + IF(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h") + MESSAGE("Please specify a valid zlib include dir") + ENDIF(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h") +ENDIF(UNIX) + +# set all include directories for in and out of source builds +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${ZLIB_INCLUDE_DIRS} +) + +# include with QT_USE selected library parts +# INCLUDE(${QT_USE_FILE}) + +file(GLOB SRCS "*.c" "*.cpp") +file(GLOB PUBLIC_HEADERS "*.h") + +# Static link! +ADD_DEFINITIONS(-DQUAZIP_STATIC) + +#qt5_wrap_cpp(MOC_SRCS ${PUBLIC_HEADERS}) +#set(SRCS ${SRCS} ${MOC_SRCS}) + +#set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +add_library(quazip STATIC ${SRCS}) +QT5_USE_MODULES(quazip Core) +target_link_libraries(quazip ${ZLIB_LIBRARIES}) + +#install(FILES ${PUBLIC_HEADERS} DESTINATION include/quazip) +#install(TARGETS quazip LIBRARY DESTINATION ${LIB_DESTINATION} ARCHIVE DESTINATION ${LIB_DESTINATION} RUNTIME DESTINATION ${LIB_DESTINATION}) diff --git a/depends/quazip/JlCompress.cpp b/depends/quazip/JlCompress.cpp new file mode 100644 index 00000000..a6cf0eaf --- /dev/null +++ b/depends/quazip/JlCompress.cpp @@ -0,0 +1,522 @@ +#include "JlCompress.h" +#include <QDebug> + +bool JlCompress::copyData(QIODevice &inFile, QIODevice &outFile) +{ + while (!inFile.atEnd()) { + char buf[4096]; + qint64 readLen = inFile.read(buf, 4096); + if (readLen <= 0) + return false; + if (outFile.write(buf, readLen) != readLen) + return false; + } + return true; +} + +/**OK + * Comprime il file fileName, nell'oggetto zip, con il nome fileDest. + * + * La funzione fallisce se: + * * zip==NULL; + * * l'oggetto zip e stato aperto in una modalita non compatibile con l'aggiunta di file; + * * non e possibile aprire il file d'origine; + * * non e possibile creare il file all'interno dell'oggetto zip; + * * si e rilevato un errore nella copia dei dati; + * * non e stato possibile chiudere il file all'interno dell'oggetto zip; + */ +bool JlCompress::compressFile(QuaZip* zip, QString fileName, QString fileDest) { + // zip: oggetto dove aggiungere il file + // fileName: nome del file reale + // fileDest: nome del file all'interno del file compresso + + // Controllo l'apertura dello zip + if (!zip) return false; + if (zip->getMode()!=QuaZip::mdCreate && + zip->getMode()!=QuaZip::mdAppend && + zip->getMode()!=QuaZip::mdAdd) return false; + + // Apro il file originale + QFile inFile; + inFile.setFileName(fileName); + if(!inFile.open(QIODevice::ReadOnly)) return false; + + // Apro il file risulato + QuaZipFile outFile(zip); + if(!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, inFile.fileName()))) return false; + + // Copio i dati + if (!copyData(inFile, outFile) || outFile.getZipError()!=UNZ_OK) { + return false; + } + + // Chiudo i file + outFile.close(); + if (outFile.getZipError()!=UNZ_OK) return false; + inFile.close(); + + return true; +} + +/**OK + * Comprime la cartella dir nel file fileCompressed, se recursive e true allora + * comprime anche le sotto cartelle. I nomi dei file preceduti dal path creato + * togliendo il pat della cartella origDir al path della cartella dir. + * Se la funzione fallisce restituisce false e cancella il file che si e tentato + * di creare. + * + * La funzione fallisce se: + * * zip==NULL; + * * l'oggetto zip e stato aperto in una modalita non compatibile con l'aggiunta di file; + * * la cartella dir non esiste; + * * la compressione di una sotto cartella fallisce (1); + * * la compressione di un file fallisce; + * (1) La funzione si richiama in maniera ricorsiva per comprimere le sotto cartelle + * dunque gli errori di compressione di una sotto cartella sono gli stessi di questa + * funzione. + */ +bool JlCompress::compressSubDir( QuaZip* parentZip, QString dir, QString parentDir, bool recursive, QSet<QString>& added ) +{ + // zip: oggetto dove aggiungere il file + // dir: cartella reale corrente + // origDir: cartella reale originale + // (path(dir)-path(origDir)) = path interno all'oggetto zip + + // Controllo l'apertura dello zip + if (!parentZip ) return false; + if ( parentZip->getMode()!=QuaZip::mdCreate && + parentZip->getMode()!=QuaZip::mdAppend && + parentZip->getMode()!=QuaZip::mdAdd) return false; + + // Controllo la cartella + QDir directory(dir); + if (!directory.exists()) return false; + + // Se comprimo anche le sotto cartelle + if (recursive) { + // Per ogni sotto cartella + QFileInfoList files = directory.entryInfoList(QDir::AllDirs|QDir::NoDotAndDotDot); + Q_FOREACH (QFileInfo file, files) + { + // Comprimo la sotto cartella + if(!compressSubDir( parentZip,file.absoluteFilePath(),parentDir,recursive,added)) return false; + } + } + + // Per ogni file nella cartella + QFileInfoList files = directory.entryInfoList(QDir::Files); + QDir origDirectory( parentDir ); + Q_FOREACH (QFileInfo file, files) + { + // Se non e un file o e il file compresso che sto creando + if(!file.isFile()||file.absoluteFilePath()==parentZip->getZipName()) continue; + + // Creo il nome relativo da usare all'interno del file compresso + QString filename = origDirectory.relativeFilePath(file.absoluteFilePath()); + + // Comprimo il file + if (!compressFile( parentZip,file.absoluteFilePath(),filename)) + return false; + added.insert(filename); + } + + return true; +} + +/**OK + * Estrae il file fileName, contenuto nell'oggetto zip, con il nome fileDest. + * Se la funzione fallisce restituisce false e cancella il file che si e tentato di estrarre. + * + * La funzione fallisce se: + * * zip==NULL; + * * l'oggetto zip e stato aperto in una modalita non compatibile con l'estrazione di file; + * * non e possibile aprire il file all'interno dell'oggetto zip; + * * non e possibile creare il file estratto; + * * si e rilevato un errore nella copia dei dati (1); + * * non e stato possibile chiudere il file all'interno dell'oggetto zip (1); + * + * (1): prima di uscire dalla funzione cancella il file estratto. + */ +bool JlCompress::extractFile(QuaZip* zip, QString fileName, QString fileDest) { + // zip: oggetto dove aggiungere il file + // filename: nome del file reale + // fileincompress: nome del file all'interno del file compresso + + // Controllo l'apertura dello zip + if (!zip) return false; + if (zip->getMode()!=QuaZip::mdUnzip) return false; + + // Apro il file compresso + if (!fileName.isEmpty()) + zip->setCurrentFile(fileName); + QuaZipFile inFile(zip); + if(!inFile.open(QIODevice::ReadOnly) || inFile.getZipError()!=UNZ_OK) return false; + + // Controllo esistenza cartella file risultato + QDir curDir; + if (!curDir.mkpath(QFileInfo(fileDest).absolutePath())) { + return false; + } + + if (QFileInfo(fileDest).isDir()) + return true; + + // Apro il file risultato + QFile outFile; + outFile.setFileName(fileDest); + if(!outFile.open(QIODevice::WriteOnly)) return false; + + // Copio i dati + if (!copyData(inFile, outFile) || inFile.getZipError()!=UNZ_OK) { + outFile.close(); + removeFile(QStringList(fileDest)); + return false; + } + outFile.close(); + + // Chiudo i file + inFile.close(); + if (inFile.getZipError()!=UNZ_OK) { + removeFile(QStringList(fileDest)); + return false; + } + + return true; +} + +/** + * Rimuove i file il cui nome e specificato all'interno di listFile. + * Restituisce true se tutti i file sono stati cancellati correttamente, attenzione + * perche puo restituire false anche se alcuni file non esistevano e si e tentato + * di cancellarli. + */ +bool JlCompress::removeFile(QStringList listFile) { + bool ret = true; + // Per ogni file + for (int i=0; i<listFile.count(); i++) { + // Lo elimino + ret = ret && QFile::remove(listFile.at(i)); + } + return ret; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/**OK + * Comprime il file fileName nel file fileCompressed. + * Se la funzione fallisce restituisce false e cancella il file che si e tentato + * di creare. + * + * La funzione fallisce se: + * * non si riesce ad aprire l'oggetto zip; + * * la compressione del file fallisce; + * * non si riesce a chiudere l'oggetto zip; + */ +bool JlCompress::compressFile(QString fileCompressed, QString file) { + // Creo lo zip + QuaZip zip(fileCompressed); + QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); + if(!zip.open(QuaZip::mdCreate)) { + QFile::remove(fileCompressed); + return false; + } + + // Aggiungo il file + if (!compressFile(&zip,file,QFileInfo(file).fileName())) { + QFile::remove(fileCompressed); + return false; + } + + // Chiudo il file zip + zip.close(); + if(zip.getZipError()!=0) { + QFile::remove(fileCompressed); + return false; + } + + return true; +} + +/**OK + * Comprime i file specificati in files nel file fileCompressed. + * Se la funzione fallisce restituisce false e cancella il file che si e tentato + * di creare. + * + * La funzione fallisce se: + * * non si riesce ad aprire l'oggetto zip; + * * la compressione di un file fallisce; + * * non si riesce a chiudere l'oggetto zip; + */ +bool JlCompress::compressFiles(QString fileCompressed, QStringList files) { + // Creo lo zip + QuaZip zip(fileCompressed); + QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); + if(!zip.open(QuaZip::mdCreate)) { + QFile::remove(fileCompressed); + return false; + } + + // Comprimo i file + QFileInfo info; + Q_FOREACH (QString file, files) { + info.setFile(file); + if (!info.exists() || !compressFile(&zip,file,info.fileName())) { + QFile::remove(fileCompressed); + return false; + } + } + + // Chiudo il file zip + zip.close(); + if(zip.getZipError()!=0) { + QFile::remove(fileCompressed); + return false; + } + + return true; +} + +/**OK + * Comprime la cartella dir nel file fileCompressed, se recursive e true allora + * comprime anche le sotto cartelle. + * Se la funzione fallisce restituisce false e cancella il file che si e tentato + * di creare. + * + * La funzione fallisce se: + * * non si riesce ad aprire l'oggetto zip; + * * la compressione di un file fallisce; + * * non si riesce a chiudere l'oggetto zip; + */ +bool JlCompress::compressDir(QString fileCompressed, QString dir, bool recursive) { + // Creo lo zip + QuaZip zip(fileCompressed); + QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); + if(!zip.open(QuaZip::mdCreate)) { + QFile::remove(fileCompressed); + return false; + } + QSet<QString> added; + // Aggiungo i file e le sotto cartelle + if (!compressSubDir(&zip,dir,dir,recursive, added)) + { + QFile::remove(fileCompressed); + return false; + } + + // Chiudo il file zip + zip.close(); + if(zip.getZipError()!=0) { + QFile::remove(fileCompressed); + return false; + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/**OK + * Estrae il file fileName, contenuto nel file fileCompressed, con il nome fileDest. + * Se fileDest = "" allora il file viene estratto con lo stesso nome con cui e + * stato compresso. + * Se la funzione fallisce cancella il file che si e tentato di estrarre. + * Restituisce il nome assoluto del file estratto. + * + * La funzione fallisce se: + * * non si riesce ad aprire l'oggetto zip; + * * l'estrazione del file fallisce; + * * non si riesce a chiudere l'oggetto zip; + */ +QString JlCompress::extractFile(QString fileCompressed, QString fileName, QString fileDest) { + // Apro lo zip + QuaZip zip(fileCompressed); + if(!zip.open(QuaZip::mdUnzip)) { + return QString(); + } + + // Estraggo il file + if (fileDest.isEmpty()) + fileDest = fileName; + if (!extractFile(&zip,fileName,fileDest)) { + return QString(); + } + + // Chiudo il file zip + zip.close(); + if(zip.getZipError()!=0) { + removeFile(QStringList(fileDest)); + return QString(); + } + return QFileInfo(fileDest).absoluteFilePath(); +} + +/**OK + * Estrae i file specificati in files, contenuti nel file fileCompressed, nella + * cartella dir. La struttura a cartelle del file compresso viene rispettata. + * Se dir = "" allora il file viene estratto nella cartella corrente. + * Se la funzione fallisce cancella i file che si e tentato di estrarre. + * Restituisce i nomi assoluti dei file estratti. + * + * La funzione fallisce se: + * * non si riesce ad aprire l'oggetto zip; + * * l'estrazione di un file fallisce; + * * non si riesce a chiudere l'oggetto zip; + */ +QStringList JlCompress::extractFiles(QString fileCompressed, QStringList files, QString dir) { + // Creo lo zip + QuaZip zip(fileCompressed); + if(!zip.open(QuaZip::mdUnzip)) { + return QStringList(); + } + + // Estraggo i file + QStringList extracted; + for (int i=0; i<files.count(); i++) { + QString absPath = QDir(dir).absoluteFilePath(files.at(i)); + if (!extractFile(&zip, files.at(i), absPath)) { + removeFile(extracted); + return QStringList(); + } + extracted.append(absPath); + } + + // Chiudo il file zip + zip.close(); + if(zip.getZipError()!=0) { + removeFile(extracted); + return QStringList(); + } + + return extracted; +} + +QStringList JlCompress::extractWithExceptions(QString fileCompressed, QString dir, QStringList exceptions) +{ + QuaZip zip(fileCompressed); + if(!zip.open(QuaZip::mdUnzip)) + { + return QStringList(); + } + + QDir directory(dir); + QStringList extracted; + if (!zip.goToFirstFile()) + { + return QStringList(); + } + do + { + QString name = zip.getCurrentFileName(); + bool ok = true; + for(auto str: exceptions) + { + if(name.startsWith(str)) + { + ok = false; + break; + } + } + if(!ok) + continue; + QString absFilePath = directory.absoluteFilePath(name); + if (!JlCompress::extractFile(&zip, "", absFilePath)) + { + JlCompress::removeFile(extracted); + return QStringList(); + } + extracted.append(absFilePath); + } while (zip.goToNextFile()); + + zip.close(); + if(zip.getZipError()!=0) + { + JlCompress::removeFile(extracted); + return QStringList(); + } + + return extracted; +} + +/**OK + * Estrae il file fileCompressed nella cartella dir. + * Se dir = "" allora il file viene estratto nella cartella corrente. + * Se la funzione fallisce cancella i file che si e tentato di estrarre. + * Restituisce i nomi assoluti dei file estratti. + * + * La funzione fallisce se: + * * non si riesce ad aprire l'oggetto zip; + * * la compressione di un file fallisce; + * * non si riesce a chiudere l'oggetto zip; + */ +QStringList JlCompress::extractDir(QString fileCompressed, QString dir) { + // Apro lo zip + QuaZip zip(fileCompressed); + if(!zip.open(QuaZip::mdUnzip)) { + return QStringList(); + } + + QDir directory(dir); + QStringList extracted; + if (!zip.goToFirstFile()) { + return QStringList(); + } + do { + QString name = zip.getCurrentFileName(); + QString absFilePath = directory.absoluteFilePath(name); + if (!extractFile(&zip, "", absFilePath)) { + removeFile(extracted); + return QStringList(); + } + extracted.append(absFilePath); + } while (zip.goToNextFile()); + + // Chiudo il file zip + zip.close(); + if(zip.getZipError()!=0) { + removeFile(extracted); + return QStringList(); + } + + return extracted; +} + +/**OK + * Restituisce la lista dei file resenti nel file compresso fileCompressed. + * Se la funzione fallisce, restituisce un elenco vuoto. + * + * La funzione fallisce se: + * * non si riesce ad aprire l'oggetto zip; + * * la richiesta di informazioni di un file fallisce; + * * non si riesce a chiudere l'oggetto zip; + */ +QStringList JlCompress::getFileList(QString fileCompressed) { + // Apro lo zip + QuaZip* zip = new QuaZip(QFileInfo(fileCompressed).absoluteFilePath()); + if(!zip->open(QuaZip::mdUnzip)) { + delete zip; + return QStringList(); + } + + // Estraggo i nomi dei file + QStringList lst; + QuaZipFileInfo info; + for(bool more=zip->goToFirstFile(); more; more=zip->goToNextFile()) { + if(!zip->getCurrentFileInfo(&info)) { + delete zip; + return QStringList(); + } + lst << info.name; + //info.name.toLocal8Bit().constData() + } + + // Chiudo il file zip + zip->close(); + if(zip->getZipError()!=0) { + delete zip; + return QStringList(); + } + delete zip; + + return lst; +} + diff --git a/depends/quazip/JlCompress.h b/depends/quazip/JlCompress.h new file mode 100644 index 00000000..3ee8c25a --- /dev/null +++ b/depends/quazip/JlCompress.h @@ -0,0 +1,125 @@ +#ifndef JLCOMPRESSFOLDER_H_ +#define JLCOMPRESSFOLDER_H_ + +#include "quazip.h" +#include "quazipfile.h" +#include "quazipfileinfo.h" +#include <QString> +#include <QDir> +#include <QFileInfo> +#include <QFile> + +/// Utility class for typical operations. +/** + This class contains a number of useful static functions to perform + simple operations, such as mass ZIP packing or extraction. + */ +class QUAZIP_EXPORT JlCompress { +private: + /// Remove some files. + /** + \param listFile The list of files to remove. + \return true if success, false otherwise. + */ + static bool removeFile(QStringList listFile); +public: + /// Compress a single file. + /** + \param zip Opened zip to compress the file to. + \param fileName The full path to the source file. + \param fileDest The full name of the file inside the archive. + \return true if success, false otherwise. + */ + static bool compressFile(QuaZip* zip, QString fileName, QString fileDest); + /// Compress a subdirectory. + /** + \param parentZip Opened zip containing the parent directory. + \param dir The full path to the directory to pack. + \param parentDir The full path to the directory corresponding to + the root of the ZIP. + \param recursive Whether to pack sub-directories as well or only + files. + \return true if success, false otherwise. + */ + static bool compressSubDir( QuaZip* parentZip, QString dir, QString parentDir, bool recursive, QSet< QString >& added ); + /// Extract a single file. + /** + \param zip The opened zip archive to extract from. + \param fileName The full name of the file to extract. + \param fileDest The full path to the destination file. + \return true if success, false otherwise. + */ + static bool extractFile(QuaZip* zip, QString fileName, QString fileDest); + + /// copy data from inFile to outFile + static bool copyData(QIODevice &inFile, QIODevice &outFile); + /// Compress a single file. + /** + \param fileCompressed The name of the archive. + \param file The file to compress. + \return true if success, false otherwise. + */ + static bool compressFile(QString fileCompressed, QString file); + /// Compress a list of files. + /** + \param fileCompressed The name of the archive. + \param files The file list to compress. + \return true if success, false otherwise. + */ + static bool compressFiles(QString fileCompressed, QStringList files); + /// Compress a whole directory. + /** + \param fileCompressed The name of the archive. + \param dir The directory to compress. + \param recursive Whether to pack the subdirectories as well, or + just regular files. + \return true if success, false otherwise. + */ + static bool compressDir(QString fileCompressed, QString dir = QString(), bool recursive = true); + +public: + /// Extract a single file. + /** + \param fileCompressed The name of the archive. + \param fileName The file to extract. + \param fileDest The destination file, assumed to be identical to + \a file if left empty. + \return The list of the full paths of the files extracted, empty on failure. + */ + static QString extractFile(QString fileCompressed, QString fileName, QString fileDest = QString()); + /// Extract a list of files. + /** + \param fileCompressed The name of the archive. + \param files The file list to extract. + \param dir The directory to put the files to, the current + directory if left empty. + \return The list of the full paths of the files extracted, empty on failure. + */ + static QStringList extractFiles(QString fileCompressed, QStringList files, QString dir = QString()); + /// Extract a whole archive. + /** + \param fileCompressed The name of the archive. + \param dir The directory to extract to, the current directory if + left empty. + \return The list of the full paths of the files extracted, empty on failure. + */ + static QStringList extractDir(QString fileCompressed, QString dir = QString()); + /// Extract a whole archive, with a list of exceptions (prefixes to ignore). + /** + \param fileCompressed The name of the archive. + \param dir The directory to extract to, the current directory if + left empty. + \param exceptions The list of exception prefixes + \return The list of the full paths of the files extracted, empty on failure. + */ + static QStringList extractWithExceptions(QString fileCompressed, QString dir, QStringList exceptions); + /// Get the file list. + /** + \return The list of the files in the archive, or, more precisely, the + list of the entries, including both files and directories if they + are present separately. + */ + static QStringList getFileList(QString fileCompressed); +}; + +#endif /* JLCOMPRESSFOLDER_H_ */ diff --git a/depends/quazip/crypt.h b/depends/quazip/crypt.h new file mode 100644 index 00000000..1d6da628 --- /dev/null +++ b/depends/quazip/crypt.h @@ -0,0 +1,135 @@ +/* crypt.h -- base code for crypt/uncrypt ZIPfile + + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + This code is a modified version of crypting code in Infozip distribution + + The encryption/decryption parts of this source code (as opposed to the + non-echoing password parts) were originally written in Europe. The + whole source package can be freely distributed, including from the USA. + (Prior to January 2000, re-export from the US was a violation of US law.) + + This encryption code is a direct transcription of the algorithm from + Roger Schlafly, described by Phil Katz in the file appnote.txt. This + file (appnote.txt) is distributed with the PKZIP program (even in the + version without encryption capabilities). + + If you don't need crypting in your application, just define symbols + NOCRYPT and NOUNCRYPT. + + This code support the "Traditional PKWARE Encryption". + + The new AES encryption added on Zip format by Winzip (see the page + http://www.winzip.com/aes_info.htm ) and PKWare PKZip 5.x Strong + Encryption is not supported. +*/ + +#include "quazip_global.h" + +#define CRC32(c, b) ((*(pcrc_32_tab+(((int)(c) ^ (b)) & 0xff))) ^ ((c) >> 8)) + +/*********************************************************************** + * Return the next byte in the pseudo-random sequence + */ +static int decrypt_byte(unsigned long* pkeys, const unsigned long* pcrc_32_tab UNUSED) +{ + //(void) pcrc_32_tab; /* avoid "unused parameter" warning */ + unsigned temp; /* POTENTIAL BUG: temp*(temp^1) may overflow in an + * unpredictable manner on 16-bit systems; not a problem + * with any known compiler so far, though */ + + temp = ((unsigned)(*(pkeys+2)) & 0xffff) | 2; + return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); +} + +/*********************************************************************** + * Update the encryption keys with the next byte of plain text + */ +static int update_keys(unsigned long* pkeys,const unsigned long* pcrc_32_tab,int c) +{ + (*(pkeys+0)) = CRC32((*(pkeys+0)), c); + (*(pkeys+1)) += (*(pkeys+0)) & 0xff; + (*(pkeys+1)) = (*(pkeys+1)) * 134775813L + 1; + { + register int keyshift = (int)((*(pkeys+1)) >> 24); + (*(pkeys+2)) = CRC32((*(pkeys+2)), keyshift); + } + return c; +} + + +/*********************************************************************** + * Initialize the encryption keys and the random header according to + * the given password. + */ +static void init_keys(const char* passwd,unsigned long* pkeys,const unsigned long* pcrc_32_tab) +{ + *(pkeys+0) = 305419896L; + *(pkeys+1) = 591751049L; + *(pkeys+2) = 878082192L; + while (*passwd != '\0') { + update_keys(pkeys,pcrc_32_tab,(int)*passwd); + passwd++; + } +} + +#define zdecode(pkeys,pcrc_32_tab,c) \ + (update_keys(pkeys,pcrc_32_tab,c ^= decrypt_byte(pkeys,pcrc_32_tab))) + +#define zencode(pkeys,pcrc_32_tab,c,t) \ + (t=decrypt_byte(pkeys,pcrc_32_tab), update_keys(pkeys,pcrc_32_tab,c), t^(c)) + +#ifdef INCLUDECRYPTINGCODE_IFCRYPTALLOWED + +#define RAND_HEAD_LEN 12 + /* "last resort" source for second part of crypt seed pattern */ +# ifndef ZCR_SEED2 +# define ZCR_SEED2 3141592654UL /* use PI as default pattern */ +# endif + +static int crypthead(passwd, buf, bufSize, pkeys, pcrc_32_tab, crcForCrypting) + const char *passwd; /* password string */ + unsigned char *buf; /* where to write header */ + int bufSize; + unsigned long* pkeys; + const unsigned long* pcrc_32_tab; + unsigned long crcForCrypting; +{ + int n; /* index in random header */ + int t; /* temporary */ + int c; /* random byte */ + unsigned char header[RAND_HEAD_LEN-2]; /* random header */ + static unsigned calls = 0; /* ensure different random header each time */ + + if (bufSize<RAND_HEAD_LEN) + return 0; + + /* First generate RAND_HEAD_LEN-2 random bytes. We encrypt the + * output of rand() to get less predictability, since rand() is + * often poorly implemented. + */ + if (++calls == 1) + { + srand((unsigned)(time(NULL) ^ ZCR_SEED2)); + } + init_keys(passwd, pkeys, pcrc_32_tab); + for (n = 0; n < RAND_HEAD_LEN-2; n++) + { + c = (rand() >> 7) & 0xff; + header[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, c, t); + } + /* Encrypt random header (last two bytes is high word of crc) */ + init_keys(passwd, pkeys, pcrc_32_tab); + for (n = 0; n < RAND_HEAD_LEN-2; n++) + { + buf[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, header[n], t); + } + buf[n++] = zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t); + buf[n++] = zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t); + return n; +} + +#endif diff --git a/depends/quazip/ioapi.h b/depends/quazip/ioapi.h new file mode 100644 index 00000000..f4c21809 --- /dev/null +++ b/depends/quazip/ioapi.h @@ -0,0 +1,77 @@ +/* ioapi.h -- IO base function header for compress/uncompress .zip + files using zlib + zip or unzip API + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + Modified by Sergey A. Tachenov to integrate with Qt. +*/ + +#ifndef _ZLIBIOAPI_H +#define _ZLIBIOAPI_H + + +#define ZLIB_FILEFUNC_SEEK_CUR (1) +#define ZLIB_FILEFUNC_SEEK_END (2) +#define ZLIB_FILEFUNC_SEEK_SET (0) + +#define ZLIB_FILEFUNC_MODE_READ (1) +#define ZLIB_FILEFUNC_MODE_WRITE (2) +#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3) + +#define ZLIB_FILEFUNC_MODE_EXISTING (4) +#define ZLIB_FILEFUNC_MODE_CREATE (8) + + +#ifndef ZCALLBACK + +#if (defined(WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK) +#define ZCALLBACK CALLBACK +#else +#define ZCALLBACK +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef voidpf (ZCALLBACK *open_file_func) OF((voidpf opaque, voidpf file, int mode)); +typedef uLong (ZCALLBACK *read_file_func) OF((voidpf opaque, voidpf stream, void* buf, uLong size)); +typedef uLong (ZCALLBACK *write_file_func) OF((voidpf opaque, voidpf stream, const void* buf, uLong size)); +typedef uLong (ZCALLBACK *tell_file_func) OF((voidpf opaque, voidpf stream)); +typedef int (ZCALLBACK *seek_file_func) OF((voidpf opaque, voidpf stream, uLong offset, int origin)); +typedef int (ZCALLBACK *close_file_func) OF((voidpf opaque, voidpf stream)); +typedef int (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream)); + +typedef struct zlib_filefunc_def_s +{ + open_file_func zopen_file; + read_file_func zread_file; + write_file_func zwrite_file; + tell_file_func ztell_file; + seek_file_func zseek_file; + close_file_func zclose_file; + testerror_file_func zerror_file; + voidpf opaque; +} zlib_filefunc_def; + + + +void fill_qiodevice_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def)); + +#define ZREAD(filefunc,filestream,buf,size) ((*((filefunc).zread_file))((filefunc).opaque,filestream,buf,size)) +#define ZWRITE(filefunc,filestream,buf,size) ((*((filefunc).zwrite_file))((filefunc).opaque,filestream,buf,size)) +#define ZTELL(filefunc,filestream) ((*((filefunc).ztell_file))((filefunc).opaque,filestream)) +#define ZSEEK(filefunc,filestream,pos,mode) ((*((filefunc).zseek_file))((filefunc).opaque,filestream,pos,mode)) +#define ZCLOSE(filefunc,filestream) ((*((filefunc).zclose_file))((filefunc).opaque,filestream)) +#define ZERROR(filefunc,filestream) ((*((filefunc).zerror_file))((filefunc).opaque,filestream)) + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/depends/quazip/qioapi.cpp b/depends/quazip/qioapi.cpp new file mode 100644 index 00000000..f254c34d --- /dev/null +++ b/depends/quazip/qioapi.cpp @@ -0,0 +1,146 @@ +/* ioapi.c -- IO base function header for compress/uncompress .zip + files using zlib + zip or unzip API + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + Modified by Sergey A. Tachenov to integrate with Qt. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "zlib.h" +#include "ioapi.h" +#include "quazip_global.h" +#include <QIODevice> + + +/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */ + +#ifndef SEEK_CUR +#define SEEK_CUR 1 +#endif + +#ifndef SEEK_END +#define SEEK_END 2 +#endif + +#ifndef SEEK_SET +#define SEEK_SET 0 +#endif + +voidpf ZCALLBACK qiodevice_open_file_func ( + voidpf opaque UNUSED, + voidpf file, + int mode) +{ + QIODevice *iodevice = reinterpret_cast<QIODevice*>(file); + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) + iodevice->open(QIODevice::ReadOnly); + else + if (mode & ZLIB_FILEFUNC_MODE_EXISTING) + iodevice->open(QIODevice::ReadWrite); + else + if (mode & ZLIB_FILEFUNC_MODE_CREATE) + iodevice->open(QIODevice::WriteOnly); + + if (iodevice->isOpen()) { + if (iodevice->isSequential()) { + iodevice->close(); + return NULL; + } else { + return iodevice; + } + } else + return NULL; +} + + +uLong ZCALLBACK qiodevice_read_file_func ( + voidpf opaque UNUSED, + voidpf stream, + void* buf, + uLong size) +{ + uLong ret; + ret = (uLong)((QIODevice*)stream)->read((char*)buf,size); + return ret; +} + + +uLong ZCALLBACK qiodevice_write_file_func ( + voidpf opaque UNUSED, + voidpf stream, + const void* buf, + uLong size) +{ + uLong ret; + ret = (uLong)((QIODevice*)stream)->write((char*)buf,size); + return ret; +} + +uLong ZCALLBACK qiodevice_tell_file_func ( + voidpf opaque UNUSED, + voidpf stream) +{ + uLong ret; + ret = ((QIODevice*)stream)->pos(); + return ret; +} + +int ZCALLBACK qiodevice_seek_file_func ( + voidpf opaque UNUSED, + voidpf stream, + uLong offset, + int origin) +{ + uLong qiodevice_seek_result=0; + int ret; + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR : + qiodevice_seek_result = ((QIODevice*)stream)->pos() + offset; + break; + case ZLIB_FILEFUNC_SEEK_END : + qiodevice_seek_result = ((QIODevice*)stream)->size() - offset; + break; + case ZLIB_FILEFUNC_SEEK_SET : + qiodevice_seek_result = offset; + break; + default: return -1; + } + ret = !((QIODevice*)stream)->seek(qiodevice_seek_result); + return ret; +} + +int ZCALLBACK qiodevice_close_file_func ( + voidpf opaque UNUSED, + voidpf stream) +{ + ((QIODevice*)stream)->close(); + return 0; +} + +int ZCALLBACK qiodevice_error_file_func ( + voidpf opaque UNUSED, + voidpf stream) +{ + // can't check for error due to the QIODevice API limitation + return 0; +} + +void fill_qiodevice_filefunc ( + zlib_filefunc_def* pzlib_filefunc_def) +{ + pzlib_filefunc_def->zopen_file = qiodevice_open_file_func; + pzlib_filefunc_def->zread_file = qiodevice_read_file_func; + pzlib_filefunc_def->zwrite_file = qiodevice_write_file_func; + pzlib_filefunc_def->ztell_file = qiodevice_tell_file_func; + pzlib_filefunc_def->zseek_file = qiodevice_seek_file_func; + pzlib_filefunc_def->zclose_file = qiodevice_close_file_func; + pzlib_filefunc_def->zerror_file = qiodevice_error_file_func; + pzlib_filefunc_def->opaque = NULL; +} diff --git a/depends/quazip/quaadler32.cpp b/depends/quazip/quaadler32.cpp new file mode 100644 index 00000000..097899f6 --- /dev/null +++ b/depends/quazip/quaadler32.cpp @@ -0,0 +1,28 @@ +#include "quaadler32.h" + +#include "zlib.h" + +QuaAdler32::QuaAdler32() +{ + reset(); +} + +quint32 QuaAdler32::calculate(const QByteArray &data) +{ + return adler32( adler32(0L, Z_NULL, 0), (const Bytef*)data.data(), data.size() ); +} + +void QuaAdler32::reset() +{ + checksum = adler32(0L, Z_NULL, 0); +} + +void QuaAdler32::update(const QByteArray &buf) +{ + checksum = adler32( checksum, (const Bytef*)buf.data(), buf.size() ); +} + +quint32 QuaAdler32::value() +{ + return checksum; +} diff --git a/depends/quazip/quaadler32.h b/depends/quazip/quaadler32.h new file mode 100644 index 00000000..c5ac0532 --- /dev/null +++ b/depends/quazip/quaadler32.h @@ -0,0 +1,29 @@ +#ifndef QUAADLER32_H +#define QUAADLER32_H + +#include <QtCore/QByteArray> + +#include "quachecksum32.h" + +/// Adler32 checksum +/** \class QuaAdler32 quaadler32.h <quazip/quaadler32.h> + * This class wrappers the adler32 function with the QuaChecksum32 interface. + * See QuaChecksum32 for more info. + */ +class QUAZIP_EXPORT QuaAdler32 : public QuaChecksum32 +{ + +public: + QuaAdler32(); + + quint32 calculate(const QByteArray &data); + + void reset(); + void update(const QByteArray &buf); + quint32 value(); + +private: + quint32 checksum; +}; + +#endif //QUAADLER32_H diff --git a/depends/quazip/quachecksum32.h b/depends/quazip/quachecksum32.h new file mode 100644 index 00000000..773ec2a4 --- /dev/null +++ b/depends/quazip/quachecksum32.h @@ -0,0 +1,54 @@ +#ifndef QUACHECKSUM32_H +#define QUACHECKSUM32_H + +#include <QtCore/QByteArray> +#include "quazip_global.h" + +/// Checksum interface. +/** \class QuaChecksum32 quachecksum32.h <quazip/quachecksum32.h> + * This is an interface for 32 bit checksums. + * Classes implementing this interface can calcunate a certin + * checksum in a single step: + * \code + * QChecksum32 *crc32 = new QuaCrc32(); + * rasoult = crc32->calculate(data); + * \endcode + * or by streaming the data: + * \code + * QChecksum32 *crc32 = new QuaCrc32(); + * while(!fileA.atEnd()) + * crc32->update(fileA.read(bufSize)); + * resoultA = crc32->value(); + * crc32->reset(); + * while(!fileB.atEnd()) + * crc32->update(fileB.read(bufSize)); + * resoultB = crc32->value(); + * \endcode + */ +class QUAZIP_EXPORT QuaChecksum32 +{ + +public: + ///Calculates the checksum for data. + /** \a data source data + * \return data checksum + * + * This function has no efect on the value returned by value(). + */ + virtual quint32 calculate(const QByteArray &data) = 0; + + ///Resets the calculation on a checksun for a stream. + virtual void reset() = 0; + + ///Updates the calculated checksum for the stream + /** \a buf next portion of data from the stream + */ + virtual void update(const QByteArray &buf) = 0; + + ///Value of the checksum calculated for the stream passed throw update(). + /** \return checksum + */ + virtual quint32 value() = 0; +}; + +#endif //QUACHECKSUM32_H diff --git a/depends/quazip/quacrc32.cpp b/depends/quazip/quacrc32.cpp new file mode 100644 index 00000000..9381f24c --- /dev/null +++ b/depends/quazip/quacrc32.cpp @@ -0,0 +1,28 @@ +#include "quacrc32.h" + +#include "zlib.h" + +QuaCrc32::QuaCrc32() +{ + reset(); +} + +quint32 QuaCrc32::calculate(const QByteArray &data) +{ + return crc32( crc32(0L, Z_NULL, 0), (const Bytef*)data.data(), data.size() ); +} + +void QuaCrc32::reset() +{ + checksum = crc32(0L, Z_NULL, 0); +} + +void QuaCrc32::update(const QByteArray &buf) +{ + checksum = crc32( checksum, (const Bytef*)buf.data(), buf.size() ); +} + +quint32 QuaCrc32::value() +{ + return checksum; +} diff --git a/depends/quazip/quacrc32.h b/depends/quazip/quacrc32.h new file mode 100644 index 00000000..4c86d566 --- /dev/null +++ b/depends/quazip/quacrc32.h @@ -0,0 +1,26 @@ +#ifndef QUACRC32_H +#define QUACRC32_H + +#include "quachecksum32.h" + +///CRC32 checksum +/** \class QuaCrc32 quacrc32.h <quazip/quacrc32.h> +* This class wrappers the crc32 function with the QuaChecksum32 interface. +* See QuaChecksum32 for more info. +*/ +class QUAZIP_EXPORT QuaCrc32 : public QuaChecksum32 { + +public: + QuaCrc32(); + + quint32 calculate(const QByteArray &data); + + void reset(); + void update(const QByteArray &buf); + quint32 value(); + +private: + quint32 checksum; +}; + +#endif //QUACRC32_H diff --git a/depends/quazip/quagzipfile.cpp b/depends/quazip/quagzipfile.cpp new file mode 100644 index 00000000..c1c70aad --- /dev/null +++ b/depends/quazip/quagzipfile.cpp @@ -0,0 +1,141 @@ +#include <QFile> + +#include "quagzipfile.h" + +class QuaGzipFilePrivate { + friend class QuaGzipFile; + QString fileName; + gzFile gzd; + inline QuaGzipFilePrivate(): gzd(NULL) {} + inline QuaGzipFilePrivate(const QString &fileName): + fileName(fileName), gzd(NULL) {} + template<typename FileId> bool open(FileId id, + QIODevice::OpenMode mode, QString &error); + gzFile open(int fd, const char *modeString); + gzFile open(const QString &name, const char *modeString); +}; + +gzFile QuaGzipFilePrivate::open(const QString &name, const char *modeString) +{ + return gzopen(QFile::encodeName(name).constData(), modeString); +} + +gzFile QuaGzipFilePrivate::open(int fd, const char *modeString) +{ + return gzdopen(fd, modeString); +} + +template<typename FileId> +bool QuaGzipFilePrivate::open(FileId id, QIODevice::OpenMode mode, + QString &error) +{ + char modeString[2]; + modeString[0] = modeString[1] = '\0'; + if ((mode & QIODevice::ReadOnly) != 0 + && (mode & QIODevice::WriteOnly) != 0) { + error = QuaGzipFile::trUtf8("Opening gzip for both reading" + " and writing is not supported"); + return false; + } else if ((mode & QIODevice::ReadOnly) != 0) { + modeString[0] = 'r'; + } else if ((mode & QIODevice::WriteOnly) != 0) { + modeString[0] = 'w'; + } else { + error = QuaGzipFile::trUtf8("You can open a gzip either for reading" + " or for writing. Which is it?"); + return false; + } + gzd = open(id, modeString); + if (gzd == NULL) { + error = QuaGzipFile::trUtf8("Could not gzopen() file"); + return false; + } + return true; +} + +QuaGzipFile::QuaGzipFile(): +d(new QuaGzipFilePrivate()) +{ +} + +QuaGzipFile::QuaGzipFile(QObject *parent): +QIODevice(parent), +d(new QuaGzipFilePrivate()) +{ +} + +QuaGzipFile::QuaGzipFile(const QString &fileName, QObject *parent): + QIODevice(parent), +d(new QuaGzipFilePrivate(fileName)) +{ +} + +QuaGzipFile::~QuaGzipFile() +{ + if (isOpen()) { + close(); + } + delete d; +} + +void QuaGzipFile::setFileName(const QString& fileName) +{ + d->fileName = fileName; +} + +QString QuaGzipFile::getFileName() const +{ + return d->fileName; +} + +bool QuaGzipFile::isSequential() const +{ + return true; +} + +bool QuaGzipFile::open(QIODevice::OpenMode mode) +{ + QString error; + if (!d->open(d->fileName, mode, error)) { + setErrorString(error); + return false; + } + return QIODevice::open(mode); +} + +bool QuaGzipFile::open(int fd, QIODevice::OpenMode mode) +{ + QString error; + if (!d->open(fd, mode, error)) { + setErrorString(error); + return false; + } + return QIODevice::open(mode); +} + +bool QuaGzipFile::flush() +{ + return gzflush(d->gzd, Z_SYNC_FLUSH) == Z_OK; +} + +void QuaGzipFile::close() +{ + QIODevice::close(); + gzclose(d->gzd); +} + +qint64 QuaGzipFile::readData(char *data, qint64 maxSize) +{ + return gzread(d->gzd, (voidp)data, (unsigned)maxSize); +} + +qint64 QuaGzipFile::writeData(const char *data, qint64 maxSize) +{ + if (maxSize == 0) + return 0; + int written = gzwrite(d->gzd, (voidp)data, (unsigned)maxSize); + if (written == 0) + return -1; + else + return written; +} diff --git a/depends/quazip/quagzipfile.h b/depends/quazip/quagzipfile.h new file mode 100644 index 00000000..211ceadb --- /dev/null +++ b/depends/quazip/quagzipfile.h @@ -0,0 +1,35 @@ +#ifndef QUAZIP_QUAGZIPFILE_H +#define QUAZIP_QUAGZIPFILE_H + +#include <QIODevice> +#include "quazip_global.h" + +#include <zlib.h> + +class QuaGzipFilePrivate; + +class QUAZIP_EXPORT QuaGzipFile: public QIODevice { + Q_OBJECT +public: + QuaGzipFile(); + QuaGzipFile(QObject *parent); + QuaGzipFile(const QString &fileName, QObject *parent = NULL); + virtual ~QuaGzipFile(); + void setFileName(const QString& fileName); + QString getFileName() const; + virtual bool isSequential() const; + virtual bool open(QIODevice::OpenMode mode); + virtual bool open(int fd, QIODevice::OpenMode mode); + virtual bool flush(); + virtual void close(); +protected: + virtual qint64 readData(char *data, qint64 maxSize); + virtual qint64 writeData(const char *data, qint64 maxSize); +private: + // not implemented by design to disable copy + QuaGzipFile(const QuaGzipFile &that); + QuaGzipFile& operator=(const QuaGzipFile &that); + QuaGzipFilePrivate *d; +}; + +#endif // QUAZIP_QUAGZIPFILE_H diff --git a/depends/quazip/quaziodevice.cpp b/depends/quazip/quaziodevice.cpp new file mode 100644 index 00000000..959ca0e8 --- /dev/null +++ b/depends/quazip/quaziodevice.cpp @@ -0,0 +1,283 @@ +#include "quaziodevice.h" + +#define QUAZIO_INBUFSIZE 4096 +#define QUAZIO_OUTBUFSIZE 4096 + +class QuaZIODevicePrivate { + friend class QuaZIODevice; + QuaZIODevicePrivate(QIODevice *io); + ~QuaZIODevicePrivate(); + QIODevice *io; + z_stream zins; + z_stream zouts; + char *inBuf; + int inBufPos; + int inBufSize; + char *outBuf; + int outBufPos; + int outBufSize; + bool zBufError; + int doFlush(QString &error); +}; + +QuaZIODevicePrivate::QuaZIODevicePrivate(QIODevice *io): + io(io), + inBuf(NULL), + inBufPos(0), + inBufSize(0), + outBuf(NULL), + outBufPos(0), + outBufSize(0), + zBufError(false) +{ + zins.zalloc = (alloc_func) NULL; + zins.zfree = (free_func) NULL; + zins.opaque = NULL; + zouts.zalloc = (alloc_func) NULL; + zouts.zfree = (free_func) NULL; + zouts.opaque = NULL; + inBuf = new char[QUAZIO_INBUFSIZE]; + outBuf = new char[QUAZIO_OUTBUFSIZE]; +#ifdef QUAZIP_ZIODEVICE_DEBUG_OUTPUT + debug.setFileName("debug.out"); + debug.open(QIODevice::WriteOnly); +#endif +#ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT + indebug.setFileName("debug.in"); + indebug.open(QIODevice::WriteOnly); +#endif +} + +QuaZIODevicePrivate::~QuaZIODevicePrivate() +{ +#ifdef QUAZIP_ZIODEVICE_DEBUG_OUTPUT + debug.close(); +#endif +#ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT + indebug.close(); +#endif + if (inBuf != NULL) + delete[] inBuf; + if (outBuf != NULL) + delete[] outBuf; +} + +int QuaZIODevicePrivate::doFlush(QString &error) +{ + int flushed = 0; + while (outBufPos < outBufSize) { + int more = io->write(outBuf + outBufPos, outBufSize - outBufPos); + if (more == -1) { + error = io->errorString(); + return -1; + } + if (more == 0) + break; + outBufPos += more; + flushed += more; + } + if (outBufPos == outBufSize) { + outBufPos = outBufSize = 0; + } + return flushed; +} + +// #define QUAZIP_ZIODEVICE_DEBUG_OUTPUT +// #define QUAZIP_ZIODEVICE_DEBUG_INPUT +#ifdef QUAZIP_ZIODEVICE_DEBUG_OUTPUT +#include <QFile> +static QFile debug; +#endif +#ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT +#include <QFile> +static QFile indebug; +#endif + +QuaZIODevice::QuaZIODevice(QIODevice *io, QObject *parent): + QIODevice(parent), + d(new QuaZIODevicePrivate(io)) +{ + connect(io, SIGNAL(readyRead()), SIGNAL(readyRead())); +} + +QuaZIODevice::~QuaZIODevice() +{ + if (isOpen()) + close(); + delete d; +} + +QIODevice *QuaZIODevice::getIoDevice() const +{ + return d->io; +} + +bool QuaZIODevice::open(QIODevice::OpenMode mode) +{ + if ((mode & QIODevice::ReadOnly) != 0) { + if (inflateInit(&d->zins) != Z_OK) { + setErrorString(d->zins.msg); + return false; + } + } + if ((mode & QIODevice::WriteOnly) != 0) { + if (deflateInit(&d->zouts, Z_DEFAULT_COMPRESSION) != Z_OK) { + setErrorString(d->zouts.msg); + return false; + } + } + return QIODevice::open(mode); +} + +void QuaZIODevice::close() +{ + if ((openMode() & QIODevice::ReadOnly) != 0) { + if (inflateEnd(&d->zins) != Z_OK) { + setErrorString(d->zins.msg); + } + } + if ((openMode() & QIODevice::WriteOnly) != 0) { + flush(); + if (deflateEnd(&d->zouts) != Z_OK) { + setErrorString(d->zouts.msg); + } + } + QIODevice::close(); +} + +qint64 QuaZIODevice::readData(char *data, qint64 maxSize) +{ + int read = 0; + while (read < maxSize) { + if (d->inBufPos == d->inBufSize) { + d->inBufPos = 0; + d->inBufSize = d->io->read(d->inBuf, QUAZIO_INBUFSIZE); + if (d->inBufSize == -1) { + d->inBufSize = 0; + setErrorString(d->io->errorString()); + return -1; + } + if (d->inBufSize == 0) + break; + } + while (read < maxSize && d->inBufPos < d->inBufSize) { + d->zins.next_in = (Bytef *) (d->inBuf + d->inBufPos); + d->zins.avail_in = d->inBufSize - d->inBufPos; + d->zins.next_out = (Bytef *) (data + read); + d->zins.avail_out = (uInt) (maxSize - read); // hope it's less than 2GB + int more = 0; + switch (inflate(&d->zins, Z_SYNC_FLUSH)) { + case Z_OK: + read = (char *) d->zins.next_out - data; + d->inBufPos = (char *) d->zins.next_in - d->inBuf; + break; + case Z_STREAM_END: + read = (char *) d->zins.next_out - data; + d->inBufPos = (char *) d->zins.next_in - d->inBuf; + return read; + case Z_BUF_ERROR: // this should never happen, but just in case + if (!d->zBufError) { + qWarning("Z_BUF_ERROR detected with %d/%d in/out, weird", + d->zins.avail_in, d->zins.avail_out); + d->zBufError = true; + } + memmove(d->inBuf, d->inBuf + d->inBufPos, d->inBufSize - d->inBufPos); + d->inBufSize -= d->inBufPos; + d->inBufPos = 0; + more = d->io->read(d->inBuf + d->inBufSize, QUAZIO_INBUFSIZE - d->inBufSize); + if (more == -1) { + setErrorString(d->io->errorString()); + return -1; + } + if (more == 0) + return read; + d->inBufSize += more; + break; + default: + setErrorString(QString::fromLocal8Bit(d->zins.msg)); + return -1; + } + } + } +#ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT + indebug.write(data, read); +#endif + return read; +} + +qint64 QuaZIODevice::writeData(const char *data, qint64 maxSize) +{ + int written = 0; + QString error; + if (d->doFlush(error) == -1) { + setErrorString(error); + return -1; + } + while (written < maxSize) { + // there is some data waiting in the output buffer + if (d->outBufPos < d->outBufSize) + return written; + d->zouts.next_in = (Bytef *) (data + written); + d->zouts.avail_in = (uInt) (maxSize - written); // hope it's less than 2GB + d->zouts.next_out = (Bytef *) d->outBuf; + d->zouts.avail_out = QUAZIO_OUTBUFSIZE; + switch (deflate(&d->zouts, Z_NO_FLUSH)) { + case Z_OK: + written = (char *) d->zouts.next_in - data; + d->outBufSize = (char *) d->zouts.next_out - d->outBuf; + break; + default: + setErrorString(QString::fromLocal8Bit(d->zouts.msg)); + return -1; + } + if (d->doFlush(error) == -1) { + setErrorString(error); + return -1; + } + } +#ifdef QUAZIP_ZIODEVICE_DEBUG_OUTPUT + debug.write(data, written); +#endif + return written; +} + +bool QuaZIODevice::flush() +{ + QString error; + if (d->doFlush(error) < 0) { + setErrorString(error); + return false; + } + // can't flush buffer, some data is still waiting + if (d->outBufPos < d->outBufSize) + return true; + Bytef c = 0; + d->zouts.next_in = &c; // fake input buffer + d->zouts.avail_in = 0; // of zero size + do { + d->zouts.next_out = (Bytef *) d->outBuf; + d->zouts.avail_out = QUAZIO_OUTBUFSIZE; + switch (deflate(&d->zouts, Z_SYNC_FLUSH)) { + case Z_OK: + d->outBufSize = (char *) d->zouts.next_out - d->outBuf; + if (d->doFlush(error) < 0) { + setErrorString(error); + return false; + } + if (d->outBufPos < d->outBufSize) + return true; + break; + case Z_BUF_ERROR: // nothing to write? + return true; + default: + setErrorString(QString::fromLocal8Bit(d->zouts.msg)); + return false; + } + } while (d->zouts.avail_out == 0); + return true; +} + +bool QuaZIODevice::isSequential() const +{ + return true; +} diff --git a/depends/quazip/quaziodevice.h b/depends/quazip/quaziodevice.h new file mode 100644 index 00000000..b061cd16 --- /dev/null +++ b/depends/quazip/quaziodevice.h @@ -0,0 +1,27 @@ +#ifndef QUAZIP_QUAZIODEVICE_H +#define QUAZIP_QUAZIODEVICE_H + +#include <QIODevice> +#include "quazip_global.h" + +#include <zlib.h> + +class QuaZIODevicePrivate; + +class QUAZIP_EXPORT QuaZIODevice: public QIODevice { + Q_OBJECT +public: + QuaZIODevice(QIODevice *io, QObject *parent = NULL); + ~QuaZIODevice(); + virtual bool flush(); + virtual bool open(QIODevice::OpenMode); + virtual void close(); + QIODevice *getIoDevice() const; + virtual bool isSequential() const; +protected: + virtual qint64 readData(char *data, qint64 maxSize); + virtual qint64 writeData(const char *data, qint64 maxSize); +private: + QuaZIODevicePrivate *d; +}; +#endif // QUAZIP_QUAZIODEVICE_H diff --git a/depends/quazip/quazip.cpp b/depends/quazip/quazip.cpp new file mode 100644 index 00000000..b6fa92f0 --- /dev/null +++ b/depends/quazip/quazip.cpp @@ -0,0 +1,554 @@ +/* +Copyright (C) 2005-2011 Sergey A. Tachenov + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2 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 Lesser +General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +See COPYING file for the full LGPL text. + +Original ZIP package is copyrighted by Gilles Vollant, see +quazip/(un)zip.h files for details, basically it's zlib license. + **/ + +#include <QtCore/QFile> +#include <QtCore/QFlags> + +#include "quazip.h" + +/// All the internal stuff for the QuaZip class. +/** + \internal + + This class keeps all the private stuff for the QuaZip class so it can + be changed without breaking binary compatibility, according to the + Pimpl idiom. + */ +class QuaZipPrivate { + friend class QuaZip; + private: + /// The pointer to the corresponding QuaZip instance. + QuaZip *q; + /// The codec for file names. + QTextCodec *fileNameCodec; + /// The codec for comments. + QTextCodec *commentCodec; + /// The archive file name. + QString zipName; + /// The device to access the archive. + QIODevice *ioDevice; + /// The global comment. + QString comment; + /// The open mode. + QuaZip::Mode mode; + union { + /// The internal handle for UNZIP modes. + unzFile unzFile_f; + /// The internal handle for ZIP modes. + zipFile zipFile_f; + }; + /// Whether a current file is set. + bool hasCurrentFile_f; + /// The last error. + int zipError; + /// Whether \ref QuaZip::setDataDescriptorWritingEnabled() "the data descriptor writing mode" is enabled. + bool dataDescriptorWritingEnabled; + /// The constructor for the corresponding QuaZip constructor. + inline QuaZipPrivate(QuaZip *q): + q(q), + fileNameCodec(QTextCodec::codecForLocale()), + commentCodec(QTextCodec::codecForLocale()), + ioDevice(NULL), + mode(QuaZip::mdNotOpen), + hasCurrentFile_f(false), + zipError(UNZ_OK), + dataDescriptorWritingEnabled(true) {} + /// The constructor for the corresponding QuaZip constructor. + inline QuaZipPrivate(QuaZip *q, const QString &zipName): + q(q), + fileNameCodec(QTextCodec::codecForLocale()), + commentCodec(QTextCodec::codecForLocale()), + zipName(zipName), + ioDevice(NULL), + mode(QuaZip::mdNotOpen), + hasCurrentFile_f(false), + zipError(UNZ_OK), + dataDescriptorWritingEnabled(true) {} + /// The constructor for the corresponding QuaZip constructor. + inline QuaZipPrivate(QuaZip *q, QIODevice *ioDevice): + q(q), + fileNameCodec(QTextCodec::codecForLocale()), + commentCodec(QTextCodec::codecForLocale()), + ioDevice(ioDevice), + mode(QuaZip::mdNotOpen), + hasCurrentFile_f(false), + zipError(UNZ_OK), + dataDescriptorWritingEnabled(true) {} + /// Returns either a list of file names or a list of QuaZipFileInfo. + template<typename TFileInfo> + bool getFileInfoList(QList<TFileInfo> *result) const; +}; + +QuaZip::QuaZip(): + p(new QuaZipPrivate(this)) +{ +} + +QuaZip::QuaZip(const QString& zipName): + p(new QuaZipPrivate(this, zipName)) +{ +} + +QuaZip::QuaZip(QIODevice *ioDevice): + p(new QuaZipPrivate(this, ioDevice)) +{ +} + +QuaZip::~QuaZip() +{ + if(isOpen()) + close(); + delete p; +} + +bool QuaZip::open(Mode mode, zlib_filefunc_def* ioApi) +{ + p->zipError=UNZ_OK; + if(isOpen()) { + qWarning("QuaZip::open(): ZIP already opened"); + return false; + } + QIODevice *ioDevice = p->ioDevice; + if (ioDevice == NULL) { + if (p->zipName.isEmpty()) { + qWarning("QuaZip::open(): set either ZIP file name or IO device first"); + return false; + } else { + ioDevice = new QFile(p->zipName); + } + } + switch(mode) { + case mdUnzip: + p->unzFile_f=unzOpen2(ioDevice, ioApi); + if(p->unzFile_f!=NULL) { + p->mode=mode; + p->ioDevice = ioDevice; + return true; + } else { + p->zipError=UNZ_OPENERROR; + if (!p->zipName.isEmpty()) + delete ioDevice; + return false; + } + case mdCreate: + case mdAppend: + case mdAdd: + p->zipFile_f=zipOpen2(ioDevice, + mode==mdCreate?APPEND_STATUS_CREATE: + mode==mdAppend?APPEND_STATUS_CREATEAFTER: + APPEND_STATUS_ADDINZIP, + NULL, + ioApi); + if(p->zipFile_f!=NULL) { + p->mode=mode; + p->ioDevice = ioDevice; + return true; + } else { + p->zipError=UNZ_OPENERROR; + if (!p->zipName.isEmpty()) + delete ioDevice; + return false; + } + default: + qWarning("QuaZip::open(): unknown mode: %d", (int)mode); + if (!p->zipName.isEmpty()) + delete ioDevice; + return false; + break; + } +} + +void QuaZip::close() +{ + p->zipError=UNZ_OK; + switch(p->mode) { + case mdNotOpen: + qWarning("QuaZip::close(): ZIP is not open"); + return; + case mdUnzip: + p->zipError=unzClose(p->unzFile_f); + break; + case mdCreate: + case mdAppend: + case mdAdd: + p->zipError=zipClose(p->zipFile_f, + p->comment.isNull() ? NULL + : p->commentCodec->fromUnicode(p->comment).constData()); + break; + default: + qWarning("QuaZip::close(): unknown mode: %d", (int)p->mode); + return; + } + // opened by name, need to delete the internal IO device + if (!p->zipName.isEmpty()) { + delete p->ioDevice; + p->ioDevice = NULL; + } + if(p->zipError==UNZ_OK) + p->mode=mdNotOpen; +} + +void QuaZip::setZipName(const QString& zipName) +{ + if(isOpen()) { + qWarning("QuaZip::setZipName(): ZIP is already open!"); + return; + } + p->zipName=zipName; + p->ioDevice = NULL; +} + +void QuaZip::setIoDevice(QIODevice *ioDevice) +{ + if(isOpen()) { + qWarning("QuaZip::setIoDevice(): ZIP is already open!"); + return; + } + p->ioDevice = ioDevice; + p->zipName = QString(); +} + +int QuaZip::getEntriesCount()const +{ + QuaZip *fakeThis=(QuaZip*)this; // non-const + fakeThis->p->zipError=UNZ_OK; + if(p->mode!=mdUnzip) { + qWarning("QuaZip::getEntriesCount(): ZIP is not open in mdUnzip mode"); + return -1; + } + unz_global_info globalInfo; + if((fakeThis->p->zipError=unzGetGlobalInfo(p->unzFile_f, &globalInfo))!=UNZ_OK) + return p->zipError; + return (int)globalInfo.number_entry; +} + +QString QuaZip::getComment()const +{ + QuaZip *fakeThis=(QuaZip*)this; // non-const + fakeThis->p->zipError=UNZ_OK; + if(p->mode!=mdUnzip) { + qWarning("QuaZip::getComment(): ZIP is not open in mdUnzip mode"); + return QString(); + } + unz_global_info globalInfo; + QByteArray comment; + if((fakeThis->p->zipError=unzGetGlobalInfo(p->unzFile_f, &globalInfo))!=UNZ_OK) + return QString(); + comment.resize(globalInfo.size_comment); + if((fakeThis->p->zipError=unzGetGlobalComment(p->unzFile_f, comment.data(), comment.size())) < 0) + return QString(); + fakeThis->p->zipError = UNZ_OK; + return p->commentCodec->toUnicode(comment); +} + +bool QuaZip::setCurrentFile(const QString& fileName, CaseSensitivity cs) +{ + p->zipError=UNZ_OK; + if(p->mode!=mdUnzip) { + qWarning("QuaZip::setCurrentFile(): ZIP is not open in mdUnzip mode"); + return false; + } + if(fileName.isEmpty()) { + p->hasCurrentFile_f=false; + return true; + } + // Unicode-aware reimplementation of the unzLocateFile function + if(p->unzFile_f==NULL) { + p->zipError=UNZ_PARAMERROR; + return false; + } + if(fileName.length()>MAX_FILE_NAME_LENGTH) { + p->zipError=UNZ_PARAMERROR; + return false; + } + bool sens = convertCaseSensitivity(cs) == Qt::CaseSensitive; + QString lower, current; + if(!sens) lower=fileName.toLower(); + p->hasCurrentFile_f=false; + for(bool more=goToFirstFile(); more; more=goToNextFile()) { + current=getCurrentFileName(); + if(current.isEmpty()) return false; + if(sens) { + if(current==fileName) break; + } else { + if(current.toLower()==lower) break; + } + } + return p->hasCurrentFile_f; +} + +bool QuaZip::goToFirstFile() +{ + p->zipError=UNZ_OK; + if(p->mode!=mdUnzip) { + qWarning("QuaZip::goToFirstFile(): ZIP is not open in mdUnzip mode"); + return false; + } + p->zipError=unzGoToFirstFile(p->unzFile_f); + p->hasCurrentFile_f=p->zipError==UNZ_OK; + return p->hasCurrentFile_f; +} + +bool QuaZip::goToNextFile() +{ + p->zipError=UNZ_OK; + if(p->mode!=mdUnzip) { + qWarning("QuaZip::goToFirstFile(): ZIP is not open in mdUnzip mode"); + return false; + } + p->zipError=unzGoToNextFile(p->unzFile_f); + p->hasCurrentFile_f=p->zipError==UNZ_OK; + if(p->zipError==UNZ_END_OF_LIST_OF_FILE) + p->zipError=UNZ_OK; + return p->hasCurrentFile_f; +} + +bool QuaZip::getCurrentFileInfo(QuaZipFileInfo *info)const +{ + QuaZip *fakeThis=(QuaZip*)this; // non-const + fakeThis->p->zipError=UNZ_OK; + if(p->mode!=mdUnzip) { + qWarning("QuaZip::getCurrentFileInfo(): ZIP is not open in mdUnzip mode"); + return false; + } + unz_file_info info_z; + QByteArray fileName; + QByteArray extra; + QByteArray comment; + if(info==NULL) return false; + if(!isOpen()||!hasCurrentFile()) return false; + if((fakeThis->p->zipError=unzGetCurrentFileInfo(p->unzFile_f, &info_z, NULL, 0, NULL, 0, NULL, 0))!=UNZ_OK) + return false; + fileName.resize(info_z.size_filename); + extra.resize(info_z.size_file_extra); + comment.resize(info_z.size_file_comment); + if((fakeThis->p->zipError=unzGetCurrentFileInfo(p->unzFile_f, NULL, + fileName.data(), fileName.size(), + extra.data(), extra.size(), + comment.data(), comment.size()))!=UNZ_OK) + return false; + info->versionCreated=info_z.version; + info->versionNeeded=info_z.version_needed; + info->flags=info_z.flag; + info->method=info_z.compression_method; + info->crc=info_z.crc; + info->compressedSize=info_z.compressed_size; + info->uncompressedSize=info_z.uncompressed_size; + info->diskNumberStart=info_z.disk_num_start; + info->internalAttr=info_z.internal_fa; + info->externalAttr=info_z.external_fa; + info->name=p->fileNameCodec->toUnicode(fileName); + info->comment=p->commentCodec->toUnicode(comment); + info->extra=extra; + info->dateTime=QDateTime( + QDate(info_z.tmu_date.tm_year, info_z.tmu_date.tm_mon+1, info_z.tmu_date.tm_mday), + QTime(info_z.tmu_date.tm_hour, info_z.tmu_date.tm_min, info_z.tmu_date.tm_sec)); + return true; +} + +QString QuaZip::getCurrentFileName()const +{ + QuaZip *fakeThis=(QuaZip*)this; // non-const + fakeThis->p->zipError=UNZ_OK; + if(p->mode!=mdUnzip) { + qWarning("QuaZip::getCurrentFileName(): ZIP is not open in mdUnzip mode"); + return QString(); + } + if(!isOpen()||!hasCurrentFile()) return QString(); + QByteArray fileName(MAX_FILE_NAME_LENGTH, 0); + if((fakeThis->p->zipError=unzGetCurrentFileInfo(p->unzFile_f, NULL, fileName.data(), fileName.size(), + NULL, 0, NULL, 0))!=UNZ_OK) + return QString(); + return p->fileNameCodec->toUnicode(fileName.constData()); +} + +void QuaZip::setFileNameCodec(QTextCodec *fileNameCodec) +{ + p->fileNameCodec=fileNameCodec; +} + +void QuaZip::setFileNameCodec(const char *fileNameCodecName) +{ + p->fileNameCodec=QTextCodec::codecForName(fileNameCodecName); +} + +QTextCodec *QuaZip::getFileNameCodec()const +{ + return p->fileNameCodec; +} + +void QuaZip::setCommentCodec(QTextCodec *commentCodec) +{ + p->commentCodec=commentCodec; +} + +void QuaZip::setCommentCodec(const char *commentCodecName) +{ + p->commentCodec=QTextCodec::codecForName(commentCodecName); +} + +QTextCodec *QuaZip::getCommentCodec()const +{ + return p->commentCodec; +} + +QString QuaZip::getZipName() const +{ + return p->zipName; +} + +QIODevice *QuaZip::getIoDevice() const +{ + if (!p->zipName.isEmpty()) // opened by name, using an internal QIODevice + return NULL; + return p->ioDevice; +} + +QuaZip::Mode QuaZip::getMode()const +{ + return p->mode; +} + +bool QuaZip::isOpen()const +{ + return p->mode!=mdNotOpen; +} + +int QuaZip::getZipError() const +{ + return p->zipError; +} + +void QuaZip::setComment(const QString& comment) +{ + p->comment=comment; +} + +bool QuaZip::hasCurrentFile()const +{ + return p->hasCurrentFile_f; +} + +unzFile QuaZip::getUnzFile() +{ + return p->unzFile_f; +} + +zipFile QuaZip::getZipFile() +{ + return p->zipFile_f; +} + +void QuaZip::setDataDescriptorWritingEnabled(bool enabled) +{ + p->dataDescriptorWritingEnabled = enabled; +} + +bool QuaZip::isDataDescriptorWritingEnabled() const +{ + return p->dataDescriptorWritingEnabled; +} + +template<typename TFileInfo> +TFileInfo QuaZip_getFileInfo(QuaZip *zip, bool *ok); + +template<> +QuaZipFileInfo QuaZip_getFileInfo(QuaZip *zip, bool *ok) +{ + QuaZipFileInfo info; + *ok = zip->getCurrentFileInfo(&info); + return info; +} + +template<> +QString QuaZip_getFileInfo(QuaZip *zip, bool *ok) +{ + QString name = zip->getCurrentFileName(); + *ok = !name.isEmpty(); + return name; +} + +template<typename TFileInfo> +bool QuaZipPrivate::getFileInfoList(QList<TFileInfo> *result) const +{ + QuaZipPrivate *fakeThis=const_cast<QuaZipPrivate*>(this); + fakeThis->zipError=UNZ_OK; + if (mode!=QuaZip::mdUnzip) { + qWarning("QuaZip::getFileNameList/getFileInfoList(): " + "ZIP is not open in mdUnzip mode"); + return false; + } + QString currentFile; + if (q->hasCurrentFile()) { + currentFile = q->getCurrentFileName(); + } + if (q->goToFirstFile()) { + do { + bool ok; + result->append(QuaZip_getFileInfo<TFileInfo>(q, &ok)); + if (!ok) + return false; + } while (q->goToNextFile()); + } + if (zipError != UNZ_OK) + return false; + if (currentFile.isEmpty()) { + if (!q->goToFirstFile()) + return false; + } else { + if (!q->setCurrentFile(currentFile)) + return false; + } + return true; +} + +QStringList QuaZip::getFileNameList() const +{ + QStringList list; + if (p->getFileInfoList(&list)) + return list; + else + return QStringList(); +} + +QList<QuaZipFileInfo> QuaZip::getFileInfoList() const +{ + QList<QuaZipFileInfo> list; + if (p->getFileInfoList(&list)) + return list; + else + return QList<QuaZipFileInfo>(); +} + +Qt::CaseSensitivity QuaZip::convertCaseSensitivity(QuaZip::CaseSensitivity cs) +{ + if (cs == csDefault) { +#ifdef Q_WS_WIN + return Qt::CaseInsensitive; +#else + return Qt::CaseSensitive; +#endif + } else { + return cs == csSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; + } +} diff --git a/depends/quazip/quazip.h b/depends/quazip/quazip.h new file mode 100644 index 00000000..a3ab8e52 --- /dev/null +++ b/depends/quazip/quazip.h @@ -0,0 +1,411 @@ +#ifndef QUA_ZIP_H +#define QUA_ZIP_H + +/* +Copyright (C) 2005-2011 Sergey A. Tachenov + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2 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 Lesser +General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +See COPYING file for the full LGPL text. + +Original ZIP package is copyrighted by Gilles Vollant, see +quazip/(un)zip.h files for details, basically it's zlib license. + **/ + +#include <QString> +#include <QStringList> +#include <QTextCodec> + +#include "zip.h" +#include "unzip.h" + +#include "quazip_global.h" +#include "quazipfileinfo.h" + +// just in case it will be defined in the later versions of the ZIP/UNZIP +#ifndef UNZ_OPENERROR +// define additional error code +#define UNZ_OPENERROR -1000 +#endif + +class QuaZipPrivate; + +/// ZIP archive. +/** \class QuaZip quazip.h <quazip/quazip.h> + * This class implements basic interface to the ZIP archive. It can be + * used to read table contents of the ZIP archive and retreiving + * information about the files inside it. + * + * You can also use this class to open files inside archive by passing + * pointer to the instance of this class to the constructor of the + * QuaZipFile class. But see QuaZipFile::QuaZipFile(QuaZip*, QObject*) + * for the possible pitfalls. + * + * This class is indended to provide interface to the ZIP subpackage of + * the ZIP/UNZIP package as well as to the UNZIP subpackage. But + * currently it supports only UNZIP. + * + * The use of this class is simple - just create instance using + * constructor, then set ZIP archive file name using setFile() function + * (if you did not passed the name to the constructor), then open() and + * then use different functions to work with it! Well, if you are + * paranoid, you may also wish to call close before destructing the + * instance, to check for errors on close. + * + * You may also use getUnzFile() and getZipFile() functions to get the + * ZIP archive handle and use it with ZIP/UNZIP package API directly. + * + * This class supports localized file names inside ZIP archive, but you + * have to set up proper codec with setCodec() function. By default, + * locale codec will be used, which is probably ok for UNIX systems, but + * will almost certainly fail with ZIP archives created in Windows. This + * is because Windows ZIP programs have strange habit of using DOS + * encoding for file names in ZIP archives. For example, ZIP archive + * with cyrillic names created in Windows will have file names in \c + * IBM866 encoding instead of \c WINDOWS-1251. I think that calling one + * function is not much trouble, but for true platform independency it + * would be nice to have some mechanism for file name encoding auto + * detection using locale information. Does anyone know a good way to do + * it? + **/ +class QUAZIP_EXPORT QuaZip { + friend class QuaZipPrivate; + public: + /// Useful constants. + enum Constants { + MAX_FILE_NAME_LENGTH=256 /**< Maximum file name length. Taken from + \c UNZ_MAXFILENAMEINZIP constant in + unzip.c. */ + }; + /// Open mode of the ZIP file. + enum Mode { + mdNotOpen, ///< ZIP file is not open. This is the initial mode. + mdUnzip, ///< ZIP file is open for reading files inside it. + mdCreate, ///< ZIP file was created with open() call. + mdAppend, /**< ZIP file was opened in append mode. This refers to + * \c APPEND_STATUS_CREATEAFTER mode in ZIP/UNZIP package + * and means that zip is appended to some existing file + * what is useful when that file contains + * self-extractor code. This is obviously \em not what + * you whant to use to add files to the existing ZIP + * archive. + **/ + mdAdd ///< ZIP file was opened for adding files in the archive. + }; + /// Case sensitivity for the file names. + /** This is what you specify when accessing files in the archive. + * Works perfectly fine with any characters thanks to Qt's great + * unicode support. This is different from ZIP/UNZIP API, where + * only US-ASCII characters was supported. + **/ + enum CaseSensitivity { + csDefault=0, ///< Default for platform. Case sensitive for UNIX, not for Windows. + csSensitive=1, ///< Case sensitive. + csInsensitive=2 ///< Case insensitive. + }; + static Qt::CaseSensitivity convertCaseSensitivity(CaseSensitivity); + private: + QuaZipPrivate *p; + // not (and will not be) implemented + QuaZip(const QuaZip& that); + // not (and will not be) implemented + QuaZip& operator=(const QuaZip& that); + public: + /// Constructs QuaZip object. + /** Call setName() before opening constructed object. */ + QuaZip(); + /// Constructs QuaZip object associated with ZIP file \a zipName. + QuaZip(const QString& zipName); + /// Constructs QuaZip object associated with ZIP file represented by \a ioDevice. + /** The IO device must be seekable, otherwise an error will occur when opening. */ + QuaZip(QIODevice *ioDevice); + /// Destroys QuaZip object. + /** Calls close() if necessary. */ + ~QuaZip(); + /// Opens ZIP file. + /** + * Argument \a mode specifies open mode of the ZIP archive. See Mode + * for details. Note that there is zipOpen2() function in the + * ZIP/UNZIP API which accepts \a globalcomment argument, but it + * does not use it anywhere, so this open() function does not have this + * argument. See setComment() if you need to set global comment. + * + * If the ZIP file is accessed via explicitly set QIODevice, then + * this device is opened in the necessary mode. If the device was + * already opened by some other means, then the behaviour is defined by + * the device implementation, but generally it is not a very good + * idea. For example, QFile will at least issue a warning. + * + * \return \c true if successful, \c false otherwise. + * + * \note ZIP/UNZIP API open calls do not return error code - they + * just return \c NULL indicating an error. But to make things + * easier, quazip.h header defines additional error code \c + * UNZ_ERROROPEN and getZipError() will return it if the open call + * of the ZIP/UNZIP API returns \c NULL. + * + * Argument \a ioApi specifies IO function set for ZIP/UNZIP + * package to use. See unzip.h, zip.h and ioapi.h for details. Note + * that IO API for QuaZip is different from the original package. + * The file path argument was changed to be of type \c voidpf, and + * QuaZip passes a QIODevice pointer there. This QIODevice is either + * set explicitly via setIoDevice() or the QuaZip(QIODevice*) + * constructor, or it is created internally when opening the archive + * by its file name. The default API (qioapi.cpp) just delegates + * everything to the QIODevice API. Not only this allows to use a + * QIODevice instead of file name, but also has a nice side effect + * of raising the file size limit from 2G to 4G. + * + * In short: just forget about the \a ioApi argument and you'll be + * fine. + **/ + bool open(Mode mode, zlib_filefunc_def *ioApi =NULL); + /// Closes ZIP file. + /** Call getZipError() to determine if the close was successful. The + * underlying QIODevice is also closed, regardless of whether it was + * set explicitly or not. */ + void close(); + /// Sets the codec used to encode/decode file names inside archive. + /** This is necessary to access files in the ZIP archive created + * under Windows with non-latin characters in file names. For + * example, file names with cyrillic letters will be in \c IBM866 + * encoding. + **/ + void setFileNameCodec(QTextCodec *fileNameCodec); + /// Sets the codec used to encode/decode file names inside archive. + /** \overload + * Equivalent to calling setFileNameCodec(QTextCodec::codecForName(codecName)); + **/ + void setFileNameCodec(const char *fileNameCodecName); + /// Returns the codec used to encode/decode comments inside archive. + QTextCodec* getFileNameCodec() const; + /// Sets the codec used to encode/decode comments inside archive. + /** This codec defaults to locale codec, which is probably ok. + **/ + void setCommentCodec(QTextCodec *commentCodec); + /// Sets the codec used to encode/decode comments inside archive. + /** \overload + * Equivalent to calling setCommentCodec(QTextCodec::codecForName(codecName)); + **/ + void setCommentCodec(const char *commentCodecName); + /// Returns the codec used to encode/decode comments inside archive. + QTextCodec* getCommentCodec() const; + /// Returns the name of the ZIP file. + /** Returns null string if no ZIP file name has been set, for + * example when the QuaZip instance is set up to use a QIODevice + * instead. + * \sa setZipName(), setIoDevice(), getIoDevice() + **/ + QString getZipName() const; + /// Sets the name of the ZIP file. + /** Does nothing if the ZIP file is open. + * + * Does not reset error code returned by getZipError(). + * \sa setIoDevice(), getIoDevice(), getZipName() + **/ + void setZipName(const QString& zipName); + /// Returns the device representing this ZIP file. + /** Returns null string if no device has been set explicitly, for + * example when opening a ZIP file by name. + * \sa setIoDevice(), getZipName(), setZipName() + **/ + QIODevice *getIoDevice() const; + /// Sets the device representing the ZIP file. + /** Does nothing if the ZIP file is open. + * + * Does not reset error code returned by getZipError(). + * \sa getIoDevice(), getZipName(), setZipName() + **/ + void setIoDevice(QIODevice *ioDevice); + /// Returns the mode in which ZIP file was opened. + Mode getMode() const; + /// Returns \c true if ZIP file is open, \c false otherwise. + bool isOpen() const; + /// Returns the error code of the last operation. + /** Returns \c UNZ_OK if the last operation was successful. + * + * Error code resets to \c UNZ_OK every time you call any function + * that accesses something inside ZIP archive, even if it is \c + * const (like getEntriesCount()). open() and close() calls reset + * error code too. See documentation for the specific functions for + * details on error detection. + **/ + int getZipError() const; + /// Returns number of the entries in the ZIP central directory. + /** Returns negative error code in the case of error. The same error + * code will be returned by subsequent getZipError() call. + **/ + int getEntriesCount() const; + /// Returns global comment in the ZIP file. + QString getComment() const; + /// Sets the global comment in the ZIP file. + /** The comment will be written to the archive on close operation. + * QuaZip makes a distinction between a null QByteArray() comment + * and an empty "" comment in the QuaZip::mdAdd mode. + * A null comment is the default and it means "don't change + * the comment". An empty comment removes the original comment. + * + * \sa open() + **/ + void setComment(const QString& comment); + /// Sets the current file to the first file in the archive. + /** Returns \c true on success, \c false otherwise. Call + * getZipError() to get the error code. + **/ + bool goToFirstFile(); + /// Sets the current file to the next file in the archive. + /** Returns \c true on success, \c false otherwise. Call + * getZipError() to determine if there was an error. + * + * Should be used only in QuaZip::mdUnzip mode. + * + * \note If the end of file was reached, getZipError() will return + * \c UNZ_OK instead of \c UNZ_END_OF_LIST_OF_FILE. This is to make + * things like this easier: + * \code + * for(bool more=zip.goToFirstFile(); more; more=zip.goToNextFile()) { + * // do something + * } + * if(zip.getZipError()==UNZ_OK) { + * // ok, there was no error + * } + * \endcode + **/ + bool goToNextFile(); + /// Sets current file by its name. + /** Returns \c true if successful, \c false otherwise. Argument \a + * cs specifies case sensitivity of the file name. Call + * getZipError() in the case of a failure to get error code. + * + * This is not a wrapper to unzLocateFile() function. That is + * because I had to implement locale-specific case-insensitive + * comparison. + * + * Here are the differences from the original implementation: + * + * - If the file was not found, error code is \c UNZ_OK, not \c + * UNZ_END_OF_LIST_OF_FILE (see also goToNextFile()). + * - If this function fails, it unsets the current file rather than + * resetting it back to what it was before the call. + * + * If \a fileName is null string then this function unsets the + * current file and return \c true. Note that you should close the + * file first if it is open! See + * QuaZipFile::QuaZipFile(QuaZip*,QObject*) for the details. + * + * Should be used only in QuaZip::mdUnzip mode. + * + * \sa setFileNameCodec(), CaseSensitivity + **/ + bool setCurrentFile(const QString& fileName, CaseSensitivity cs =csDefault); + /// Returns \c true if the current file has been set. + bool hasCurrentFile() const; + /// Retrieves information about the current file. + /** Fills the structure pointed by \a info. Returns \c true on + * success, \c false otherwise. In the latter case structure pointed + * by \a info remains untouched. If there was an error, + * getZipError() returns error code. + * + * Should be used only in QuaZip::mdUnzip mode. + * + * Does nothing and returns \c false in any of the following cases. + * - ZIP is not open; + * - ZIP does not have current file; + * - \a info is \c NULL; + * + * In all these cases getZipError() returns \c UNZ_OK since there + * is no ZIP/UNZIP API call. + **/ + bool getCurrentFileInfo(QuaZipFileInfo* info)const; + /// Returns the current file name. + /** Equivalent to calling getCurrentFileInfo() and then getting \c + * name field of the QuaZipFileInfo structure, but faster and more + * convenient. + * + * Should be used only in QuaZip::mdUnzip mode. + **/ + QString getCurrentFileName()const; + /// Returns \c unzFile handle. + /** You can use this handle to directly call UNZIP part of the + * ZIP/UNZIP package functions (see unzip.h). + * + * \warning When using the handle returned by this function, please + * keep in mind that QuaZip class is unable to detect any changes + * you make in the ZIP file state (e. g. changing current file, or + * closing the handle). So please do not do anything with this + * handle that is possible to do with the functions of this class. + * Or at least return the handle in the original state before + * calling some another function of this class (including implicit + * destructor calls and calls from the QuaZipFile objects that refer + * to this QuaZip instance!). So if you have changed the current + * file in the ZIP archive - then change it back or you may + * experience some strange behavior or even crashes. + **/ + unzFile getUnzFile(); + /// Returns \c zipFile handle. + /** You can use this handle to directly call ZIP part of the + * ZIP/UNZIP package functions (see zip.h). Warnings about the + * getUnzFile() function also apply to this function. + **/ + zipFile getZipFile(); + /// Changes the data descriptor writing mode. + /** + According to the ZIP format specification, a file inside archive + may have a data descriptor immediately following the file + data. This is reflected by a special flag in the local file header + and in the central directory. By default, QuaZIP sets this flag + and writes the data descriptor unless both method and level were + set to 0, in which case it operates in 1.0-compatible mode and + never writes data descriptors. + + By setting this flag to false, it is possible to disable data + descriptor writing, thus increasing compatibility with archive + readers that don't understand this feature of the ZIP file format. + + Setting this flag affects all the QuaZipFile instances that are + opened after this flag is set. + + The data descriptor writing mode is enabled by default. + + \param enabled If \c true, enable local descriptor writing, + disable it otherwise. + + \sa QuaZipFile::setDataDescriptorWritingEnabled() + */ + void setDataDescriptorWritingEnabled(bool enabled); + /// Returns the data descriptor default writing mode. + /** + \sa setDataDescriptorWritingEnabled() + */ + bool isDataDescriptorWritingEnabled() const; + /// Returns a list of files inside the archive. + /** + \return A list of file names or an empty list if there + was an error or if the archive is empty (call getZipError() to + figure out which). + \sa getFileInfoList() + */ + QStringList getFileNameList() const; + /// Returns information list about all files inside the archive. + /** + \return A list of QuaZipFileInfo objects or an empty list if there + was an error or if the archive is empty (call getZipError() to + figure out which). + \sa getFileNameList() + */ + QList<QuaZipFileInfo> getFileInfoList() const; +}; + +#endif diff --git a/depends/quazip/quazip_global.h b/depends/quazip/quazip_global.h new file mode 100644 index 00000000..d9d09ade --- /dev/null +++ b/depends/quazip/quazip_global.h @@ -0,0 +1,55 @@ +/** +Copyright (C) 2005-2011 Sergey A. Tachenov + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2 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 Lesser +General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +See COPYING file for the full LGPL text. + +Original ZIP package is copyrighted by Gilles Vollant, see +quazip/(un)zip.h files for details, basically it's zlib license. + */ + +#ifndef QUAZIP_GLOBAL_H +#define QUAZIP_GLOBAL_H + +#include <QtCore/qglobal.h> + +/** + This is automatically defined when building a static library, but when + including QuaZip sources directly into a project, QUAZIP_STATIC should + be defined explicitly to avoid possible troubles with unnecessary + importing/exporting. + */ +#ifdef QUAZIP_STATIC +#define QUAZIP_EXPORT +#else +/** + * When building a DLL with MSVC, QUAZIP_BUILD must be defined. + * qglobal.h takes care of defining Q_DECL_* correctly for msvc/gcc. + */ +#if defined(QUAZIP_BUILD) + #define QUAZIP_EXPORT Q_DECL_EXPORT +#else + #define QUAZIP_EXPORT Q_DECL_IMPORT +#endif +#endif // QUAZIP_STATIC + +#ifdef __GNUC__ +#define UNUSED __attribute__((__unused__)) +#else +#define UNUSED +#endif + +#endif // QUAZIP_GLOBAL_H diff --git a/depends/quazip/quazipdir.cpp b/depends/quazip/quazipdir.cpp new file mode 100644 index 00000000..02208894 --- /dev/null +++ b/depends/quazip/quazipdir.cpp @@ -0,0 +1,507 @@ +#include "quazipdir.h" + +#include <QSet> +#include <QSharedData> + +class QuaZipDirPrivate: public QSharedData { + friend class QuaZipDir; +private: + QuaZipDirPrivate(QuaZip *zip, const QString &dir = QString()): + zip(zip), dir(dir), caseSensitivity(QuaZip::csDefault), + filter(QDir::NoFilter), sorting(QDir::NoSort) {} + QuaZip *zip; + QString dir; + QuaZip::CaseSensitivity caseSensitivity; + QDir::Filters filter; + QStringList nameFilters; + QDir::SortFlags sorting; + template<typename TFileInfoList> + bool entryInfoList(QStringList nameFilters, QDir::Filters filter, + QDir::SortFlags sort, TFileInfoList &result) const; + inline QString simplePath() const {return QDir::cleanPath(dir);} +}; + +QuaZipDir::QuaZipDir(const QuaZipDir &that): + d(that.d) +{ +} + +QuaZipDir::QuaZipDir(QuaZip *zip, const QString &dir): + d(new QuaZipDirPrivate(zip, dir)) +{ + if (d->dir.startsWith('/')) + d->dir = d->dir.mid(1); +} + +QuaZipDir::~QuaZipDir() +{ +} + +bool QuaZipDir::operator==(const QuaZipDir &that) +{ + return d->zip == that.d->zip && d->dir == that.d->dir; +} + +QuaZipDir& QuaZipDir::operator=(const QuaZipDir &that) +{ + this->d = that.d; + return *this; +} + +QString QuaZipDir::operator[](int pos) const +{ + return entryList().at(pos); +} + +QuaZip::CaseSensitivity QuaZipDir::caseSensitivity() const +{ + return d->caseSensitivity; +} + +bool QuaZipDir::cd(const QString &directoryName) +{ + if (directoryName == "/") { + d->dir = ""; + return true; + } + QString dirName = directoryName; + if (dirName.endsWith('/')) + dirName.chop(1); + if (dirName.contains('/')) { + QuaZipDir dir(*this); + if (dirName.startsWith('/')) { +#ifdef QUAZIP_QUAZIPDIR_DEBUG + qDebug("QuaZipDir::cd(%s): going to /", + dirName.toUtf8().constData()); +#endif + if (!dir.cd("/")) + return false; + } + QStringList path = dirName.split('/', QString::SkipEmptyParts); + for (QStringList::const_iterator i = path.constBegin(); + i != path.end(); + ++i) { + const QString &step = *i; +#ifdef QUAZIP_QUAZIPDIR_DEBUG + qDebug("QuaZipDir::cd(%s): going to %s", + dirName.toUtf8().constData(), + step.toUtf8().constData()); +#endif + if (!dir.cd(step)) + return false; + } + d->dir = dir.path(); + return true; + } else { // no '/' + if (dirName == ".") { + return true; + } else if (dirName == "..") { + if (isRoot()) { + return false; + } else { + int slashPos = d->dir.lastIndexOf('/'); + if (slashPos == -1) { + d->dir = ""; + } else { + d->dir = d->dir.left(slashPos); + } + return true; + } + } else { // a simple subdirectory + if (exists(dirName)) { + if (isRoot()) + d->dir = dirName; + else + d->dir += "/" + dirName; + return true; + } else { + return false; + } + } + } +} + +bool QuaZipDir::cdUp() +{ + return cd(".."); +} + +uint QuaZipDir::count() const +{ + return entryList().count(); +} + +QString QuaZipDir::dirName() const +{ + return QDir(d->dir).dirName(); +} + +QuaZipFileInfo QuaZipDir_getFileInfo(QuaZip *zip, bool *ok, + const QString &relativeName, + bool isReal) +{ + QuaZipFileInfo info; + if (isReal) { + *ok = zip->getCurrentFileInfo(&info); + } else { + *ok = true; + info.compressedSize = 0; + info.crc = 0; + info.diskNumberStart = 0; + info.externalAttr = 0; + info.flags = 0; + info.internalAttr = 0; + info.method = 0; + info.uncompressedSize = 0; + info.versionCreated = info.versionNeeded = 0; + } + info.name = relativeName; + return info; +} + +template<typename TFileInfoList> +void QuaZipDir_convertInfoList(const QList<QuaZipFileInfo> &from, TFileInfoList &to); + +template<> +void QuaZipDir_convertInfoList(const QList<QuaZipFileInfo> &from, QList<QuaZipFileInfo> &to) +{ + to = from; +} + +template<> +void QuaZipDir_convertInfoList(const QList<QuaZipFileInfo> &from, QStringList &to) +{ + to.clear(); + for (QList<QuaZipFileInfo>::const_iterator i = from.constBegin(); + i != from.constEnd(); + ++i) { + to.append(i->name); + } +} + +// utility class to restore the current file +class QuaZipDirRestoreCurrent { +public: + inline QuaZipDirRestoreCurrent(QuaZip *zip): + zip(zip), currentFile(zip->getCurrentFileName()) {} + inline ~QuaZipDirRestoreCurrent() + { + zip->setCurrentFile(currentFile); + } +private: + QuaZip *zip; + QString currentFile; +}; + +class QuaZipDirComparator +{ + private: + QDir::SortFlags sort; + static QString getExtension(const QString &name); + int compareStrings(const QString &string1, const QString &string2); + public: + inline QuaZipDirComparator(QDir::SortFlags sort): sort(sort) {} + bool operator()(const QuaZipFileInfo &info1, const QuaZipFileInfo &info2); +}; + +QString QuaZipDirComparator::getExtension(const QString &name) +{ + if (name.endsWith('.') || name.indexOf('.', 1) == -1) { + return ""; + } else { + return name.mid(name.lastIndexOf('.') + 1); + } + +} + +int QuaZipDirComparator::compareStrings(const QString &string1, + const QString &string2) +{ + if (sort & QDir::LocaleAware) { + if (sort & QDir::IgnoreCase) { + return string1.toLower().localeAwareCompare(string2.toLower()); + } else { + return string1.localeAwareCompare(string2); + } + } else { + return string1.compare(string2, (sort & QDir::IgnoreCase) + ? Qt::CaseInsensitive : Qt::CaseSensitive); + } +} + +bool QuaZipDirComparator::operator()(const QuaZipFileInfo &info1, + const QuaZipFileInfo &info2) +{ + QDir::SortFlags order = sort + & (QDir::Name | QDir::Time | QDir::Size | QDir::Type); + if ((sort & QDir::DirsFirst) == QDir::DirsFirst + || (sort & QDir::DirsLast) == QDir::DirsLast) { + if (info1.name.endsWith('/') && !info2.name.endsWith('/')) + return (sort & QDir::DirsFirst) == QDir::DirsFirst; + else if (!info1.name.endsWith('/') && info2.name.endsWith('/')) + return (sort & QDir::DirsLast) == QDir::DirsLast; + } + bool result; + int extDiff; + switch (order) { + case QDir::Name: + result = compareStrings(info1.name, info2.name) < 0; + break; + case QDir::Type: + extDiff = compareStrings(getExtension(info1.name), + getExtension(info2.name)); + if (extDiff == 0) { + result = compareStrings(info1.name, info2.name) < 0; + } else { + result = extDiff < 0; + } + break; + case QDir::Size: + if (info1.uncompressedSize == info2.uncompressedSize) { + result = compareStrings(info1.name, info2.name) < 0; + } else { + result = info1.uncompressedSize < info2.uncompressedSize; + } + break; + case QDir::Time: + if (info1.dateTime == info2.dateTime) { + result = compareStrings(info1.name, info2.name) < 0; + } else { + result = info1.dateTime < info2.dateTime; + } + break; + default: + qWarning("QuaZipDirComparator(): Invalid sort mode 0x%2X", + static_cast<unsigned>(sort)); + return false; + } + return (sort & QDir::Reversed) ? !result : result; +} + +template<typename TFileInfoList> +bool QuaZipDirPrivate::entryInfoList(QStringList nameFilters, + QDir::Filters filter, QDir::SortFlags sort, TFileInfoList &result) const +{ + QString basePath = simplePath(); + if (!basePath.isEmpty()) + basePath += "/"; + int baseLength = basePath.length(); + result.clear(); + QuaZipDirRestoreCurrent saveCurrent(zip); + if (!zip->goToFirstFile()) { + return zip->getZipError() == UNZ_OK; + } + QDir::Filters fltr = filter; + if (fltr == QDir::NoFilter) + fltr = this->filter; + if (fltr == QDir::NoFilter) + fltr = QDir::AllEntries; + QStringList nmfltr = nameFilters; + if (nmfltr.isEmpty()) + nmfltr = this->nameFilters; + QSet<QString> dirsFound; + QList<QuaZipFileInfo> list; + do { + QString name = zip->getCurrentFileName(); + if (!name.startsWith(basePath)) + continue; + QString relativeName = name.mid(baseLength); + bool isDir = false; + bool isReal = true; + if (relativeName.contains('/')) { + int indexOfSlash = relativeName.indexOf('/'); + // something like "subdir/" + isReal = indexOfSlash == relativeName.length() - 1; + relativeName = relativeName.left(indexOfSlash + 1); + if (dirsFound.contains(relativeName)) + continue; + isDir = true; + } + dirsFound.insert(relativeName); + if ((fltr & QDir::Dirs) == 0 && isDir) + continue; + if ((fltr & QDir::Files) == 0 && !isDir) + continue; + if (!nmfltr.isEmpty() && QDir::match(nmfltr, relativeName)) + continue; + bool ok; + QuaZipFileInfo info = QuaZipDir_getFileInfo(zip, &ok, relativeName, + isReal); + if (!ok) { + return false; + } + list.append(info); + } while (zip->goToNextFile()); + QDir::SortFlags srt = sort; + if (srt == QDir::NoSort) + srt = sorting; +#ifdef QUAZIP_QUAZIPDIR_DEBUG + qDebug("QuaZipDirPrivate::entryInfoList(): before sort:"); + foreach (QuaZipFileInfo info, list) { + qDebug("%s\t%s", info.name.toUtf8().constData(), + info.dateTime.toString(Qt::ISODate).toUtf8().constData()); + } +#endif + if (srt != QDir::NoSort && (srt & QDir::Unsorted) != QDir::Unsorted) { + if (QuaZip::convertCaseSensitivity(caseSensitivity) + == Qt::CaseInsensitive) + srt |= QDir::IgnoreCase; + QuaZipDirComparator lessThan(srt); + qSort(list.begin(), list.end(), lessThan); + } + QuaZipDir_convertInfoList(list, result); + return true; +} + +QList<QuaZipFileInfo> QuaZipDir::entryInfoList(const QStringList &nameFilters, + QDir::Filters filters, QDir::SortFlags sort) const +{ + QList<QuaZipFileInfo> result; + if (d->entryInfoList(nameFilters, filters, sort, result)) + return result; + else + return QList<QuaZipFileInfo>(); +} + +QList<QuaZipFileInfo> QuaZipDir::entryInfoList(QDir::Filters filters, + QDir::SortFlags sort) const +{ + return entryInfoList(QStringList(), filters, sort); +} + +QStringList QuaZipDir::entryList(const QStringList &nameFilters, + QDir::Filters filters, QDir::SortFlags sort) const +{ + QStringList result; + if (d->entryInfoList(nameFilters, filters, sort, result)) + return result; + else + return QStringList(); +} + +QStringList QuaZipDir::entryList(QDir::Filters filters, + QDir::SortFlags sort) const +{ + return entryList(QStringList(), filters, sort); +} + +bool QuaZipDir::exists(const QString &filePath) const +{ + if (filePath == "/") + return true; + QString fileName = filePath; + if (fileName.endsWith('/')) + fileName.chop(1); + if (fileName.contains('/')) { + QFileInfo fileInfo(fileName); +#ifdef QUAZIP_QUAZIPDIR_DEBUG + qDebug("QuaZipDir::exists(): fileName=%s, fileInfo.fileName()=%s, " + "fileInfo.path()=%s", fileName.toUtf8().constData(), + fileInfo.fileName().toUtf8().constData(), + fileInfo.path().toUtf8().constData()); +#endif + QuaZipDir dir(*this); + return dir.cd(fileInfo.path()) && dir.exists(fileInfo.fileName()); + } else { + if (fileName == "..") { + return !isRoot(); + } else if (fileName == ".") { + return true; + } else { + QStringList entries = entryList(QDir::AllEntries, QDir::NoSort); +#ifdef QUAZIP_QUAZIPDIR_DEBUG + qDebug("QuaZipDir::exists(): looking for %s", + fileName.toUtf8().constData()); + for (QStringList::const_iterator i = entries.constBegin(); + i != entries.constEnd(); + ++i) { + qDebug("QuaZipDir::exists(): entry: %s", + i->toUtf8().constData()); + } +#endif + Qt::CaseSensitivity cs = QuaZip::convertCaseSensitivity( + d->caseSensitivity); + if (filePath.endsWith('/')) { + return entries.contains(filePath, cs); + } else { + return entries.contains(fileName, cs) + || entries.contains(fileName + "/", cs); + } + } + } +} + +bool QuaZipDir::exists() const +{ + QDir thisDir(d->dir); + return QuaZipDir(d->zip, thisDir.filePath("..")).exists(thisDir.dirName()); +} + +QString QuaZipDir::filePath(const QString &fileName) const +{ + return QDir(d->dir).filePath(fileName); +} + +QDir::Filters QuaZipDir::filter() +{ + return d->filter; +} + +bool QuaZipDir::isRoot() const +{ + return d->simplePath().isEmpty(); +} + +QStringList QuaZipDir::nameFilters() const +{ + return d->nameFilters; +} + +QString QuaZipDir::path() const +{ + return d->dir; +} + +QString QuaZipDir::relativeFilePath(const QString &fileName) const +{ + return QDir(d->dir).relativeFilePath(fileName); +} + +void QuaZipDir::setCaseSensitivity(QuaZip::CaseSensitivity caseSensitivity) +{ + d->caseSensitivity = caseSensitivity; +} + +void QuaZipDir::setFilter(QDir::Filters filters) +{ + d->filter = filters; +} + +void QuaZipDir::setNameFilters(const QStringList &nameFilters) +{ + d->nameFilters = nameFilters; +} + +void QuaZipDir::setPath(const QString &path) +{ + QString newDir = path; + if (newDir == "/") { + d->dir = ""; + } else { + if (newDir.endsWith('/')) + newDir.chop(1); + if (newDir.startsWith('/')) + newDir = newDir.mid(1); + d->dir = newDir; + } +} + +void QuaZipDir::setSorting(QDir::SortFlags sort) +{ + d->sorting = sort; +} + +QDir::SortFlags QuaZipDir::sorting() const +{ + return d->sorting; +} diff --git a/depends/quazip/quazipdir.h b/depends/quazip/quazipdir.h new file mode 100644 index 00000000..e2d70bc8 --- /dev/null +++ b/depends/quazip/quazipdir.h @@ -0,0 +1,171 @@ +#ifndef QUAZIP_QUAZIPDIR_H +#define QUAZIP_QUAZIPDIR_H + +class QuaZipDirPrivate; + +#include "quazip.h" +#include "quazipfileinfo.h" +#include <QDir> +#include <QList> +#include <QSharedDataPointer> + +/// Provides ZIP archive navigation. +/** +* This class is modelled after QDir, and is designed to provide similar +* features for ZIP archives. +* +* The only significant difference from QDir is that the root path is not +* '/', but an empty string since that's how the file paths are stored in +* the archive. However, QuaZipDir understands the paths starting with +* '/'. It is important in a few places: +* +* - In the cd() function. +* - In the constructor. +* - In the exists() function. +* +* Note that since ZIP uses '/' on all platforms, the '\' separator is +* not supported. +*/ +class QUAZIP_EXPORT QuaZipDir { +private: + QSharedDataPointer<QuaZipDirPrivate> d; +public: + /// The copy constructor. + QuaZipDir(const QuaZipDir &that); + /// Constructs a QuaZipDir instance pointing to the specified directory. + /** + If \a dir is not specified, points to the root of the archive. + The same happens if the \a dir is "/". + */ + QuaZipDir(QuaZip *zip, const QString &dir = QString()); + /// Destructor. + ~QuaZipDir(); + /// The assignment operator. + bool operator==(const QuaZipDir &that); + /// operator!= + /** + \return \c true if either this and \a that use different QuaZip + instances or if they point to different directories. + */ + inline bool operator!=(const QuaZipDir &that) {return !operator==(that);} + /// operator== + /** + \return \c true if both this and \a that use the same QuaZip + instance and point to the same directory. + */ + QuaZipDir& operator=(const QuaZipDir &that); + /// Returns the name of the entry at the specified position. + QString operator[](int pos) const; + /// Returns the current case sensitivity mode. + QuaZip::CaseSensitivity caseSensitivity() const; + /// Changes the 'current' directory. + /** + * If the path starts with '/', it is interpreted as an absolute + * path from the root of the archive. Otherwise, it is interpreted + * as a path relative to the current directory as was set by the + * previous cd() or the constructor. + * + * Note that the subsequent path() call will not return a path + * starting with '/' in all cases. + */ + bool cd(const QString &dirName); + /// Goes up. + bool cdUp(); + /// Returns the number of entries in the directory. + uint count() const; + /// Returns the current directory name. + /** + The name doesn't include the path. + */ + QString dirName() const; + /// Returns the list of the entries in the directory. + /** + \param nameFilters The list of file patterns to list, uses the same + syntax as QDir. + \param filters The entry type filters, only Files and Dirs are + accepted. + \param sort Sorting mode (not supported yet). + */ + QList<QuaZipFileInfo> entryInfoList(const QStringList &nameFilters, + QDir::Filters filters = QDir::NoFilter, + QDir::SortFlags sort = QDir::NoSort) const; + /// Returns the list of the entries in the directory. + /** + \overload + + The same as entryInfoList(QStringList(), filters, sort). + */ + QList<QuaZipFileInfo> entryInfoList(QDir::Filters filters = QDir::NoFilter, + QDir::SortFlags sort = QDir::NoSort) const; + /// Returns the list of the entry names in the directory. + /** + The same as entryInfoList(nameFilters, filters, sort), but only + returns entry names. + */ + QStringList entryList(const QStringList &nameFilters, + QDir::Filters filters = QDir::NoFilter, + QDir::SortFlags sort = QDir::NoSort) const; + /// Returns the list of the entry names in the directory. + /** + \overload + + The same as entryList(QStringList(), filters, sort). + */ + QStringList entryList(QDir::Filters filters = QDir::NoFilter, + QDir::SortFlags sort = QDir::NoSort) const; + /// Returns \c true if the entry with the specified name exists. + /** + The ".." is considered to exist if the current directory + is not root. The "." and "/" are considered to + always exist. Paths starting with "/" are relative to + the archive root, other paths are relative to the current dir. + */ + bool exists(const QString &fileName) const; + /// Return \c true if the directory pointed by this QuaZipDir exists. + bool exists() const; + /// Returns the full path to the specified file. + /** + Doesn't check if the file actually exists. + */ + QString filePath(const QString &fileName) const; + /// Returns the default filter. + QDir::Filters filter(); + /// Returns if the QuaZipDir points to the root of the archive. + /** + Not that the root path is the empty string, not '/'. + */ + bool isRoot() const; + /// Return the default name filter. + QStringList nameFilters() const; + /// Returns the path to the current dir. + /** + The path never starts with '/', and the root path is an empty + string. + */ + QString path() const; + /// Returns the path to the specified file relative to the current dir. + QString relativeFilePath(const QString &fileName) const; + /// Sets the default case sensitivity mode. + void setCaseSensitivity(QuaZip::CaseSensitivity caseSensitivity); + /// Sets the default filter. + void setFilter(QDir::Filters filters); + /// Sets the default name filter. + void setNameFilters(const QStringList &nameFilters); + /// Goes to the specified path. + /** + The difference from cd() is that this function never checks if the + path actually exists and doesn't use relative paths, so it's + possible to go to the root directory with setPath(""). + + Note that this function still chops the trailing and/or leading + '/' and treats a single '/' as the root path (path() will still + return an empty string). + */ + void setPath(const QString &path); + /// Sets the default sorting mode. + void setSorting(QDir::SortFlags sort); + /// Returns the default sorting mode. + QDir::SortFlags sorting() const; +}; + +#endif // QUAZIP_QUAZIPDIR_H diff --git a/depends/quazip/quazipfile.cpp b/depends/quazip/quazipfile.cpp new file mode 100644 index 00000000..323f815e --- /dev/null +++ b/depends/quazip/quazipfile.cpp @@ -0,0 +1,488 @@ +/* +Copyright (C) 2005-2011 Sergey A. Tachenov + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2 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 Lesser +General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +See COPYING file for the full LGPL text. + +Original ZIP package is copyrighted by Gilles Vollant, see +quazip/(un)zip.h files for details, basically it's zlib license. + **/ + +#include "quazipfile.h" + +using namespace std; + +/// The implementation class for QuaZip. +/** +\internal + +This class contains all the private stuff for the QuaZipFile class, thus +allowing to preserve binary compatibility between releases, the +technique known as the Pimpl (private implementation) idiom. +*/ +class QuaZipFilePrivate { + friend class QuaZipFile; + private: + /// The pointer to the associated QuaZipFile instance. + QuaZipFile *q; + /// The QuaZip object to work with. + QuaZip *zip; + /// The file name. + QString fileName; + /// Case sensitivity mode. + QuaZip::CaseSensitivity caseSensitivity; + /// Whether this file is opened in the raw mode. + bool raw; + /// Write position to keep track of. + /** + QIODevice::pos() is broken for non-seekable devices, so we need + our own position. + */ + qint64 writePos; + /// Uncompressed size to write along with a raw file. + ulong uncompressedSize; + /// CRC to write along with a raw file. + quint32 crc; + /// Whether \ref zip points to an internal QuaZip instance. + /** + This is true if the archive was opened by name, rather than by + supplying an existing QuaZip instance. + */ + bool internal; + /// The last error. + int zipError; + /// Resets \ref zipError. + inline void resetZipError() const {setZipError(UNZ_OK);} + /// Sets the zip error. + /** + This function is marked as const although it changes one field. + This allows to call it from const functions that don't change + anything by themselves. + */ + void setZipError(int zipError) const; + /// The constructor for the corresponding QuaZipFile constructor. + inline QuaZipFilePrivate(QuaZipFile *q): + q(q), zip(NULL), internal(true), zipError(UNZ_OK) {} + /// The constructor for the corresponding QuaZipFile constructor. + inline QuaZipFilePrivate(QuaZipFile *q, const QString &zipName): + q(q), internal(true), zipError(UNZ_OK) + { + zip=new QuaZip(zipName); + } + /// The constructor for the corresponding QuaZipFile constructor. + inline QuaZipFilePrivate(QuaZipFile *q, const QString &zipName, const QString &fileName, + QuaZip::CaseSensitivity cs): + q(q), internal(true), zipError(UNZ_OK) + { + zip=new QuaZip(zipName); + this->fileName=fileName; + if (this->fileName.startsWith('/')) + this->fileName = this->fileName.mid(1); + this->caseSensitivity=cs; + } + /// The constructor for the QuaZipFile constructor accepting a file name. + inline QuaZipFilePrivate(QuaZipFile *q, QuaZip *zip): + q(q), zip(zip), internal(false), zipError(UNZ_OK) {} + /// The destructor. + inline ~QuaZipFilePrivate() + { + if (internal) + delete zip; + } +}; + +QuaZipFile::QuaZipFile(): + p(new QuaZipFilePrivate(this)) +{ +} + +QuaZipFile::QuaZipFile(QObject *parent): + QIODevice(parent), + p(new QuaZipFilePrivate(this)) +{ +} + +QuaZipFile::QuaZipFile(const QString& zipName, QObject *parent): + QIODevice(parent), + p(new QuaZipFilePrivate(this, zipName)) +{ +} + +QuaZipFile::QuaZipFile(const QString& zipName, const QString& fileName, + QuaZip::CaseSensitivity cs, QObject *parent): + QIODevice(parent), + p(new QuaZipFilePrivate(this, zipName, fileName, cs)) +{ +} + +QuaZipFile::QuaZipFile(QuaZip *zip, QObject *parent): + QIODevice(parent), + p(new QuaZipFilePrivate(this, zip)) +{ +} + +QuaZipFile::~QuaZipFile() +{ + if (isOpen()) + close(); + delete p; +} + +QString QuaZipFile::getZipName() const +{ + return p->zip==NULL ? QString() : p->zip->getZipName(); +} + +QuaZip *QuaZipFile::getZip() const +{ + return p->internal ? NULL : p->zip; +} + +QString QuaZipFile::getActualFileName()const +{ + p->setZipError(UNZ_OK); + if (p->zip == NULL || (openMode() & WriteOnly)) + return QString(); + QString name=p->zip->getCurrentFileName(); + if(name.isNull()) + p->setZipError(p->zip->getZipError()); + return name; +} + +void QuaZipFile::setZipName(const QString& zipName) +{ + if(isOpen()) { + qWarning("QuaZipFile::setZipName(): file is already open - can not set ZIP name"); + return; + } + if(p->zip!=NULL && p->internal) + delete p->zip; + p->zip=new QuaZip(zipName); + p->internal=true; +} + +void QuaZipFile::setZip(QuaZip *zip) +{ + if(isOpen()) { + qWarning("QuaZipFile::setZip(): file is already open - can not set ZIP"); + return; + } + if(p->zip!=NULL && p->internal) + delete p->zip; + p->zip=zip; + p->fileName=QString(); + p->internal=false; +} + +void QuaZipFile::setFileName(const QString& fileName, QuaZip::CaseSensitivity cs) +{ + if(p->zip==NULL) { + qWarning("QuaZipFile::setFileName(): call setZipName() first"); + return; + } + if(!p->internal) { + qWarning("QuaZipFile::setFileName(): should not be used when not using internal QuaZip"); + return; + } + if(isOpen()) { + qWarning("QuaZipFile::setFileName(): can not set file name for already opened file"); + return; + } + p->fileName=fileName; + if (p->fileName.startsWith('/')) + p->fileName = p->fileName.mid(1); + p->caseSensitivity=cs; +} + +void QuaZipFilePrivate::setZipError(int zipError) const +{ + QuaZipFilePrivate *fakeThis = const_cast<QuaZipFilePrivate*>(this); // non-const + fakeThis->zipError=zipError; + if(zipError==UNZ_OK) + q->setErrorString(QString()); + else + q->setErrorString(q->tr("ZIP/UNZIP API error %1").arg(zipError)); +} + +bool QuaZipFile::open(OpenMode mode) +{ + return open(mode, NULL); +} + +bool QuaZipFile::open(OpenMode mode, int *method, int *level, bool raw, const char *password) +{ + p->resetZipError(); + if(isOpen()) { + qWarning("QuaZipFile::open(): already opened"); + return false; + } + if(mode&Unbuffered) { + qWarning("QuaZipFile::open(): Unbuffered mode is not supported"); + return false; + } + if((mode&ReadOnly)&&!(mode&WriteOnly)) { + if(p->internal) { + if(!p->zip->open(QuaZip::mdUnzip)) { + p->setZipError(p->zip->getZipError()); + return false; + } + if(!p->zip->setCurrentFile(p->fileName, p->caseSensitivity)) { + p->setZipError(p->zip->getZipError()); + p->zip->close(); + return false; + } + } else { + if(p->zip==NULL) { + qWarning("QuaZipFile::open(): zip is NULL"); + return false; + } + if(p->zip->getMode()!=QuaZip::mdUnzip) { + qWarning("QuaZipFile::open(): file open mode %d incompatible with ZIP open mode %d", + (int)mode, (int)p->zip->getMode()); + return false; + } + if(!p->zip->hasCurrentFile()) { + qWarning("QuaZipFile::open(): zip does not have current file"); + return false; + } + } + p->setZipError(unzOpenCurrentFile3(p->zip->getUnzFile(), method, level, (int)raw, password)); + if(p->zipError==UNZ_OK) { + setOpenMode(mode); + p->raw=raw; + return true; + } else + return false; + } + qWarning("QuaZipFile::open(): open mode %d not supported by this function", (int)mode); + return false; +} + +bool QuaZipFile::open(OpenMode mode, const QuaZipNewInfo& info, + const char *password, quint32 crc, + int method, int level, bool raw, + int windowBits, int memLevel, int strategy) +{ + zip_fileinfo info_z; + p->resetZipError(); + if(isOpen()) { + qWarning("QuaZipFile::open(): already opened"); + return false; + } + if((mode&WriteOnly)&&!(mode&ReadOnly)) { + if(p->internal) { + qWarning("QuaZipFile::open(): write mode is incompatible with internal QuaZip approach"); + return false; + } + if(p->zip==NULL) { + qWarning("QuaZipFile::open(): zip is NULL"); + return false; + } + if(p->zip->getMode()!=QuaZip::mdCreate&&p->zip->getMode()!=QuaZip::mdAppend&&p->zip->getMode()!=QuaZip::mdAdd) { + qWarning("QuaZipFile::open(): file open mode %d incompatible with ZIP open mode %d", + (int)mode, (int)p->zip->getMode()); + return false; + } + info_z.tmz_date.tm_year=info.dateTime.date().year(); + info_z.tmz_date.tm_mon=info.dateTime.date().month() - 1; + info_z.tmz_date.tm_mday=info.dateTime.date().day(); + info_z.tmz_date.tm_hour=info.dateTime.time().hour(); + info_z.tmz_date.tm_min=info.dateTime.time().minute(); + info_z.tmz_date.tm_sec=info.dateTime.time().second(); + info_z.dosDate = 0; + info_z.internal_fa=(uLong)info.internalAttr; + info_z.external_fa=(uLong)info.externalAttr; + if (!p->zip->isDataDescriptorWritingEnabled()) + zipClearFlags(p->zip->getZipFile(), ZIP_WRITE_DATA_DESCRIPTOR); + p->setZipError(zipOpenNewFileInZip3(p->zip->getZipFile(), + p->zip->getFileNameCodec()->fromUnicode(info.name).constData(), &info_z, + info.extraLocal.constData(), info.extraLocal.length(), + info.extraGlobal.constData(), info.extraGlobal.length(), + p->zip->getCommentCodec()->fromUnicode(info.comment).constData(), + method, level, (int)raw, + windowBits, memLevel, strategy, + password, (uLong)crc)); + if(p->zipError==UNZ_OK) { + p->writePos=0; + setOpenMode(mode); + p->raw=raw; + if(raw) { + p->crc=crc; + p->uncompressedSize=info.uncompressedSize; + } + return true; + } else + return false; + } + qWarning("QuaZipFile::open(): open mode %d not supported by this function", (int)mode); + return false; +} + +bool QuaZipFile::isSequential()const +{ + return true; +} + +qint64 QuaZipFile::pos()const +{ + if(p->zip==NULL) { + qWarning("QuaZipFile::pos(): call setZipName() or setZip() first"); + return -1; + } + if(!isOpen()) { + qWarning("QuaZipFile::pos(): file is not open"); + return -1; + } + if(openMode()&ReadOnly) + // QIODevice::pos() is broken for sequential devices, + // but thankfully bytesAvailable() returns the number of + // bytes buffered, so we know how far ahead we are. + return unztell(p->zip->getUnzFile()) - QIODevice::bytesAvailable(); + else + return p->writePos; +} + +bool QuaZipFile::atEnd()const +{ + if(p->zip==NULL) { + qWarning("QuaZipFile::atEnd(): call setZipName() or setZip() first"); + return false; + } + if(!isOpen()) { + qWarning("QuaZipFile::atEnd(): file is not open"); + return false; + } + if(openMode()&ReadOnly) + // the same problem as with pos() + return QIODevice::bytesAvailable() == 0 + && unzeof(p->zip->getUnzFile())==1; + else + return true; +} + +qint64 QuaZipFile::size()const +{ + if(!isOpen()) { + qWarning("QuaZipFile::atEnd(): file is not open"); + return -1; + } + if(openMode()&ReadOnly) + return p->raw?csize():usize(); + else + return p->writePos; +} + +qint64 QuaZipFile::csize()const +{ + unz_file_info info_z; + p->setZipError(UNZ_OK); + if(p->zip==NULL||p->zip->getMode()!=QuaZip::mdUnzip) return -1; + p->setZipError(unzGetCurrentFileInfo(p->zip->getUnzFile(), &info_z, NULL, 0, NULL, 0, NULL, 0)); + if(p->zipError!=UNZ_OK) + return -1; + return info_z.compressed_size; +} + +qint64 QuaZipFile::usize()const +{ + unz_file_info info_z; + p->setZipError(UNZ_OK); + if(p->zip==NULL||p->zip->getMode()!=QuaZip::mdUnzip) return -1; + p->setZipError(unzGetCurrentFileInfo(p->zip->getUnzFile(), &info_z, NULL, 0, NULL, 0, NULL, 0)); + if(p->zipError!=UNZ_OK) + return -1; + return info_z.uncompressed_size; +} + +bool QuaZipFile::getFileInfo(QuaZipFileInfo *info) +{ + if(p->zip==NULL||p->zip->getMode()!=QuaZip::mdUnzip) return false; + p->zip->getCurrentFileInfo(info); + p->setZipError(p->zip->getZipError()); + return p->zipError==UNZ_OK; +} + +void QuaZipFile::close() +{ + p->resetZipError(); + if(p->zip==NULL||!p->zip->isOpen()) return; + if(!isOpen()) { + qWarning("QuaZipFile::close(): file isn't open"); + return; + } + if(openMode()&ReadOnly) + p->setZipError(unzCloseCurrentFile(p->zip->getUnzFile())); + else if(openMode()&WriteOnly) + if(isRaw()) p->setZipError(zipCloseFileInZipRaw(p->zip->getZipFile(), p->uncompressedSize, p->crc)); + else p->setZipError(zipCloseFileInZip(p->zip->getZipFile())); + else { + qWarning("Wrong open mode: %d", (int)openMode()); + return; + } + if(p->zipError==UNZ_OK) setOpenMode(QIODevice::NotOpen); + else return; + if(p->internal) { + p->zip->close(); + p->setZipError(p->zip->getZipError()); + } +} + +qint64 QuaZipFile::readData(char *data, qint64 maxSize) +{ + p->setZipError(UNZ_OK); + qint64 bytesRead=unzReadCurrentFile(p->zip->getUnzFile(), data, (unsigned)maxSize); + if (bytesRead < 0) { + p->setZipError((int) bytesRead); + return -1; + } + return bytesRead; +} + +qint64 QuaZipFile::writeData(const char* data, qint64 maxSize) +{ + p->setZipError(ZIP_OK); + p->setZipError(zipWriteInFileInZip(p->zip->getZipFile(), data, (uint)maxSize)); + if(p->zipError!=ZIP_OK) return -1; + else { + p->writePos+=maxSize; + return maxSize; + } +} + +QString QuaZipFile::getFileName() const +{ + return p->fileName; +} + +QuaZip::CaseSensitivity QuaZipFile::getCaseSensitivity() const +{ + return p->caseSensitivity; +} + +bool QuaZipFile::isRaw() const +{ + return p->raw; +} + +int QuaZipFile::getZipError() const +{ + return p->zipError; +} + +qint64 QuaZipFile::bytesAvailable() const +{ + return size() - pos(); +} diff --git a/depends/quazip/quazipfile.h b/depends/quazip/quazipfile.h new file mode 100644 index 00000000..f6cc41a6 --- /dev/null +++ b/depends/quazip/quazipfile.h @@ -0,0 +1,442 @@ +#ifndef QUA_ZIPFILE_H +#define QUA_ZIPFILE_H + +/* +Copyright (C) 2005-2011 Sergey A. Tachenov + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2 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 Lesser +General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +See COPYING file for the full LGPL text. + +Original ZIP package is copyrighted by Gilles Vollant, see +quazip/(un)zip.h files for details, basically it's zlib license. + **/ + +#include <QIODevice> + +#include "quazip_global.h" +#include "quazip.h" +#include "quazipnewinfo.h" + +class QuaZipFilePrivate; + +/// A file inside ZIP archive. +/** \class QuaZipFile quazipfile.h <quazip/quazipfile.h> + * This is the most interesting class. Not only it provides C++ + * interface to the ZIP/UNZIP package, but also integrates it with Qt by + * subclassing QIODevice. This makes possible to access files inside ZIP + * archive using QTextStream or QDataStream, for example. Actually, this + * is the main purpose of the whole QuaZIP library. + * + * You can either use existing QuaZip instance to create instance of + * this class or pass ZIP archive file name to this class, in which case + * it will create internal QuaZip object. See constructors' descriptions + * for details. Writing is only possible with the existing instance. + * + * Note that due to the underlying library's limitation it is not + * possible to use multiple QuaZipFile instances to open several files + * in the same archive at the same time. If you need to write to + * multiple files in parallel, then you should write to temporary files + * first, then pack them all at once when you have finished writing. If + * you need to read multiple files inside the same archive in parallel, + * you should extract them all into a temporary directory first. + * + * \section quazipfile-sequential Sequential or random-access? + * + * At the first thought, QuaZipFile has fixed size, the start and the + * end and should be therefore considered random-access device. But + * there is one major obstacle to making it random-access: ZIP/UNZIP API + * does not support seek() operation and the only way to implement it is + * through reopening the file and re-reading to the required position, + * but this is prohibitively slow. + * + * Therefore, QuaZipFile is considered to be a sequential device. This + * has advantage of availability of the ungetChar() operation (QIODevice + * does not implement it properly for non-sequential devices unless they + * support seek()). Disadvantage is a somewhat strange behaviour of the + * size() and pos() functions. This should be kept in mind while using + * this class. + * + **/ +class QUAZIP_EXPORT QuaZipFile: public QIODevice { + friend class QuaZipFilePrivate; + Q_OBJECT + private: + QuaZipFilePrivate *p; + // these are not supported nor implemented + QuaZipFile(const QuaZipFile& that); + QuaZipFile& operator=(const QuaZipFile& that); + protected: + /// Implementation of the QIODevice::readData(). + qint64 readData(char *data, qint64 maxSize); + /// Implementation of the QIODevice::writeData(). + qint64 writeData(const char *data, qint64 maxSize); + public: + /// Constructs a QuaZipFile instance. + /** You should use setZipName() and setFileName() or setZip() before + * trying to call open() on the constructed object. + **/ + QuaZipFile(); + /// Constructs a QuaZipFile instance. + /** \a parent argument specifies this object's parent object. + * + * You should use setZipName() and setFileName() or setZip() before + * trying to call open() on the constructed object. + **/ + QuaZipFile(QObject *parent); + /// Constructs a QuaZipFile instance. + /** \a parent argument specifies this object's parent object and \a + * zipName specifies ZIP archive file name. + * + * You should use setFileName() before trying to call open() on the + * constructed object. + * + * QuaZipFile constructed by this constructor can be used for read + * only access. Use QuaZipFile(QuaZip*,QObject*) for writing. + **/ + QuaZipFile(const QString& zipName, QObject *parent =NULL); + /// Constructs a QuaZipFile instance. + /** \a parent argument specifies this object's parent object, \a + * zipName specifies ZIP archive file name and \a fileName and \a cs + * specify a name of the file to open inside archive. + * + * QuaZipFile constructed by this constructor can be used for read + * only access. Use QuaZipFile(QuaZip*,QObject*) for writing. + * + * \sa QuaZip::setCurrentFile() + **/ + QuaZipFile(const QString& zipName, const QString& fileName, + QuaZip::CaseSensitivity cs =QuaZip::csDefault, QObject *parent =NULL); + /// Constructs a QuaZipFile instance. + /** \a parent argument specifies this object's parent object. + * + * \a zip is the pointer to the existing QuaZip object. This + * QuaZipFile object then can be used to read current file in the + * \a zip or to write to the file inside it. + * + * \warning Using this constructor for reading current file can be + * tricky. Let's take the following example: + * \code + * QuaZip zip("archive.zip"); + * zip.open(QuaZip::mdUnzip); + * zip.setCurrentFile("file-in-archive"); + * QuaZipFile file(&zip); + * file.open(QIODevice::ReadOnly); + * // ok, now we can read from the file + * file.read(somewhere, some); + * zip.setCurrentFile("another-file-in-archive"); // oops... + * QuaZipFile anotherFile(&zip); + * anotherFile.open(QIODevice::ReadOnly); + * anotherFile.read(somewhere, some); // this is still ok... + * file.read(somewhere, some); // and this is NOT + * \endcode + * So, what exactly happens here? When we change current file in the + * \c zip archive, \c file that references it becomes invalid + * (actually, as far as I understand ZIP/UNZIP sources, it becomes + * closed, but QuaZipFile has no means to detect it). + * + * Summary: do not close \c zip object or change its current file as + * long as QuaZipFile is open. Even better - use another constructors + * which create internal QuaZip instances, one per object, and + * therefore do not cause unnecessary trouble. This constructor may + * be useful, though, if you already have a QuaZip instance and do + * not want to access several files at once. Good example: + * \code + * QuaZip zip("archive.zip"); + * zip.open(QuaZip::mdUnzip); + * // first, we need some information about archive itself + * QByteArray comment=zip.getComment(); + * // and now we are going to access files inside it + * QuaZipFile file(&zip); + * for(bool more=zip.goToFirstFile(); more; more=zip.goToNextFile()) { + * file.open(QIODevice::ReadOnly); + * // do something cool with file here + * file.close(); // do not forget to close! + * } + * zip.close(); + * \endcode + **/ + QuaZipFile(QuaZip *zip, QObject *parent =NULL); + /// Destroys a QuaZipFile instance. + /** Closes file if open, destructs internal QuaZip object (if it + * exists and \em is internal, of course). + **/ + virtual ~QuaZipFile(); + /// Returns the ZIP archive file name. + /** If this object was created by passing QuaZip pointer to the + * constructor, this function will return that QuaZip's file name + * (or null string if that object does not have file name yet). + * + * Otherwise, returns associated ZIP archive file name or null + * string if there are no name set yet. + * + * \sa setZipName() getFileName() + **/ + QString getZipName()const; + /// Returns a pointer to the associated QuaZip object. + /** Returns \c NULL if there is no associated QuaZip or it is + * internal (so you will not mess with it). + **/ + QuaZip* getZip()const; + /// Returns file name. + /** This function returns file name you passed to this object either + * by using + * QuaZipFile(const QString&,const QString&,QuaZip::CaseSensitivity,QObject*) + * or by calling setFileName(). Real name of the file may differ in + * case if you used case-insensitivity. + * + * Returns null string if there is no file name set yet. This is the + * case when this QuaZipFile operates on the existing QuaZip object + * (constructor QuaZipFile(QuaZip*,QObject*) or setZip() was used). + * + * \sa getActualFileName + **/ + QString getFileName() const; + /// Returns case sensitivity of the file name. + /** This function returns case sensitivity argument you passed to + * this object either by using + * QuaZipFile(const QString&,const QString&,QuaZip::CaseSensitivity,QObject*) + * or by calling setFileName(). + * + * Returns unpredictable value if getFileName() returns null string + * (this is the case when you did not used setFileName() or + * constructor above). + * + * \sa getFileName + **/ + QuaZip::CaseSensitivity getCaseSensitivity() const; + /// Returns the actual file name in the archive. + /** This is \em not a ZIP archive file name, but a name of file inside + * archive. It is not necessary the same name that you have passed + * to the + * QuaZipFile(const QString&,const QString&,QuaZip::CaseSensitivity,QObject*), + * setFileName() or QuaZip::setCurrentFile() - this is the real file + * name inside archive, so it may differ in case if the file name + * search was case-insensitive. + * + * Equivalent to calling getCurrentFileName() on the associated + * QuaZip object. Returns null string if there is no associated + * QuaZip object or if it does not have a current file yet. And this + * is the case if you called setFileName() but did not open the + * file yet. So this is perfectly fine: + * \code + * QuaZipFile file("somezip.zip"); + * file.setFileName("somefile"); + * QString name=file.getName(); // name=="somefile" + * QString actual=file.getActualFileName(); // actual is null string + * file.open(QIODevice::ReadOnly); + * QString actual=file.getActualFileName(); // actual can be "SoMeFiLe" on Windows + * \endcode + * + * \sa getZipName(), getFileName(), QuaZip::CaseSensitivity + **/ + QString getActualFileName()const; + /// Sets the ZIP archive file name. + /** Automatically creates internal QuaZip object and destroys + * previously created internal QuaZip object, if any. + * + * Will do nothing if this file is already open. You must close() it + * first. + **/ + void setZipName(const QString& zipName); + /// Returns \c true if the file was opened in raw mode. + /** If the file is not open, the returned value is undefined. + * + * \sa open(OpenMode,int*,int*,bool,const char*) + **/ + bool isRaw() const; + /// Binds to the existing QuaZip instance. + /** This function destroys internal QuaZip object, if any, and makes + * this QuaZipFile to use current file in the \a zip object for any + * further operations. See QuaZipFile(QuaZip*,QObject*) for the + * possible pitfalls. + * + * Will do nothing if the file is currently open. You must close() + * it first. + **/ + void setZip(QuaZip *zip); + /// Sets the file name. + /** Will do nothing if at least one of the following conditions is + * met: + * - ZIP name has not been set yet (getZipName() returns null + * string). + * - This QuaZipFile is associated with external QuaZip. In this + * case you should call that QuaZip's setCurrentFile() function + * instead! + * - File is already open so setting the name is meaningless. + * + * \sa QuaZip::setCurrentFile + **/ + void setFileName(const QString& fileName, QuaZip::CaseSensitivity cs =QuaZip::csDefault); + /// Opens a file for reading. + /** Returns \c true on success, \c false otherwise. + * Call getZipError() to get error code. + * + * \note Since ZIP/UNZIP API provides buffered reading only, + * QuaZipFile does not support unbuffered reading. So do not pass + * QIODevice::Unbuffered flag in \a mode, or open will fail. + **/ + virtual bool open(OpenMode mode); + /// Opens a file for reading. + /** \overload + * Argument \a password specifies a password to decrypt the file. If + * it is NULL then this function behaves just like open(OpenMode). + **/ + inline bool open(OpenMode mode, const char *password) + {return open(mode, NULL, NULL, false, password);} + /// Opens a file for reading. + /** \overload + * Argument \a password specifies a password to decrypt the file. + * + * An integers pointed by \a method and \a level will receive codes + * of the compression method and level used. See unzip.h. + * + * If raw is \c true then no decompression is performed. + * + * \a method should not be \c NULL. \a level can be \c NULL if you + * don't want to know the compression level. + **/ + bool open(OpenMode mode, int *method, int *level, bool raw, const char *password =NULL); + /// Opens a file for writing. + /** \a info argument specifies information about file. It should at + * least specify a correct file name. Also, it is a good idea to + * specify correct timestamp (by default, current time will be + * used). See QuaZipNewInfo. + * + * The \a password argument specifies the password for crypting. Pass NULL + * if you don't need any crypting. The \a crc argument was supposed + * to be used for crypting too, but then it turned out that it's + * false information, so you need to set it to 0 unless you want to + * use the raw mode (see below). + * + * Arguments \a method and \a level specify compression method and + * level. The only method supported is Z_DEFLATED, but you may also + * specify 0 for no compression. If all of the files in the archive + * use both method 0 and either level 0 is explicitly specified or + * data descriptor writing is disabled with + * QuaZip::setDataDescriptorWritingEnabled(), then the + * resulting archive is supposed to be compatible with the 1.0 ZIP + * format version, should you need that. Except for this, \a level + * has no other effects with method 0. + * + * If \a raw is \c true, no compression is performed. In this case, + * \a crc and uncompressedSize field of the \a info are required. + * + * Arguments \a windowBits, \a memLevel, \a strategy provide zlib + * algorithms tuning. See deflateInit2() in zlib. + **/ + bool open(OpenMode mode, const QuaZipNewInfo& info, + const char *password =NULL, quint32 crc =0, + int method =Z_DEFLATED, int level =Z_DEFAULT_COMPRESSION, bool raw =false, + int windowBits =-MAX_WBITS, int memLevel =DEF_MEM_LEVEL, int strategy =Z_DEFAULT_STRATEGY); + /// Returns \c true, but \ref quazipfile-sequential "beware"! + virtual bool isSequential()const; + /// Returns current position in the file. + /** Implementation of the QIODevice::pos(). When reading, this + * function is a wrapper to the ZIP/UNZIP unztell(), therefore it is + * unable to keep track of the ungetChar() calls (which is + * non-virtual and therefore is dangerous to reimplement). So if you + * are using ungetChar() feature of the QIODevice, this function + * reports incorrect value until you get back characters which you + * ungot. + * + * When writing, pos() returns number of bytes already written + * (uncompressed unless you use raw mode). + * + * \note Although + * \ref quazipfile-sequential "QuaZipFile is a sequential device" + * and therefore pos() should always return zero, it does not, + * because it would be misguiding. Keep this in mind. + * + * This function returns -1 if the file or archive is not open. + * + * Error code returned by getZipError() is not affected by this + * function call. + **/ + virtual qint64 pos()const; + /// Returns \c true if the end of file was reached. + /** This function returns \c false in the case of error. This means + * that you called this function on either not open file, or a file + * in the not open archive or even on a QuaZipFile instance that + * does not even have QuaZip instance associated. Do not do that + * because there is no means to determine whether \c false is + * returned because of error or because end of file was reached. + * Well, on the other side you may interpret \c false return value + * as "there is no file open to check for end of file and there is + * no end of file therefore". + * + * When writing, this function always returns \c true (because you + * are always writing to the end of file). + * + * Error code returned by getZipError() is not affected by this + * function call. + **/ + virtual bool atEnd()const; + /// Returns file size. + /** This function returns csize() if the file is open for reading in + * raw mode, usize() if it is open for reading in normal mode and + * pos() if it is open for writing. + * + * Returns -1 on error, call getZipError() to get error code. + * + * \note This function returns file size despite that + * \ref quazipfile-sequential "QuaZipFile is considered to be sequential device", + * for which size() should return bytesAvailable() instead. But its + * name would be very misguiding otherwise, so just keep in mind + * this inconsistence. + **/ + virtual qint64 size()const; + /// Returns compressed file size. + /** Equivalent to calling getFileInfo() and then getting + * compressedSize field, but more convenient and faster. + * + * File must be open for reading before calling this function. + * + * Returns -1 on error, call getZipError() to get error code. + **/ + qint64 csize()const; + /// Returns uncompressed file size. + /** Equivalent to calling getFileInfo() and then getting + * uncompressedSize field, but more convenient and faster. See + * getFileInfo() for a warning. + * + * File must be open for reading before calling this function. + * + * Returns -1 on error, call getZipError() to get error code. + **/ + qint64 usize()const; + /// Gets information about current file. + /** This function does the same thing as calling + * QuaZip::getCurrentFileInfo() on the associated QuaZip object, + * but you can not call getCurrentFileInfo() if the associated + * QuaZip is internal (because you do not have access to it), while + * you still can call this function in that case. + * + * File must be open for reading before calling this function. + * + * Returns \c false in the case of an error. + **/ + bool getFileInfo(QuaZipFileInfo *info); + /// Closes the file. + /** Call getZipError() to determine if the close was successful. + **/ + virtual void close(); + /// Returns the error code returned by the last ZIP/UNZIP API call. + int getZipError() const; + /// Returns the number of bytes available for reading. + virtual qint64 bytesAvailable() const; +}; + +#endif diff --git a/depends/quazip/quazipfileinfo.h b/depends/quazip/quazipfileinfo.h new file mode 100644 index 00000000..99540229 --- /dev/null +++ b/depends/quazip/quazipfileinfo.h @@ -0,0 +1,66 @@ +#ifndef QUA_ZIPFILEINFO_H +#define QUA_ZIPFILEINFO_H + +/* +Copyright (C) 2005-2011 Sergey A. Tachenov + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2 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 Lesser +General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +See COPYING file for the full LGPL text. + +Original ZIP package is copyrighted by Gilles Vollant, see +quazip/(un)zip.h files for details, basically it's zlib license. + **/ + +#include <QByteArray> +#include <QDateTime> + +#include "quazip_global.h" + +/// Information about a file inside archive. +/** Call QuaZip::getCurrentFileInfo() or QuaZipFile::getFileInfo() to + * fill this structure. */ +struct QUAZIP_EXPORT QuaZipFileInfo { + /// File name. + QString name; + /// Version created by. + quint16 versionCreated; + /// Version needed to extract. + quint16 versionNeeded; + /// General purpose flags. + quint16 flags; + /// Compression method. + quint16 method; + /// Last modification date and time. + QDateTime dateTime; + /// CRC. + quint32 crc; + /// Compressed file size. + quint32 compressedSize; + /// Uncompressed file size. + quint32 uncompressedSize; + /// Disk number start. + quint16 diskNumberStart; + /// Internal file attributes. + quint16 internalAttr; + /// External file attributes. + quint32 externalAttr; + /// Comment. + QString comment; + /// Extra field. + QByteArray extra; +}; + +#endif diff --git a/depends/quazip/quazipnewinfo.cpp b/depends/quazip/quazipnewinfo.cpp new file mode 100644 index 00000000..ed57e09f --- /dev/null +++ b/depends/quazip/quazipnewinfo.cpp @@ -0,0 +1,51 @@ +/* +Copyright (C) 2005-2011 Sergey A. Tachenov + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2 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 Lesser +General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +See COPYING file for the full LGPL text. + +Original ZIP package is copyrighted by Gilles Vollant, see +quazip/(un)zip.h files for details, basically it's zlib license. +*/ + +#include <QFileInfo> + +#include "quazipnewinfo.h" + + +QuaZipNewInfo::QuaZipNewInfo(const QString& name): + name(name), dateTime(QDateTime::currentDateTime()), internalAttr(0), externalAttr(0) +{ +} + +QuaZipNewInfo::QuaZipNewInfo(const QString& name, const QString& file): + name(name), internalAttr(0), externalAttr(0) +{ + QFileInfo info(file); + QDateTime lm = info.lastModified(); + if (!info.exists()) + dateTime = QDateTime::currentDateTime(); + else + dateTime = lm; +} + +void QuaZipNewInfo::setFileDateTime(const QString& file) +{ + QFileInfo info(file); + QDateTime lm = info.lastModified(); + if (info.exists()) + dateTime = lm; +} diff --git a/depends/quazip/quazipnewinfo.h b/depends/quazip/quazipnewinfo.h new file mode 100644 index 00000000..62159ea7 --- /dev/null +++ b/depends/quazip/quazipnewinfo.h @@ -0,0 +1,102 @@ +#ifndef QUA_ZIPNEWINFO_H +#define QUA_ZIPNEWINFO_H + +/* +Copyright (C) 2005-2011 Sergey A. Tachenov + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2 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 Lesser +General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +See COPYING file for the full LGPL text. + +Original ZIP package is copyrighted by Gilles Vollant, see +quazip/(un)zip.h files for details, basically it's zlib license. + **/ + +#include <QDateTime> +#include <QString> + +#include "quazip_global.h" + +/// Information about a file to be created. +/** This structure holds information about a file to be created inside + * ZIP archive. At least name should be set to something correct before + * passing this structure to + * QuaZipFile::open(OpenMode,const QuaZipNewInfo&,int,int,bool). + **/ +struct QUAZIP_EXPORT QuaZipNewInfo { + /// File name. + /** This field holds file name inside archive, including path relative + * to archive root. + **/ + QString name; + /// File timestamp. + /** This is the last file modification date and time. Will be stored + * in the archive central directory. It is a good practice to set it + * to the source file timestamp instead of archive creating time. Use + * setFileDateTime() or QuaZipNewInfo(const QString&, const QString&). + **/ + QDateTime dateTime; + /// File internal attributes. + quint16 internalAttr; + /// File external attributes. + quint32 externalAttr; + /// File comment. + /** Will be encoded using QuaZip::getCommentCodec(). + **/ + QString comment; + /// File local extra field. + QByteArray extraLocal; + /// File global extra field. + QByteArray extraGlobal; + /// Uncompressed file size. + /** This is only needed if you are using raw file zipping mode, i. e. + * adding precompressed file in the zip archive. + **/ + ulong uncompressedSize; + /// Constructs QuaZipNewInfo instance. + /** Initializes name with \a name, dateTime with current date and + * time. Attributes are initialized with zeros, comment and extra + * field with null values. + **/ + QuaZipNewInfo(const QString& name); + /// Constructs QuaZipNewInfo instance. + /** Initializes name with \a name and dateTime with timestamp of the + * file named \a file. If the \a file does not exists or its timestamp + * is inaccessible (e. g. you do not have read permission for the + * directory file in), uses current date and time. Attributes are + * initialized with zeros, comment and extra field with null values. + * + * \sa setFileDateTime() + **/ + QuaZipNewInfo(const QString& name, const QString& file); + /// Sets the file timestamp from the existing file. + /** Use this function to set the file timestamp from the existing + * file. Use it like this: + * \code + * QuaZipFile zipFile(&zip); + * QFile file("file-to-add"); + * file.open(QIODevice::ReadOnly); + * QuaZipNewInfo info("file-name-in-archive"); + * info.setFileDateTime("file-to-add"); // take the timestamp from file + * zipFile.open(QIODevice::WriteOnly, info); + * \endcode + * + * This function does not change dateTime if some error occured (e. g. + * file is inaccessible). + **/ + void setFileDateTime(const QString& file); +}; + +#endif diff --git a/depends/quazip/unzip.c b/depends/quazip/unzip.c new file mode 100644 index 00000000..52bc081f --- /dev/null +++ b/depends/quazip/unzip.c @@ -0,0 +1,1603 @@ +/* unzip.c -- IO for uncompress .zip files using zlib + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + Read unzip.h for more info + + Modified by Sergey A. Tachenov to integrate with Qt. +*/ + +/* Decryption code comes from crypt.c by Info-ZIP but has been greatly reduced in terms of +compatibility with older software. The following is from the original crypt.c. Code +woven in by Terry Thorsen 1/2003. +*/ +/* + Copyright (c) 1990-2000 Info-ZIP. All rights reserved. + + See the accompanying file LICENSE, version 2000-Apr-09 or later + (the contents of which are also included in zip.h) for terms of use. + If, for some reason, all these files are missing, the Info-ZIP license + also may be found at: ftp://ftp.info-zip.org/pub/infozip/license.html +*/ +/* + crypt.c (full version) by Info-ZIP. Last revised: [see crypt.h] + + The encryption/decryption parts of this source code (as opposed to the + non-echoing password parts) were originally written in Europe. The + whole source package can be freely distributed, including from the USA. + (Prior to January 2000, re-export from the US was a violation of US law.) + */ + +/* + This encryption code is a direct transcription of the algorithm from + Roger Schlafly, described by Phil Katz in the file appnote.txt. This + file (appnote.txt) is distributed with the PKZIP program (even in the + version without encryption capabilities). + */ + + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "zlib.h" +#include "unzip.h" + +#ifdef STDC +# include <stddef.h> +# include <string.h> +# include <stdlib.h> +#endif +#ifdef NO_ERRNO_H + extern int errno; +#else +# include <errno.h> +#endif + + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + + +#ifndef CASESENSITIVITYDEFAULT_NO +# if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) +# define CASESENSITIVITYDEFAULT_NO +# endif +#endif + + +#ifndef UNZ_BUFSIZE +#define UNZ_BUFSIZE (16384) +#endif + +#ifndef UNZ_MAXFILENAMEINZIP +#define UNZ_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (malloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) free(p);} +#endif + +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) + + + + +const char unz_copyright[] = + " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; + +/* unz_file_info_interntal contain internal info about a file in zipfile*/ +typedef struct unz_file_info_internal_s +{ + uLong offset_curfile;/* relative offset of local header 4 bytes */ +} unz_file_info_internal; + + +/* file_in_zip_read_info_s contain internal information about a file in zipfile, + when reading and decompress it */ +typedef struct +{ + char *read_buffer; /* internal buffer for compressed data */ + z_stream stream; /* zLib stream structure for inflate */ + + uLong pos_in_zipfile; /* position in byte on the zipfile, for fseek*/ + uLong stream_initialised; /* flag set if stream structure is initialised*/ + + uLong offset_local_extrafield;/* offset of the local extra field */ + uInt size_local_extrafield;/* size of the local extra field */ + uLong pos_local_extrafield; /* position in the local extra field in read*/ + + uLong crc32; /* crc32 of all data uncompressed */ + uLong crc32_wait; /* crc32 we must obtain after decompress all */ + uLong rest_read_compressed; /* number of byte to be decompressed */ + uLong rest_read_uncompressed;/*number of byte to be obtained after decomp*/ + zlib_filefunc_def z_filefunc; + voidpf filestream; /* io structore of the zipfile */ + uLong compression_method; /* compression method (0==store) */ + uLong byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + int raw; +} file_in_zip_read_info_s; + + +/* unz_s contain internal information about the zipfile +*/ +typedef struct +{ + zlib_filefunc_def z_filefunc; + voidpf filestream; /* io structore of the zipfile */ + unz_global_info gi; /* public global information */ + uLong byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + uLong num_file; /* number of the current file in the zipfile*/ + uLong pos_in_central_dir; /* pos of the current file in the central dir*/ + uLong current_file_ok; /* flag about the usability of the current file*/ + uLong central_pos; /* position of the beginning of the central dir*/ + + uLong size_central_dir; /* size of the central directory */ + uLong offset_central_dir; /* offset of start of central directory with + respect to the starting disk number */ + + unz_file_info cur_file_info; /* public info about the current file in zip*/ + unz_file_info_internal cur_file_info_internal; /* private info about it*/ + file_in_zip_read_info_s* pfile_in_zip_read; /* structure about the current + file if we are decompressing it */ + int encrypted; +# ifndef NOUNCRYPT + unsigned long keys[3]; /* keys defining the pseudo-random sequence */ + const unsigned long* pcrc_32_tab; +# endif +} unz_s; + + +#ifndef NOUNCRYPT +#include "crypt.h" +#endif + +/* =========================================================================== + Read a byte from a gz_stream; update next_in and avail_in. Return EOF + for end of file. + IN assertion: the stream s has been sucessfully opened for reading. +*/ + + +local int unzlocal_getByte OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + int *pi)); + +local int unzlocal_getByte(pzlib_filefunc_def,filestream,pi) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + int *pi; +{ + unsigned char c; + int err = (int)ZREAD(*pzlib_filefunc_def,filestream,&c,1); + if (err==1) + { + *pi = (int)c; + return UNZ_OK; + } + else + { + if (ZERROR(*pzlib_filefunc_def,filestream)) + return UNZ_ERRNO; + else + return UNZ_EOF; + } +} + + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +local int unzlocal_getShort OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int unzlocal_getShort (pzlib_filefunc_def,filestream,pX) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + uLong *pX; +{ + uLong x ; + int i; + int err; + + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int unzlocal_getLong OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int unzlocal_getLong (pzlib_filefunc_def,filestream,pX) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + uLong *pX; +{ + uLong x ; + int i; + int err; + + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==UNZ_OK) + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<16; + + if (err==UNZ_OK) + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<24; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + + +/* My own strcmpi / strcasecmp */ +local int strcmpcasenosensitive_internal (fileName1,fileName2) + const char* fileName1; + const char* fileName2; +{ + for (;;) + { + char c1=*(fileName1++); + char c2=*(fileName2++); + if ((c1>='a') && (c1<='z')) + c1 -= 0x20; + if ((c2>='a') && (c2<='z')) + c2 -= 0x20; + if (c1=='\0') + return ((c2=='\0') ? 0 : -1); + if (c2=='\0') + return 1; + if (c1<c2) + return -1; + if (c1>c2) + return 1; + } +} + + +#ifdef CASESENSITIVITYDEFAULT_NO +#define CASESENSITIVITYDEFAULTVALUE 2 +#else +#define CASESENSITIVITYDEFAULTVALUE 1 +#endif + +#ifndef STRCMPCASENOSENTIVEFUNCTION +#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal +#endif + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) + +*/ +extern int ZEXPORT unzStringFileNameCompare (fileName1,fileName2,iCaseSensitivity) + const char* fileName1; + const char* fileName2; + int iCaseSensitivity; +{ + if (iCaseSensitivity==0) + iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; + + if (iCaseSensitivity==1) + return strcmp(fileName1,fileName2); + + return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); +} + +#ifndef BUFREADCOMMENT +#define BUFREADCOMMENT (0x400) +#endif + +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +local uLong unzlocal_SearchCentralDir OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream)); + +local uLong unzlocal_SearchCentralDir(pzlib_filefunc_def,filestream) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; +{ + unsigned char* buf; + uLong uSizeFile; + uLong uBackRead; + uLong uMaxBack=0xffff; /* maximum size of global comment */ + uLong uPosFound=0; + + if (ZSEEK(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + + uSizeFile = ZTELL(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackRead<uMaxBack) + { + uLong uReadSize,uReadPos ; + int i; + if (uBackRead+BUFREADCOMMENT>uMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uSizeFile-uReadPos); + if (ZSEEK(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\test\\zlib114.zip" or on an Unix computer + "zlib/zlib114.zip". + If the zipfile cannot be opened (file doesn't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ +extern unzFile ZEXPORT unzOpen2 (file, pzlib_filefunc_def) + voidpf file; + zlib_filefunc_def* pzlib_filefunc_def; +{ + unz_s us; + unz_s *s; + uLong central_pos,uL; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + uLong number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + + int err=UNZ_OK; + + if (unz_copyright[0]!=' ') + return NULL; + + if (pzlib_filefunc_def==NULL) + fill_qiodevice_filefunc(&us.z_filefunc); + else + us.z_filefunc = *pzlib_filefunc_def; + + us.filestream= (*(us.z_filefunc.zopen_file))(us.z_filefunc.opaque, + file, + ZLIB_FILEFUNC_MODE_READ | + ZLIB_FILEFUNC_MODE_EXISTING); + if (us.filestream==NULL) + return NULL; + + central_pos = unzlocal_SearchCentralDir(&us.z_filefunc,us.filestream); + if (central_pos==0) + err=UNZ_ERRNO; + + if (ZSEEK(us.z_filefunc, us.filestream, + central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + /* the signature, already checked */ + if (unzlocal_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of this disk */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of the disk with the start of the central directory */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir on this disk */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&us.gi.number_entry)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_entry_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((number_entry_CD!=us.gi.number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=UNZ_BADZIPFILE; + + /* size of the central directory */ + if (unzlocal_getLong(&us.z_filefunc, us.filestream,&us.size_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (unzlocal_getLong(&us.z_filefunc, us.filestream,&us.offset_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* zipfile comment length */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&us.gi.size_comment)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((central_pos<us.offset_central_dir+us.size_central_dir) && + (err==UNZ_OK)) + err=UNZ_BADZIPFILE; + + if (err!=UNZ_OK) + { + ZCLOSE(us.z_filefunc, us.filestream); + return NULL; + } + + us.byte_before_the_zipfile = central_pos - + (us.offset_central_dir+us.size_central_dir); + us.central_pos = central_pos; + us.pfile_in_zip_read = NULL; + us.encrypted = 0; + + + s=(unz_s*)ALLOC(sizeof(unz_s)); + *s=us; + unzGoToFirstFile((unzFile)s); + return (unzFile)s; +} + + +extern unzFile ZEXPORT unzOpen (file) + voidpf file; +{ + return unzOpen2(file, NULL); +} + +/* + Close a ZipFile opened with unzipOpen. + If there is files inside the .Zip opened with unzipOpenCurrentFile (see later), + these files MUST be closed with unzipCloseCurrentFile before call unzipClose. + return UNZ_OK if there is no problem. */ +extern int ZEXPORT unzClose (file) + unzFile file; +{ + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + if (s->pfile_in_zip_read!=NULL) + unzCloseCurrentFile(file); + + ZCLOSE(s->z_filefunc, s->filestream); + TRYFREE(s); + return UNZ_OK; +} + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ +extern int ZEXPORT unzGetGlobalInfo (file,pglobal_info) + unzFile file; + unz_global_info *pglobal_info; +{ + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + *pglobal_info=s->gi; + return UNZ_OK; +} + + +/* + Translate date/time from Dos format to tm_unz (readable more easilty) +*/ +local void unzlocal_DosDateToTmuDate (ulDosDate, ptm) + uLong ulDosDate; + tm_unz* ptm; +{ + uLong uDate; + uDate = (uLong)(ulDosDate>>16); + ptm->tm_mday = (uInt)(uDate&0x1f) ; + ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; + ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; + + ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); + ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; + ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; +} + +/* + Get Info about the current file in the zipfile, with internal only info +*/ +local int unzlocal_GetCurrentFileInfoInternal OF((unzFile file, + unz_file_info *pfile_info, + unz_file_info_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); + +local int unzlocal_GetCurrentFileInfoInternal (file, + pfile_info, + pfile_info_internal, + szFileName, fileNameBufferSize, + extraField, extraFieldBufferSize, + szComment, commentBufferSize) + unzFile file; + unz_file_info *pfile_info; + unz_file_info_internal *pfile_info_internal; + char *szFileName; + uLong fileNameBufferSize; + void *extraField; + uLong extraFieldBufferSize; + char *szComment; + uLong commentBufferSize; +{ + unz_s* s; + unz_file_info file_info; + unz_file_info_internal file_info_internal; + int err=UNZ_OK; + uLong uMagic; + uLong uSeek=0; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (ZSEEK(s->z_filefunc, s->filestream, + s->pos_in_central_dir+s->byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + + /* we check the magic */ + if (err==UNZ_OK) { + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x02014b50) + err=UNZ_BADZIPFILE; + } + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.version) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.version_needed) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.flag) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.compression_method) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.dosDate) != UNZ_OK) + err=UNZ_ERRNO; + + unzlocal_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.crc) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.compressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.uncompressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_filename) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_extra) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_comment) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.disk_num_start) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.internal_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.external_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info_internal.offset_curfile) != UNZ_OK) + err=UNZ_ERRNO; + + uSeek+=file_info.size_filename; + if ((err==UNZ_OK) && (szFileName!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_filename<fileNameBufferSize) + { + *(szFileName+file_info.size_filename)='\0'; + uSizeRead = file_info.size_filename; + } + else + uSizeRead = fileNameBufferSize; + + if ((file_info.size_filename>0) && (fileNameBufferSize>0)) + if (ZREAD(s->z_filefunc, s->filestream,szFileName,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + uSeek -= uSizeRead; + } + + + if ((err==UNZ_OK) && (extraField!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_extra<extraFieldBufferSize) + uSizeRead = file_info.size_file_extra; + else + uSizeRead = extraFieldBufferSize; + + if (uSeek!=0) { + if (ZSEEK(s->z_filefunc, s->filestream,uSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + uSeek=0; + else + err=UNZ_ERRNO; + } + if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) + if (ZREAD(s->z_filefunc, s->filestream,extraField,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + uSeek += file_info.size_file_extra - uSizeRead; + } + else + uSeek+=file_info.size_file_extra; + + + if ((err==UNZ_OK) && (szComment!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_comment<commentBufferSize) + { + *(szComment+file_info.size_file_comment)='\0'; + uSizeRead = file_info.size_file_comment; + } + else + uSizeRead = commentBufferSize; + + if (uSeek!=0) { + if (ZSEEK(s->z_filefunc, s->filestream,uSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + uSeek=0; + else + err=UNZ_ERRNO; + } + if ((file_info.size_file_comment>0) && (commentBufferSize>0)) + if (ZREAD(s->z_filefunc, s->filestream,szComment,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + uSeek+=file_info.size_file_comment - uSizeRead; + } + else + uSeek+=file_info.size_file_comment; + + if ((err==UNZ_OK) && (pfile_info!=NULL)) + *pfile_info=file_info; + + if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) + *pfile_info_internal=file_info_internal; + + return err; +} + + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. +*/ +extern int ZEXPORT unzGetCurrentFileInfo (file, + pfile_info, + szFileName, fileNameBufferSize, + extraField, extraFieldBufferSize, + szComment, commentBufferSize) + unzFile file; + unz_file_info *pfile_info; + char *szFileName; + uLong fileNameBufferSize; + void *extraField; + uLong extraFieldBufferSize; + char *szComment; + uLong commentBufferSize; +{ + return unzlocal_GetCurrentFileInfoInternal(file,pfile_info,NULL, + szFileName,fileNameBufferSize, + extraField,extraFieldBufferSize, + szComment,commentBufferSize); +} + +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ +extern int ZEXPORT unzGoToFirstFile (file) + unzFile file; +{ + int err=UNZ_OK; + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + s->pos_in_central_dir=s->offset_central_dir; + s->num_file=0; + err=unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ +extern int ZEXPORT unzGoToNextFile (file) + unzFile file; +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + if (s->gi.number_entry != 0xffff) /* 2^16 files overflow hack */ + if (s->num_file+1==s->gi.number_entry) + return UNZ_END_OF_LIST_OF_FILE; + + s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; + s->num_file++; + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzipStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ +extern int ZEXPORT unzLocateFile (file, szFileName, iCaseSensitivity) + unzFile file; + const char *szFileName; + int iCaseSensitivity; +{ + unz_s* s; + int err; + + /* We remember the 'current' position in the file so that we can jump + * back there if we fail. + */ + unz_file_info cur_file_infoSaved; + unz_file_info_internal cur_file_info_internalSaved; + uLong num_fileSaved; + uLong pos_in_central_dirSaved; + + + if (file==NULL) + return UNZ_PARAMERROR; + + if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) + return UNZ_PARAMERROR; + + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + /* Save the current state */ + num_fileSaved = s->num_file; + pos_in_central_dirSaved = s->pos_in_central_dir; + cur_file_infoSaved = s->cur_file_info; + cur_file_info_internalSaved = s->cur_file_info_internal; + + err = unzGoToFirstFile(file); + + while (err == UNZ_OK) + { + char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; + err = unzGetCurrentFileInfo(file,NULL, + szCurrentFileName,sizeof(szCurrentFileName)-1, + NULL,0,NULL,0); + if (err == UNZ_OK) + { + if (unzStringFileNameCompare(szCurrentFileName, + szFileName,iCaseSensitivity)==0) + return UNZ_OK; + err = unzGoToNextFile(file); + } + } + + /* We failed, so restore the state of the 'current file' to where we + * were. + */ + s->num_file = num_fileSaved ; + s->pos_in_central_dir = pos_in_central_dirSaved ; + s->cur_file_info = cur_file_infoSaved; + s->cur_file_info_internal = cur_file_info_internalSaved; + return err; +} + + +/* +/////////////////////////////////////////// +// Contributed by Ryan Haksi (mailto://cryogen@infoserve.net) +// I need random access +// +// Further optimization could be realized by adding an ability +// to cache the directory in memory. The goal being a single +// comprehensive file read to put the file I need in a memory. +*/ + +/* +typedef struct unz_file_pos_s +{ + uLong pos_in_zip_directory; // offset in file + uLong num_of_file; // # of file +} unz_file_pos; +*/ + +extern int ZEXPORT unzGetFilePos(file, file_pos) + unzFile file; + unz_file_pos* file_pos; +{ + unz_s* s; + + if (file==NULL || file_pos==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + file_pos->pos_in_zip_directory = s->pos_in_central_dir; + file_pos->num_of_file = s->num_file; + + return UNZ_OK; +} + +extern int ZEXPORT unzGoToFilePos(file, file_pos) + unzFile file; + unz_file_pos* file_pos; +{ + unz_s* s; + int err; + + if (file==NULL || file_pos==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + /* jump to the right spot */ + s->pos_in_central_dir = file_pos->pos_in_zip_directory; + s->num_file = file_pos->num_of_file; + + /* set the current file */ + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + /* return results */ + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* +// Unzip Helper Functions - should be here? +/////////////////////////////////////////// +*/ + +/* + Read the local header of the current zipfile + Check the coherency of the local header and info in the end of central + directory about this file + store in *piSizeVar the size of extra info in local header + (filename and size of extra field data) +*/ +local int unzlocal_CheckCurrentFileCoherencyHeader (s,piSizeVar, + poffset_local_extrafield, + psize_local_extrafield) + unz_s* s; + uInt* piSizeVar; + uLong *poffset_local_extrafield; + uInt *psize_local_extrafield; +{ + uLong uMagic,uData,uFlags; + uLong size_filename; + uLong size_extra_field; + int err=UNZ_OK; + + *piSizeVar = 0; + *poffset_local_extrafield = 0; + *psize_local_extrafield = 0; + + if (ZSEEK(s->z_filefunc, s->filestream,s->cur_file_info_internal.offset_curfile + + s->byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + + if (err==UNZ_OK) { + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x04034b50) + err=UNZ_BADZIPFILE; + } + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) + err=UNZ_ERRNO; +/* + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) + err=UNZ_BADZIPFILE; +*/ + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uFlags) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) + err=UNZ_BADZIPFILE; + + if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* date/time */ + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* crc */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size compr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size uncompr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&size_filename) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) + err=UNZ_BADZIPFILE; + + *piSizeVar += (uInt)size_filename; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&size_extra_field) != UNZ_OK) + err=UNZ_ERRNO; + *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + + SIZEZIPLOCALHEADER + size_filename; + *psize_local_extrafield = (uInt)size_extra_field; + + *piSizeVar += (uInt)size_extra_field; + + return err; +} + +/* + Open for reading data the current file in the zipfile. + If there is no error and the file is opened, the return value is UNZ_OK. +*/ +extern int ZEXPORT unzOpenCurrentFile3 (file, method, level, raw, password) + unzFile file; + int* method; + int* level; + int raw; + const char* password; +{ + int err=UNZ_OK; + uInt iSizeVar; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uLong offset_local_extrafield; /* offset of the local extra field */ + uInt size_local_extrafield; /* size of the local extra field */ +# ifndef NOUNCRYPT + char source[12]; +# else + if (password != NULL) + return UNZ_PARAMERROR; +# endif + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_PARAMERROR; + + if (s->pfile_in_zip_read != NULL) + unzCloseCurrentFile(file); + + if (unzlocal_CheckCurrentFileCoherencyHeader(s,&iSizeVar, + &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) + return UNZ_BADZIPFILE; + + pfile_in_zip_read_info = (file_in_zip_read_info_s*) + ALLOC(sizeof(file_in_zip_read_info_s)); + if (pfile_in_zip_read_info==NULL) + return UNZ_INTERNALERROR; + + pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE); + pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; + pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; + pfile_in_zip_read_info->pos_local_extrafield=0; + pfile_in_zip_read_info->raw=raw; + + if (pfile_in_zip_read_info->read_buffer==NULL) + { + TRYFREE(pfile_in_zip_read_info); + return UNZ_INTERNALERROR; + } + + pfile_in_zip_read_info->stream_initialised=0; + + if (method!=NULL) + *method = (int)s->cur_file_info.compression_method; + + if (level!=NULL) + { + *level = 6; + switch (s->cur_file_info.flag & 0x06) + { + case 6 : *level = 1; break; + case 4 : *level = 2; break; + case 2 : *level = 9; break; + } + } + + if ((s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + + pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; + pfile_in_zip_read_info->crc32=0; + pfile_in_zip_read_info->compression_method = + s->cur_file_info.compression_method; + pfile_in_zip_read_info->filestream=s->filestream; + pfile_in_zip_read_info->z_filefunc=s->z_filefunc; + pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; + + pfile_in_zip_read_info->stream.total_out = 0; + + if ((s->cur_file_info.compression_method==Z_DEFLATED) && + (!raw)) + { + pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; + pfile_in_zip_read_info->stream.zfree = (free_func)0; + pfile_in_zip_read_info->stream.opaque = (voidpf)0; + pfile_in_zip_read_info->stream.next_in = (voidpf)0; + pfile_in_zip_read_info->stream.avail_in = 0; + + err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised=1; + else + { + TRYFREE(pfile_in_zip_read_info); + return err; + } + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + } + pfile_in_zip_read_info->rest_read_compressed = + s->cur_file_info.compressed_size ; + pfile_in_zip_read_info->rest_read_uncompressed = + s->cur_file_info.uncompressed_size ; + + + pfile_in_zip_read_info->pos_in_zipfile = + s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + + iSizeVar; + + pfile_in_zip_read_info->stream.avail_in = (uInt)0; + + s->pfile_in_zip_read = pfile_in_zip_read_info; + +# ifndef NOUNCRYPT + if (password != NULL) + { + int i; + s->pcrc_32_tab = (const unsigned long*) get_crc_table(); + init_keys(password,s->keys,s->pcrc_32_tab); + if (ZSEEK(s->z_filefunc, s->filestream, + s->pfile_in_zip_read->pos_in_zipfile + + s->pfile_in_zip_read->byte_before_the_zipfile, + SEEK_SET)!=0) + return UNZ_INTERNALERROR; + if(ZREAD(s->z_filefunc, s->filestream,source, 12)<12) + return UNZ_INTERNALERROR; + + for (i = 0; i<12; i++) + zdecode(s->keys,s->pcrc_32_tab,source[i]); + + s->pfile_in_zip_read->pos_in_zipfile+=12; + s->encrypted=1; + } +# endif + + + return UNZ_OK; +} + +extern int ZEXPORT unzOpenCurrentFile (file) + unzFile file; +{ + return unzOpenCurrentFile3(file, NULL, NULL, 0, NULL); +} + +extern int ZEXPORT unzOpenCurrentFilePassword (file, password) + unzFile file; + const char* password; +{ + return unzOpenCurrentFile3(file, NULL, NULL, 0, password); +} + +extern int ZEXPORT unzOpenCurrentFile2 (file,method,level,raw) + unzFile file; + int* method; + int* level; + int raw; +{ + return unzOpenCurrentFile3(file, method, level, raw, NULL); +} + +/* + Read bytes from the current file. + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ +extern int ZEXPORT unzReadCurrentFile (file, buf, len) + unzFile file; + voidp buf; + unsigned len; +{ + int err=UNZ_OK; + uInt iRead = 0; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->read_buffer == NULL)) + return UNZ_END_OF_LIST_OF_FILE; + if (len==0) + return 0; + + pfile_in_zip_read_info->stream.next_out = (Bytef*)buf; + + pfile_in_zip_read_info->stream.avail_out = (uInt)len; + + if ((len>pfile_in_zip_read_info->rest_read_uncompressed) && + (!(pfile_in_zip_read_info->raw))) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_uncompressed; + + if ((len>pfile_in_zip_read_info->rest_read_compressed+ + pfile_in_zip_read_info->stream.avail_in) && + (pfile_in_zip_read_info->raw)) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_compressed+ + pfile_in_zip_read_info->stream.avail_in; + + while (pfile_in_zip_read_info->stream.avail_out>0) + { + if ((pfile_in_zip_read_info->stream.avail_in==0) && + (pfile_in_zip_read_info->rest_read_compressed>0)) + { + uInt uReadThis = UNZ_BUFSIZE; + if (pfile_in_zip_read_info->rest_read_compressed<uReadThis) + uReadThis = (uInt)pfile_in_zip_read_info->rest_read_compressed; + if (uReadThis == 0) + return UNZ_EOF; + if (ZSEEK(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->pos_in_zipfile + + pfile_in_zip_read_info->byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + if (ZREAD(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->read_buffer, + uReadThis)!=uReadThis) + return UNZ_ERRNO; + + +# ifndef NOUNCRYPT + if(s->encrypted) + { + uInt i; + for(i=0;i<uReadThis;i++) + pfile_in_zip_read_info->read_buffer[i] = + zdecode(s->keys,s->pcrc_32_tab, + pfile_in_zip_read_info->read_buffer[i]); + } +# endif + + + pfile_in_zip_read_info->pos_in_zipfile += uReadThis; + + pfile_in_zip_read_info->rest_read_compressed-=uReadThis; + + pfile_in_zip_read_info->stream.next_in = + (Bytef*)pfile_in_zip_read_info->read_buffer; + pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; + } + + if ((pfile_in_zip_read_info->compression_method==0) || (pfile_in_zip_read_info->raw)) + { + uInt uDoCopy,i ; + + if ((pfile_in_zip_read_info->stream.avail_in == 0) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + return (iRead==0) ? UNZ_EOF : iRead; + + if (pfile_in_zip_read_info->stream.avail_out < + pfile_in_zip_read_info->stream.avail_in) + uDoCopy = pfile_in_zip_read_info->stream.avail_out ; + else + uDoCopy = pfile_in_zip_read_info->stream.avail_in ; + + for (i=0;i<uDoCopy;i++) + *(pfile_in_zip_read_info->stream.next_out+i) = + *(pfile_in_zip_read_info->stream.next_in+i); + + pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, + pfile_in_zip_read_info->stream.next_out, + uDoCopy); + pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; + pfile_in_zip_read_info->stream.avail_in -= uDoCopy; + pfile_in_zip_read_info->stream.avail_out -= uDoCopy; + pfile_in_zip_read_info->stream.next_out += uDoCopy; + pfile_in_zip_read_info->stream.next_in += uDoCopy; + pfile_in_zip_read_info->stream.total_out += uDoCopy; + iRead += uDoCopy; + } + else + { + uLong uTotalOutBefore,uTotalOutAfter; + const Bytef *bufBefore; + uLong uOutThis; + int flush=Z_SYNC_FLUSH; + + uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; + bufBefore = pfile_in_zip_read_info->stream.next_out; + + /* + if ((pfile_in_zip_read_info->rest_read_uncompressed == + pfile_in_zip_read_info->stream.avail_out) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + flush = Z_FINISH; + */ + err=inflate(&pfile_in_zip_read_info->stream,flush); + + if ((err>=0) && (pfile_in_zip_read_info->stream.msg!=NULL)) + err = Z_DATA_ERROR; + + uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; + uOutThis = uTotalOutAfter-uTotalOutBefore; + + pfile_in_zip_read_info->crc32 = + crc32(pfile_in_zip_read_info->crc32,bufBefore, + (uInt)(uOutThis)); + + pfile_in_zip_read_info->rest_read_uncompressed -= + uOutThis; + + iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); + + if (err==Z_STREAM_END) + return (iRead==0) ? UNZ_EOF : iRead; + if (err!=Z_OK) + break; + } + } + + if (err==Z_OK) + return iRead; + return err; +} + + +/* + Give the current position in uncompressed data +*/ +extern z_off_t ZEXPORT unztell (file) + unzFile file; +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + return (z_off_t)pfile_in_zip_read_info->stream.total_out; +} + + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ +extern int ZEXPORT unzeof (file) + unzFile file; +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + return 1; + else + return 0; +} + + + +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field that can be read + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ +extern int ZEXPORT unzGetLocalExtrafield (file,buf,len) + unzFile file; + voidp buf; + unsigned len; +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uInt read_now; + uLong size_to_read; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + size_to_read = (pfile_in_zip_read_info->size_local_extrafield - + pfile_in_zip_read_info->pos_local_extrafield); + + if (buf==NULL) + return (int)size_to_read; + + if (len>size_to_read) + read_now = (uInt)size_to_read; + else + read_now = (uInt)len ; + + if (read_now==0) + return 0; + + if (ZSEEK(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->offset_local_extrafield + + pfile_in_zip_read_info->pos_local_extrafield, + ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + if (ZREAD(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + buf,read_now)!=read_now) + return UNZ_ERRNO; + + return (int)read_now; +} + +/* + Close the file in zip opened with unzipOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ +extern int ZEXPORT unzCloseCurrentFile (file) + unzFile file; +{ + int err=UNZ_OK; + + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->rest_read_uncompressed == 0) && + (!pfile_in_zip_read_info->raw)) + { + if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) + err=UNZ_CRCERROR; + } + + + TRYFREE(pfile_in_zip_read_info->read_buffer); + pfile_in_zip_read_info->read_buffer = NULL; + if (pfile_in_zip_read_info->stream_initialised) + inflateEnd(&pfile_in_zip_read_info->stream); + + pfile_in_zip_read_info->stream_initialised = 0; + TRYFREE(pfile_in_zip_read_info); + + s->pfile_in_zip_read=NULL; + + return err; +} + + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ +extern int ZEXPORT unzGetGlobalComment (file, szComment, uSizeBuf) + unzFile file; + char *szComment; + uLong uSizeBuf; +{ + unz_s* s; + uLong uReadThis ; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + uReadThis = uSizeBuf; + if (uReadThis>s->gi.size_comment) + uReadThis = s->gi.size_comment; + + if (ZSEEK(s->z_filefunc,s->filestream,s->central_pos+22,ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + if (uReadThis>0) + { + *szComment='\0'; + if (ZREAD(s->z_filefunc,s->filestream,szComment,uReadThis)!=uReadThis) + return UNZ_ERRNO; + } + + if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) + *(szComment+s->gi.size_comment)='\0'; + return (int)uReadThis; +} + +/* Additions by RX '2004 */ +extern uLong ZEXPORT unzGetOffset (file) + unzFile file; +{ + unz_s* s; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return 0; + if (s->gi.number_entry != 0 && s->gi.number_entry != 0xffff) + if (s->num_file==s->gi.number_entry) + return 0; + return s->pos_in_central_dir; +} + +extern int ZEXPORT unzSetOffset (file, pos) + unzFile file; + uLong pos; +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + s->pos_in_central_dir = pos; + s->num_file = s->gi.number_entry; /* hack */ + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} diff --git a/depends/quazip/unzip.h b/depends/quazip/unzip.h new file mode 100644 index 00000000..33c9dc1a --- /dev/null +++ b/depends/quazip/unzip.h @@ -0,0 +1,356 @@ +/* unzip.h -- IO for uncompress .zip files using zlib + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + + Multi volume ZipFile (span) are not supported. + Encryption compatible with pkzip 2.04g only supported + Old compressions used by old PKZip 1.x are not supported + + + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Modified by Sergey A. Tachenov to integrate with Qt. + + +*/ + +/* for more info about .ZIP format, see + http://www.info-zip.org/pub/infozip/doc/appnote-981119-iz.zip + http://www.info-zip.org/pub/infozip/doc/ + PkWare has also a specification at : + ftp://ftp.pkware.com/probdesc.zip +*/ + +#ifndef _unz_H +#define _unz_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#ifndef _ZLIBIOAPI_H +#include "ioapi.h" +#endif + +#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unzFile__; +typedef unzFile__ *unzFile; +#else +typedef voidp unzFile; +#endif + + +#define UNZ_OK (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO (Z_ERRNO) +#define UNZ_EOF (0) +#define UNZ_PARAMERROR (-102) +#define UNZ_BADZIPFILE (-103) +#define UNZ_INTERNALERROR (-104) +#define UNZ_CRCERROR (-105) + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s +{ + uInt tm_sec; /* seconds after the minute - [0,59] */ + uInt tm_min; /* minutes after the hour - [0,59] */ + uInt tm_hour; /* hours since midnight - [0,23] */ + uInt tm_mday; /* day of the month - [1,31] */ + uInt tm_mon; /* months since January - [0,11] */ + uInt tm_year; /* years - [1980..2044] */ +} tm_unz; + +/* unz_global_info structure contain global data about the ZIPfile + These data comes from the end of central dir */ +typedef struct unz_global_info_s +{ + uLong number_entry; /* total number of entries in + the central dir on this disk */ + uLong size_comment; /* size of the global comment of the zipfile */ +} unz_global_info; + + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info_s +{ + uLong version; /* version made by 2 bytes */ + uLong version_needed; /* version needed to extract 2 bytes */ + uLong flag; /* general purpose bit flag 2 bytes */ + uLong compression_method; /* compression method 2 bytes */ + uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ + uLong crc; /* crc-32 4 bytes */ + uLong compressed_size; /* compressed size 4 bytes */ + uLong uncompressed_size; /* uncompressed size 4 bytes */ + uLong size_filename; /* filename length 2 bytes */ + uLong size_file_extra; /* extra field length 2 bytes */ + uLong size_file_comment; /* file comment length 2 bytes */ + + uLong disk_num_start; /* disk number start 2 bytes */ + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ + + tm_unz tmu_date; +} unz_file_info; + +extern int ZEXPORT unzStringFileNameCompare OF ((const char* fileName1, + const char* fileName2, + int iCaseSensitivity)); +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) +*/ + + +extern unzFile ZEXPORT unzOpen OF((voidpf file)); +/* + Open a Zip file. path contain whatever zopen_file from the IO API + accepts. For Qt implementation it is a pointer to QIODevice, for + fopen() implementation it's a file name. + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ + +extern unzFile ZEXPORT unzOpen2 OF((voidpf file, + zlib_filefunc_def* pzlib_filefunc_def)); +/* + Open a Zip file, like unzOpen, but provide a set of file low level API + for read/write the zip file (see ioapi.h) +*/ + +extern int ZEXPORT unzClose OF((unzFile file)); +/* + Close a ZipFile opened with unzipOpen. + If there is files inside the .Zip opened with unzOpenCurrentFile (see later), + these files MUST be closed with unzipCloseCurrentFile before call unzipClose. + return UNZ_OK if there is no problem. */ + +extern int ZEXPORT unzGetGlobalInfo OF((unzFile file, + unz_global_info *pglobal_info)); +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ + + +extern int ZEXPORT unzGetGlobalComment OF((unzFile file, + char *szComment, + uLong uSizeBuf)); +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ + + +/***************************************************************************/ +/* Unzip package allow you browse the directory of the zipfile */ + +extern int ZEXPORT unzGoToFirstFile OF((unzFile file)); +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ + +extern int ZEXPORT unzGoToNextFile OF((unzFile file)); +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ + +extern int ZEXPORT unzLocateFile OF((unzFile file, + const char *szFileName, + int iCaseSensitivity)); +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ + + +/* ****************************************** */ +/* Ryan supplied functions */ +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_pos_s +{ + uLong pos_in_zip_directory; /* offset in zip file directory */ + uLong num_of_file; /* # of file */ +} unz_file_pos; + +extern int ZEXPORT unzGetFilePos( + unzFile file, + unz_file_pos* file_pos); + +extern int ZEXPORT unzGoToFilePos( + unzFile file, + unz_file_pos* file_pos); + +/* ****************************************** */ + +extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file, + unz_file_info *pfile_info, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); +/* + Get Info about the current file + if pfile_info!=NULL, the *pfile_info structure will contain somes info about + the current file + if szFileName!=NULL, the filemane string will be copied in szFileName + (fileNameBufferSize is the size of the buffer) + if extraField!=NULL, the extra field information will be copied in extraField + (extraFieldBufferSize is the size of the buffer). + This is the Central-header version of the extra field + if szComment!=NULL, the comment string of the file will be copied in szComment + (commentBufferSize is the size of the buffer) +*/ + +/***************************************************************************/ +/* for reading the content of the current zipfile, you can open it, read data + from it, and close it (you can close it before reading all the file) + */ + +extern int ZEXPORT unzOpenCurrentFile OF((unzFile file)); +/* + Open for reading data the current file in the zipfile. + If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFilePassword OF((unzFile file, + const char* password)); +/* + Open for reading data the current file in the zipfile. + password is a crypting password + If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFile2 OF((unzFile file, + int* method, + int* level, + int raw)); +/* + Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 + *method will receive method of compression, *level will receive level of + compression + note : you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL +*/ + +extern int ZEXPORT unzOpenCurrentFile3 OF((unzFile file, + int* method, + int* level, + int raw, + const char* password)); +/* + Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 + *method will receive method of compression, *level will receive level of + compression + note : you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL +*/ + + +extern int ZEXPORT unzCloseCurrentFile OF((unzFile file)); +/* + Close the file in zip opened with unzOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ + +extern int ZEXPORT unzReadCurrentFile OF((unzFile file, + voidp buf, + unsigned len)); +/* + Read bytes from the current file (opened by unzOpenCurrentFile) + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ + +extern z_off_t ZEXPORT unztell OF((unzFile file)); +/* + Give the current position in uncompressed data +*/ + +extern int ZEXPORT unzeof OF((unzFile file)); +/* + return 1 if the end of file was reached, 0 elsewhere +*/ + +extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file, + voidp buf, + unsigned len)); +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ + +/***************************************************************************/ + +/* Get the current file offset */ +extern uLong ZEXPORT unzGetOffset (unzFile file); + +/* Set the current file offset */ +extern int ZEXPORT unzSetOffset (unzFile file, uLong pos); + + + +#ifdef __cplusplus +} +#endif + +#endif /* _unz_H */ diff --git a/depends/quazip/zip.c b/depends/quazip/zip.c new file mode 100644 index 00000000..a36a20a1 --- /dev/null +++ b/depends/quazip/zip.c @@ -0,0 +1,1281 @@ +/* zip.c -- IO on .zip files using zlib + Version 1.01e, February 12th, 2005 + + 27 Dec 2004 Rolf Kalbermatter + Modification to zipOpen2 to support globalComment retrieval. + + Copyright (C) 1998-2005 Gilles Vollant + + Read zip.h for more info + + Modified by Sergey A. Tachenov to integrate with Qt. +*/ + + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include "zlib.h" +#include "zip.h" +#include "quazip_global.h" + +#ifdef STDC +# include <stddef.h> +# include <string.h> +# include <stdlib.h> +#endif +#ifdef NO_ERRNO_H + extern int errno; +#else +# include <errno.h> +#endif + + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + +#ifndef VERSIONMADEBY +# define VERSIONMADEBY (0x031e) /* best for standard pkware crypt */ +#endif + +#ifndef Z_BUFSIZE +#define Z_BUFSIZE (16384) +#endif + +#ifndef Z_MAXFILENAMEINZIP +#define Z_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (malloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) free(p);} +#endif + +/* +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) +*/ + +/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */ + +#ifndef SEEK_CUR +#define SEEK_CUR 1 +#endif + +#ifndef SEEK_END +#define SEEK_END 2 +#endif + +#ifndef SEEK_SET +#define SEEK_SET 0 +#endif + +#ifndef DEF_MEM_LEVEL +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +#endif +const char zip_copyright[] = + " zip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; + + +#define SIZEDATA_INDATABLOCK (4096-(4*4)) + +#define LOCALHEADERMAGIC (0x04034b50) +#define DESCRIPTORHEADERMAGIC (0x08074b50) +#define CENTRALHEADERMAGIC (0x02014b50) +#define ENDHEADERMAGIC (0x06054b50) + +#define FLAG_LOCALHEADER_OFFSET (0x06) +#define CRC_LOCALHEADER_OFFSET (0x0e) + +#define SIZECENTRALHEADER (0x2e) /* 46 */ + +typedef struct linkedlist_datablock_internal_s +{ + struct linkedlist_datablock_internal_s* next_datablock; + uLong avail_in_this_block; + uLong filled_in_this_block; + uLong unused; /* for future use and alignement */ + unsigned char data[SIZEDATA_INDATABLOCK]; +} linkedlist_datablock_internal; + +typedef struct linkedlist_data_s +{ + linkedlist_datablock_internal* first_block; + linkedlist_datablock_internal* last_block; +} linkedlist_data; + + +typedef struct +{ + z_stream stream; /* zLib stream structure for inflate */ + int stream_initialised; /* 1 is stream is initialised */ + uInt pos_in_buffered_data; /* last written byte in buffered_data */ + + uLong pos_local_header; /* offset of the local header of the file + currenty writing */ + char* central_header; /* central header data for the current file */ + uLong size_centralheader; /* size of the central header for cur file */ + uLong flag; /* flag of the file currently writing */ + + int method; /* compression method of file currenty wr.*/ + int raw; /* 1 for directly writing raw data */ + Byte buffered_data[Z_BUFSIZE];/* buffer contain compressed data to be writ*/ + uLong dosDate; + uLong crc32; + int encrypt; +#ifndef NOCRYPT + unsigned long keys[3]; /* keys defining the pseudo-random sequence */ + const unsigned long* pcrc_32_tab; + int crypt_header_size; +#endif +} curfile_info; + +typedef struct +{ + zlib_filefunc_def z_filefunc; + voidpf filestream; /* io structore of the zipfile */ + linkedlist_data central_dir;/* datablock with central dir in construction*/ + int in_opened_file_inzip; /* 1 if a file in the zip is currently writ.*/ + curfile_info ci; /* info on the file curretly writing */ + + uLong begin_pos; /* position of the beginning of the zipfile */ + uLong add_position_when_writting_offset; + uLong number_entry; +#ifndef NO_ADDFILEINEXISTINGZIP + char *globalcomment; +#endif + unsigned flags; +} zip_internal; + + + +#ifndef NOCRYPT +#define INCLUDECRYPTINGCODE_IFCRYPTALLOWED +#include "crypt.h" +#endif + +local linkedlist_datablock_internal* allocate_new_datablock() +{ + linkedlist_datablock_internal* ldi; + ldi = (linkedlist_datablock_internal*) + ALLOC(sizeof(linkedlist_datablock_internal)); + if (ldi!=NULL) + { + ldi->next_datablock = NULL ; + ldi->filled_in_this_block = 0 ; + ldi->avail_in_this_block = SIZEDATA_INDATABLOCK ; + } + return ldi; +} + +local void free_datablock(ldi) + linkedlist_datablock_internal* ldi; +{ + while (ldi!=NULL) + { + linkedlist_datablock_internal* ldinext = ldi->next_datablock; + TRYFREE(ldi); + ldi = ldinext; + } +} + +local void init_linkedlist(ll) + linkedlist_data* ll; +{ + ll->first_block = ll->last_block = NULL; +} + +#if 0 // unused +local void free_linkedlist(ll) + linkedlist_data* ll; +{ + free_datablock(ll->first_block); + ll->first_block = ll->last_block = NULL; +} +#endif + +local int add_data_in_datablock(ll,buf,len) + linkedlist_data* ll; + const void* buf; + uLong len; +{ + linkedlist_datablock_internal* ldi; + const unsigned char* from_copy; + + if (ll==NULL) + return ZIP_INTERNALERROR; + + if (ll->last_block == NULL) + { + ll->first_block = ll->last_block = allocate_new_datablock(); + if (ll->first_block == NULL) + return ZIP_INTERNALERROR; + } + + ldi = ll->last_block; + from_copy = (unsigned char*)buf; + + while (len>0) + { + uInt copy_this; + uInt i; + unsigned char* to_copy; + + if (ldi->avail_in_this_block==0) + { + ldi->next_datablock = allocate_new_datablock(); + if (ldi->next_datablock == NULL) + return ZIP_INTERNALERROR; + ldi = ldi->next_datablock ; + ll->last_block = ldi; + } + + if (ldi->avail_in_this_block < len) + copy_this = (uInt)ldi->avail_in_this_block; + else + copy_this = (uInt)len; + + to_copy = &(ldi->data[ldi->filled_in_this_block]); + + for (i=0;i<copy_this;i++) + *(to_copy+i)=*(from_copy+i); + + ldi->filled_in_this_block += copy_this; + ldi->avail_in_this_block -= copy_this; + from_copy += copy_this ; + len -= copy_this; + } + return ZIP_OK; +} + + + +/****************************************************************************/ + +#ifndef NO_ADDFILEINEXISTINGZIP +/* =========================================================================== + Inputs a long in LSB order to the given file + nbByte == 1, 2 or 4 (byte, short or long) +*/ + +local int ziplocal_putValue OF((const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, uLong x, int nbByte)); +local int ziplocal_putValue (pzlib_filefunc_def, filestream, x, nbByte) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + uLong x; + int nbByte; +{ + unsigned char buf[4]; + int n; + for (n = 0; n < nbByte; n++) + { + buf[n] = (unsigned char)(x & 0xff); + x >>= 8; + } + if (x != 0) + { /* data overflow - hack for ZIP64 (X Roche) */ + for (n = 0; n < nbByte; n++) + { + buf[n] = 0xff; + } + } + + if (ZWRITE(*pzlib_filefunc_def,filestream,buf,nbByte)!=(uLong)nbByte) + return ZIP_ERRNO; + else + return ZIP_OK; +} + +local void ziplocal_putValue_inmemory OF((void* dest, uLong x, int nbByte)); +local void ziplocal_putValue_inmemory (dest, x, nbByte) + void* dest; + uLong x; + int nbByte; +{ + unsigned char* buf=(unsigned char*)dest; + int n; + for (n = 0; n < nbByte; n++) { + buf[n] = (unsigned char)(x & 0xff); + x >>= 8; + } + + if (x != 0) + { /* data overflow - hack for ZIP64 */ + for (n = 0; n < nbByte; n++) + { + buf[n] = 0xff; + } + } +} + +/****************************************************************************/ + + +local uLong ziplocal_TmzDateToDosDate(ptm,dosDate) + const tm_zip* ptm; + uLong dosDate UNUSED; +{ + uLong year = (uLong)ptm->tm_year; + if (year>1980) + year-=1980; + else if (year>80) + year-=80; + return + (uLong) (((ptm->tm_mday) + (32 * (ptm->tm_mon+1)) + (512 * year)) << 16) | + ((ptm->tm_sec/2) + (32* ptm->tm_min) + (2048 * (uLong)ptm->tm_hour)); +} + + +/****************************************************************************/ + +local int ziplocal_getByte OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + int *pi)); + +local int ziplocal_getByte(pzlib_filefunc_def,filestream,pi) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + int *pi; +{ + unsigned char c; + int err = (int)ZREAD(*pzlib_filefunc_def,filestream,&c,1); + if (err==1) + { + *pi = (int)c; + return ZIP_OK; + } + else + { + if (ZERROR(*pzlib_filefunc_def,filestream)) + return ZIP_ERRNO; + else + return ZIP_EOF; + } +} + + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +local int ziplocal_getShort OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int ziplocal_getShort (pzlib_filefunc_def,filestream,pX) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + uLong *pX; +{ + uLong x ; + int i; + int err; + + err = ziplocal_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==ZIP_OK) + err = ziplocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==ZIP_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int ziplocal_getLong OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int ziplocal_getLong (pzlib_filefunc_def,filestream,pX) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + uLong *pX; +{ + uLong x ; + int i; + int err; + + err = ziplocal_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==ZIP_OK) + err = ziplocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==ZIP_OK) + err = ziplocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<16; + + if (err==ZIP_OK) + err = ziplocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<24; + + if (err==ZIP_OK) + *pX = x; + else + *pX = 0; + return err; +} + +#ifndef BUFREADCOMMENT +#define BUFREADCOMMENT (0x400) +#endif +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +local uLong ziplocal_SearchCentralDir OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream)); + +local uLong ziplocal_SearchCentralDir(pzlib_filefunc_def,filestream) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; +{ + unsigned char* buf; + uLong uSizeFile; + uLong uBackRead; + uLong uMaxBack=0xffff; /* maximum size of global comment */ + uLong uPosFound=0; + + if (ZSEEK(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + + uSizeFile = ZTELL(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackRead<uMaxBack) + { + uLong uReadSize,uReadPos ; + int i; + if (uBackRead+BUFREADCOMMENT>uMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uSizeFile-uReadPos); + if (ZSEEK(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} +#endif /* !NO_ADDFILEINEXISTINGZIP*/ + +/************************************************************/ +extern zipFile ZEXPORT zipOpen2 (file, append, globalcomment, pzlib_filefunc_def) + voidpf file; + int append; + zipcharpc* globalcomment; + zlib_filefunc_def* pzlib_filefunc_def; +{ + zip_internal ziinit; + zip_internal* zi; + int err=ZIP_OK; + + + if (pzlib_filefunc_def==NULL) + fill_qiodevice_filefunc(&ziinit.z_filefunc); + else + ziinit.z_filefunc = *pzlib_filefunc_def; + + ziinit.filestream = (*(ziinit.z_filefunc.zopen_file)) + (ziinit.z_filefunc.opaque, + file, + (append == APPEND_STATUS_CREATE) ? + (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_CREATE) : + (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_EXISTING)); + + if (ziinit.filestream == NULL) + return NULL; + ziinit.begin_pos = ZTELL(ziinit.z_filefunc,ziinit.filestream); + ziinit.in_opened_file_inzip = 0; + ziinit.ci.stream_initialised = 0; + ziinit.number_entry = 0; + ziinit.add_position_when_writting_offset = 0; + ziinit.flags = ZIP_WRITE_DATA_DESCRIPTOR; + init_linkedlist(&(ziinit.central_dir)); + + + zi = (zip_internal*)ALLOC(sizeof(zip_internal)); + if (zi==NULL) + { + ZCLOSE(ziinit.z_filefunc,ziinit.filestream); + return NULL; + } + + /* now we add file in a zipfile */ +# ifndef NO_ADDFILEINEXISTINGZIP + ziinit.globalcomment = NULL; + if (append == APPEND_STATUS_ADDINZIP) + { + uLong byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + + uLong size_central_dir; /* size of the central directory */ + uLong offset_central_dir; /* offset of start of central directory */ + uLong central_pos,uL; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + uLong number_entry; + uLong number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + uLong size_comment; + + central_pos = ziplocal_SearchCentralDir(&ziinit.z_filefunc,ziinit.filestream); + if (central_pos==0) + err=ZIP_ERRNO; + + if (ZSEEK(ziinit.z_filefunc, ziinit.filestream, + central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=ZIP_ERRNO; + + /* the signature, already checked */ + if (ziplocal_getLong(&ziinit.z_filefunc, ziinit.filestream,&uL)!=ZIP_OK) + err=ZIP_ERRNO; + + /* number of this disk */ + if (ziplocal_getShort(&ziinit.z_filefunc, ziinit.filestream,&number_disk)!=ZIP_OK) + err=ZIP_ERRNO; + + /* number of the disk with the start of the central directory */ + if (ziplocal_getShort(&ziinit.z_filefunc, ziinit.filestream,&number_disk_with_CD)!=ZIP_OK) + err=ZIP_ERRNO; + + /* total number of entries in the central dir on this disk */ + if (ziplocal_getShort(&ziinit.z_filefunc, ziinit.filestream,&number_entry)!=ZIP_OK) + err=ZIP_ERRNO; + + /* total number of entries in the central dir */ + if (ziplocal_getShort(&ziinit.z_filefunc, ziinit.filestream,&number_entry_CD)!=ZIP_OK) + err=ZIP_ERRNO; + + if ((number_entry_CD!=number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=ZIP_BADZIPFILE; + + /* size of the central directory */ + if (ziplocal_getLong(&ziinit.z_filefunc, ziinit.filestream,&size_central_dir)!=ZIP_OK) + err=ZIP_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (ziplocal_getLong(&ziinit.z_filefunc, ziinit.filestream,&offset_central_dir)!=ZIP_OK) + err=ZIP_ERRNO; + + /* zipfile global comment length */ + if (ziplocal_getShort(&ziinit.z_filefunc, ziinit.filestream,&size_comment)!=ZIP_OK) + err=ZIP_ERRNO; + + if ((central_pos<offset_central_dir+size_central_dir) && + (err==ZIP_OK)) + err=ZIP_BADZIPFILE; + + if (err!=ZIP_OK) + { + ZCLOSE(ziinit.z_filefunc, ziinit.filestream); + TRYFREE(zi); + return NULL; + } + + if (size_comment>0) + { + ziinit.globalcomment = ALLOC(size_comment+1); + if (ziinit.globalcomment) + { + size_comment = ZREAD(ziinit.z_filefunc, ziinit.filestream,ziinit.globalcomment,size_comment); + ziinit.globalcomment[size_comment]=0; + } + } + + byte_before_the_zipfile = central_pos - + (offset_central_dir+size_central_dir); + ziinit.add_position_when_writting_offset = byte_before_the_zipfile; + + { + uLong size_central_dir_to_read = size_central_dir; + size_t buf_size = SIZEDATA_INDATABLOCK; + void* buf_read = (void*)ALLOC(buf_size); + if (ZSEEK(ziinit.z_filefunc, ziinit.filestream, + offset_central_dir + byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET) != 0) + err=ZIP_ERRNO; + + while ((size_central_dir_to_read>0) && (err==ZIP_OK)) + { + uLong read_this = SIZEDATA_INDATABLOCK; + if (read_this > size_central_dir_to_read) + read_this = size_central_dir_to_read; + if (ZREAD(ziinit.z_filefunc, ziinit.filestream,buf_read,read_this) != read_this) + err=ZIP_ERRNO; + + if (err==ZIP_OK) + err = add_data_in_datablock(&ziinit.central_dir,buf_read, + (uLong)read_this); + size_central_dir_to_read-=read_this; + } + TRYFREE(buf_read); + } + ziinit.begin_pos = byte_before_the_zipfile; + ziinit.number_entry = number_entry_CD; + + if (ZSEEK(ziinit.z_filefunc, ziinit.filestream, + offset_central_dir+byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=ZIP_ERRNO; + } + + if (globalcomment) + { + *globalcomment = ziinit.globalcomment; + } +# endif /* !NO_ADDFILEINEXISTINGZIP*/ + + if (err != ZIP_OK) + { +# ifndef NO_ADDFILEINEXISTINGZIP + TRYFREE(ziinit.globalcomment); +# endif /* !NO_ADDFILEINEXISTINGZIP*/ + TRYFREE(zi); + return NULL; + } + else + { + *zi = ziinit; + return (zipFile)zi; + } +} + +extern zipFile ZEXPORT zipOpen (file, append) + voidpf file; + int append; +{ + return zipOpen2(file,append,NULL,NULL); +} + +extern int ZEXPORT zipOpenNewFileInZip3 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + windowBits, memLevel, strategy, + password, crcForCrypting) + zipFile file; + const char* filename; + const zip_fileinfo* zipfi; + const void* extrafield_local; + uInt size_extrafield_local; + const void* extrafield_global; + uInt size_extrafield_global; + const char* comment; + int method; + int level; + int raw; + int windowBits; + int memLevel; + int strategy; + const char* password; + uLong crcForCrypting; +{ + zip_internal* zi; + uInt size_filename; + uInt size_comment; + uInt i; + int err = ZIP_OK; + uLong version_to_extract; + +# ifdef NOCRYPT + if (password != NULL) + return ZIP_PARAMERROR; +# endif + + if (file == NULL) + return ZIP_PARAMERROR; + if ((method!=0) && (method!=Z_DEFLATED)) + return ZIP_PARAMERROR; + + zi = (zip_internal*)file; + + if (zi->in_opened_file_inzip == 1) + { + err = zipCloseFileInZip (file); + if (err != ZIP_OK) + return err; + } + + if (method == 0 + && (level == 0 || (zi->flags & ZIP_WRITE_DATA_DESCRIPTOR) == 0)) + { + version_to_extract = 10; + } + else + { + version_to_extract = 20; + } + + + if (filename==NULL) + filename="-"; + + if (comment==NULL) + size_comment = 0; + else + size_comment = (uInt)strlen(comment); + + size_filename = (uInt)strlen(filename); + + if (zipfi == NULL) + zi->ci.dosDate = 0; + else + { + if (zipfi->dosDate != 0) + zi->ci.dosDate = zipfi->dosDate; + else zi->ci.dosDate = ziplocal_TmzDateToDosDate(&zipfi->tmz_date,zipfi->dosDate); + } + + zi->ci.flag = 0; + if ((level==8) || (level==9)) + zi->ci.flag |= 2; + if ((level==2)) + zi->ci.flag |= 4; + if ((level==1)) + zi->ci.flag |= 6; + if (password != NULL) + { + zi->ci.flag |= 1; + } + if (version_to_extract >= 20 + && (zi->flags & ZIP_WRITE_DATA_DESCRIPTOR) != 0) + zi->ci.flag |= 8; + zi->ci.crc32 = 0; + zi->ci.method = method; + zi->ci.encrypt = 0; + zi->ci.stream_initialised = 0; + zi->ci.pos_in_buffered_data = 0; + zi->ci.raw = raw; + zi->ci.pos_local_header = ZTELL(zi->z_filefunc,zi->filestream) ; + zi->ci.size_centralheader = SIZECENTRALHEADER + size_filename + + size_extrafield_global + size_comment; + zi->ci.central_header = (char*)ALLOC((uInt)zi->ci.size_centralheader); + + ziplocal_putValue_inmemory(zi->ci.central_header,(uLong)CENTRALHEADERMAGIC,4); + /* version info */ + ziplocal_putValue_inmemory(zi->ci.central_header+4,(uLong)VERSIONMADEBY,2); + ziplocal_putValue_inmemory(zi->ci.central_header+6,(uLong)version_to_extract,2); + ziplocal_putValue_inmemory(zi->ci.central_header+8,(uLong)zi->ci.flag,2); + ziplocal_putValue_inmemory(zi->ci.central_header+10,(uLong)zi->ci.method,2); + ziplocal_putValue_inmemory(zi->ci.central_header+12,(uLong)zi->ci.dosDate,4); + ziplocal_putValue_inmemory(zi->ci.central_header+16,(uLong)0,4); /*crc*/ + ziplocal_putValue_inmemory(zi->ci.central_header+20,(uLong)0,4); /*compr size*/ + ziplocal_putValue_inmemory(zi->ci.central_header+24,(uLong)0,4); /*uncompr size*/ + ziplocal_putValue_inmemory(zi->ci.central_header+28,(uLong)size_filename,2); + ziplocal_putValue_inmemory(zi->ci.central_header+30,(uLong)size_extrafield_global,2); + ziplocal_putValue_inmemory(zi->ci.central_header+32,(uLong)size_comment,2); + ziplocal_putValue_inmemory(zi->ci.central_header+34,(uLong)0,2); /*disk nm start*/ + + if (zipfi==NULL) + ziplocal_putValue_inmemory(zi->ci.central_header+36,(uLong)0,2); + else + ziplocal_putValue_inmemory(zi->ci.central_header+36,(uLong)zipfi->internal_fa,2); + + if (zipfi==NULL) + ziplocal_putValue_inmemory(zi->ci.central_header+38,(uLong)0,4); + else + ziplocal_putValue_inmemory(zi->ci.central_header+38,(uLong)zipfi->external_fa,4); + + ziplocal_putValue_inmemory(zi->ci.central_header+42,(uLong)zi->ci.pos_local_header- zi->add_position_when_writting_offset,4); + + for (i=0;i<size_filename;i++) + *(zi->ci.central_header+SIZECENTRALHEADER+i) = *(filename+i); + + for (i=0;i<size_extrafield_global;i++) + *(zi->ci.central_header+SIZECENTRALHEADER+size_filename+i) = + *(((const char*)extrafield_global)+i); + + for (i=0;i<size_comment;i++) + *(zi->ci.central_header+SIZECENTRALHEADER+size_filename+ + size_extrafield_global+i) = *(comment+i); + if (zi->ci.central_header == NULL) + return ZIP_INTERNALERROR; + + /* write the local header */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)LOCALHEADERMAGIC,4); + + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)version_to_extract,2); + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.flag,2); + + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.method,2); + + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.dosDate,4); + + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* crc 32, unknown */ + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* compressed size, unknown */ + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* uncompressed size, unknown */ + + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_filename,2); + + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_extrafield_local,2); + + if ((err==ZIP_OK) && (size_filename>0)) + if (ZWRITE(zi->z_filefunc,zi->filestream,filename,size_filename)!=size_filename) + err = ZIP_ERRNO; + + if ((err==ZIP_OK) && (size_extrafield_local>0)) + if (ZWRITE(zi->z_filefunc,zi->filestream,extrafield_local,size_extrafield_local) + !=size_extrafield_local) + err = ZIP_ERRNO; + + zi->ci.stream.avail_in = (uInt)0; + zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + zi->ci.stream.total_in = 0; + zi->ci.stream.total_out = 0; + + if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + { + zi->ci.stream.zalloc = (alloc_func)0; + zi->ci.stream.zfree = (free_func)0; + zi->ci.stream.opaque = (voidpf)0; + + if (windowBits>0) + windowBits = -windowBits; + + err = deflateInit2(&zi->ci.stream, level, + Z_DEFLATED, windowBits, memLevel, strategy); + + if (err==Z_OK) + zi->ci.stream_initialised = 1; + } +# ifndef NOCRYPT + zi->ci.crypt_header_size = 0; + if ((err==Z_OK) && (password != NULL)) + { + unsigned char bufHead[RAND_HEAD_LEN]; + unsigned int sizeHead; + zi->ci.encrypt = 1; + zi->ci.pcrc_32_tab = (const unsigned long*) get_crc_table(); + /*init_keys(password,zi->ci.keys,zi->ci.pcrc_32_tab);*/ + + crcForCrypting = (uLong)zi->ci.dosDate << 16; // ATTANTION! Without this row, you don't unpack your password protected archive in other app. + + sizeHead=crypthead(password,bufHead,RAND_HEAD_LEN,zi->ci.keys,zi->ci.pcrc_32_tab,crcForCrypting); + zi->ci.crypt_header_size = sizeHead; + + if (ZWRITE(zi->z_filefunc,zi->filestream,bufHead,sizeHead) != sizeHead) + err = ZIP_ERRNO; + } +# endif + + if (err==Z_OK) + zi->in_opened_file_inzip = 1; + return err; +} + +extern int ZEXPORT zipOpenNewFileInZip2(file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw) + zipFile file; + const char* filename; + const zip_fileinfo* zipfi; + const void* extrafield_local; + uInt size_extrafield_local; + const void* extrafield_global; + uInt size_extrafield_global; + const char* comment; + int method; + int level; + int raw; +{ + return zipOpenNewFileInZip3 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + NULL, 0); +} + +extern int ZEXPORT zipOpenNewFileInZip (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level) + zipFile file; + const char* filename; + const zip_fileinfo* zipfi; + const void* extrafield_local; + uInt size_extrafield_local; + const void* extrafield_global; + uInt size_extrafield_global; + const char* comment; + int method; + int level; +{ + return zipOpenNewFileInZip2 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, 0); +} + +local int zipFlushWriteBuffer(zi) + zip_internal* zi; +{ + int err=ZIP_OK; + + if (zi->ci.encrypt != 0) + { +#ifndef NOCRYPT + uInt i; + int t; + for (i=0;i<zi->ci.pos_in_buffered_data;i++) + zi->ci.buffered_data[i] = zencode(zi->ci.keys, zi->ci.pcrc_32_tab, + zi->ci.buffered_data[i],t); +#endif + } + if (ZWRITE(zi->z_filefunc,zi->filestream,zi->ci.buffered_data,zi->ci.pos_in_buffered_data) + !=zi->ci.pos_in_buffered_data) + err = ZIP_ERRNO; + zi->ci.pos_in_buffered_data = 0; + return err; +} + +extern int ZEXPORT zipWriteInFileInZip (file, buf, len) + zipFile file; + const void* buf; + unsigned len; +{ + zip_internal* zi; + int err=ZIP_OK; + + if (file == NULL) + return ZIP_PARAMERROR; + zi = (zip_internal*)file; + + if (zi->in_opened_file_inzip == 0) + return ZIP_PARAMERROR; + + zi->ci.stream.next_in = (void*)buf; + zi->ci.stream.avail_in = len; + zi->ci.crc32 = crc32(zi->ci.crc32,buf,len); + + while ((err==ZIP_OK) && (zi->ci.stream.avail_in>0)) + { + if (zi->ci.stream.avail_out == 0) + { + if (zipFlushWriteBuffer(zi) == ZIP_ERRNO) + err = ZIP_ERRNO; + zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + } + + + if(err != ZIP_OK) + break; + + if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + { + uLong uTotalOutBefore = zi->ci.stream.total_out; + err=deflate(&zi->ci.stream, Z_NO_FLUSH); + zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ; + + } + else + { + uInt copy_this,i; + if (zi->ci.stream.avail_in < zi->ci.stream.avail_out) + copy_this = zi->ci.stream.avail_in; + else + copy_this = zi->ci.stream.avail_out; + for (i=0;i<copy_this;i++) + *(((char*)zi->ci.stream.next_out)+i) = + *(((const char*)zi->ci.stream.next_in)+i); + { + zi->ci.stream.avail_in -= copy_this; + zi->ci.stream.avail_out-= copy_this; + zi->ci.stream.next_in+= copy_this; + zi->ci.stream.next_out+= copy_this; + zi->ci.stream.total_in+= copy_this; + zi->ci.stream.total_out+= copy_this; + zi->ci.pos_in_buffered_data += copy_this; + } + } + } + + return err; +} + +extern int ZEXPORT zipCloseFileInZipRaw (file, uncompressed_size, crc32) + zipFile file; + uLong uncompressed_size; + uLong crc32; +{ + zip_internal* zi; + uLong compressed_size; + int err=ZIP_OK; + + if (file == NULL) + return ZIP_PARAMERROR; + zi = (zip_internal*)file; + + if (zi->in_opened_file_inzip == 0) + return ZIP_PARAMERROR; + zi->ci.stream.avail_in = 0; + + if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + while (err==ZIP_OK) + { + uLong uTotalOutBefore; + if (zi->ci.stream.avail_out == 0) + { + if (zipFlushWriteBuffer(zi) == ZIP_ERRNO) + err = ZIP_ERRNO; + zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + } + uTotalOutBefore = zi->ci.stream.total_out; + err=deflate(&zi->ci.stream, Z_FINISH); + zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ; + } + + if (err==Z_STREAM_END) + err=ZIP_OK; /* this is normal */ + + if ((zi->ci.pos_in_buffered_data>0) && (err==ZIP_OK)) + if (zipFlushWriteBuffer(zi)==ZIP_ERRNO) + err = ZIP_ERRNO; + + if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + { + err=deflateEnd(&zi->ci.stream); + zi->ci.stream_initialised = 0; + } + + if (!zi->ci.raw) + { + crc32 = (uLong)zi->ci.crc32; + uncompressed_size = (uLong)zi->ci.stream.total_in; + } + compressed_size = (uLong)zi->ci.stream.total_out; +# ifndef NOCRYPT + compressed_size += zi->ci.crypt_header_size; +# endif + + ziplocal_putValue_inmemory(zi->ci.central_header+16,crc32,4); /*crc*/ + ziplocal_putValue_inmemory(zi->ci.central_header+20, + compressed_size,4); /*compr size*/ + if (zi->ci.stream.data_type == Z_ASCII) + ziplocal_putValue_inmemory(zi->ci.central_header+36,(uLong)Z_ASCII,2); + ziplocal_putValue_inmemory(zi->ci.central_header+24, + uncompressed_size,4); /*uncompr size*/ + + if (err==ZIP_OK) + err = add_data_in_datablock(&zi->central_dir,zi->ci.central_header, + (uLong)zi->ci.size_centralheader); + free(zi->ci.central_header); + + if (err==ZIP_OK) + { + uLong cur_pos_inzip = ZTELL(zi->z_filefunc,zi->filestream); + if (ZSEEK(zi->z_filefunc,zi->filestream, + zi->ci.pos_local_header + 14,ZLIB_FILEFUNC_SEEK_SET)!=0) + err = ZIP_ERRNO; + + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,crc32,4); /* crc 32, unknown */ + + if (err==ZIP_OK) /* compressed size, unknown */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,compressed_size,4); + + if (err==ZIP_OK) /* uncompressed size, unknown */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,uncompressed_size,4); + + if (ZSEEK(zi->z_filefunc,zi->filestream, + cur_pos_inzip,ZLIB_FILEFUNC_SEEK_SET)!=0) + err = ZIP_ERRNO; + + if ((zi->ci.flag & 8) != 0) { + /* Write local Descriptor after file data */ + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)DESCRIPTORHEADERMAGIC,4); + + if (err==ZIP_OK) + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,crc32,4); /* crc 32, unknown */ + + if (err==ZIP_OK) /* compressed size, unknown */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,compressed_size,4); + + if (err==ZIP_OK) /* uncompressed size, unknown */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,uncompressed_size,4); + } + + + } + + zi->number_entry ++; + zi->in_opened_file_inzip = 0; + + return err; +} + +extern int ZEXPORT zipCloseFileInZip (file) + zipFile file; +{ + return zipCloseFileInZipRaw (file,0,0); +} + +extern int ZEXPORT zipClose (file, global_comment) + zipFile file; + const char* global_comment; +{ + zip_internal* zi; + int err = 0; + uLong size_centraldir = 0; + uLong centraldir_pos_inzip; + uInt size_global_comment; + if (file == NULL) + return ZIP_PARAMERROR; + zi = (zip_internal*)file; + + if (zi->in_opened_file_inzip == 1) + { + err = zipCloseFileInZip (file); + } + +#ifndef NO_ADDFILEINEXISTINGZIP + if (global_comment==NULL) + global_comment = zi->globalcomment; +#endif + if (global_comment==NULL) + size_global_comment = 0; + else + size_global_comment = (uInt)strlen(global_comment); + + centraldir_pos_inzip = ZTELL(zi->z_filefunc,zi->filestream); + if (err==ZIP_OK) + { + linkedlist_datablock_internal* ldi = zi->central_dir.first_block ; + while (ldi!=NULL) + { + if ((err==ZIP_OK) && (ldi->filled_in_this_block>0)) + if (ZWRITE(zi->z_filefunc,zi->filestream, + ldi->data,ldi->filled_in_this_block) + !=ldi->filled_in_this_block ) + err = ZIP_ERRNO; + + size_centraldir += ldi->filled_in_this_block; + ldi = ldi->next_datablock; + } + } + free_datablock(zi->central_dir.first_block); + + if (err==ZIP_OK) /* Magic End */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)ENDHEADERMAGIC,4); + + if (err==ZIP_OK) /* number of this disk */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2); + + if (err==ZIP_OK) /* number of the disk with the start of the central directory */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2); + + if (err==ZIP_OK) /* total number of entries in the central dir on this disk */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2); + + if (err==ZIP_OK) /* total number of entries in the central dir */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2); + + if (err==ZIP_OK) /* size of the central directory */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_centraldir,4); + + if (err==ZIP_OK) /* offset of start of central directory with respect to the + starting disk number */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream, + (uLong)(centraldir_pos_inzip - zi->add_position_when_writting_offset),4); + + if (err==ZIP_OK) /* zipfile comment length */ + err = ziplocal_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_global_comment,2); + + if ((err==ZIP_OK) && (size_global_comment>0)) + if (ZWRITE(zi->z_filefunc,zi->filestream, + global_comment,size_global_comment) != size_global_comment) + err = ZIP_ERRNO; + + if (ZCLOSE(zi->z_filefunc,zi->filestream) != 0) + if (err == ZIP_OK) + err = ZIP_ERRNO; + +#ifndef NO_ADDFILEINEXISTINGZIP + TRYFREE(zi->globalcomment); +#endif + TRYFREE(zi); + + return err; +} + +extern int ZEXPORT zipSetFlags(zipFile file, unsigned flags) +{ + zip_internal* zi; + if (file == NULL) + return ZIP_PARAMERROR; + zi = (zip_internal*)file; + zi->flags |= flags; + return ZIP_OK; +} + +extern int ZEXPORT zipClearFlags(zipFile file, unsigned flags) +{ + zip_internal* zi; + if (file == NULL) + return ZIP_PARAMERROR; + zi = (zip_internal*)file; + zi->flags &= ~flags; + return ZIP_OK; +} diff --git a/depends/quazip/zip.h b/depends/quazip/zip.h new file mode 100644 index 00000000..269ec2da --- /dev/null +++ b/depends/quazip/zip.h @@ -0,0 +1,245 @@ +/* zip.h -- IO for compress .zip files using zlib + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + This unzip package allow creates .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + Multi volume ZipFile (span) are not supported. + Encryption compatible with pkzip 2.04g only supported + Old compressions used by old PKZip 1.x are not supported + + For uncompress .zip file, look at unzip.h + + + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.html for evolution + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Modified by Sergey A. Tachenov to integrate with Qt. + + +*/ + +/* for more info about .ZIP format, see + http://www.info-zip.org/pub/infozip/doc/appnote-981119-iz.zip + http://www.info-zip.org/pub/infozip/doc/ + PkWare has also a specification at : + ftp://ftp.pkware.com/probdesc.zip +*/ + +#ifndef _zip_H +#define _zip_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#ifndef _ZLIBIOAPI_H +#include "ioapi.h" +#endif + +#if defined(STRICTZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagzipFile__ { int unused; } zipFile__; +typedef zipFile__ *zipFile; +#else +typedef voidp zipFile; +#endif + +#define ZIP_OK (0) +#define ZIP_EOF (0) +#define ZIP_ERRNO (Z_ERRNO) +#define ZIP_PARAMERROR (-102) +#define ZIP_BADZIPFILE (-103) +#define ZIP_INTERNALERROR (-104) + +#define ZIP_WRITE_DATA_DESCRIPTOR 0x8u + +#ifndef DEF_MEM_LEVEL +# if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +# else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +# endif +#endif +/* default memLevel */ + +/* tm_zip contain date/time info */ +typedef struct tm_zip_s +{ + uInt tm_sec; /* seconds after the minute - [0,59] */ + uInt tm_min; /* minutes after the hour - [0,59] */ + uInt tm_hour; /* hours since midnight - [0,23] */ + uInt tm_mday; /* day of the month - [1,31] */ + uInt tm_mon; /* months since January - [0,11] */ + uInt tm_year; /* years - [1980..2044] */ +} tm_zip; + +typedef struct +{ + tm_zip tmz_date; /* date in understandable format */ + uLong dosDate; /* if dos_date == 0, tmu_date is used */ +/* uLong flag; */ /* general purpose bit flag 2 bytes */ + + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ +} zip_fileinfo; + +typedef const char* zipcharpc; + + +#define APPEND_STATUS_CREATE (0) +#define APPEND_STATUS_CREATEAFTER (1) +#define APPEND_STATUS_ADDINZIP (2) + +extern zipFile ZEXPORT zipOpen OF((voidpf file, int append)); +/* + Create a zipfile. + file is whatever the IO API accepts. For Qt IO API it's a pointer to + QIODevice. For fopen() IO API it's a file name (const char*). + if the file pathname exist and append==APPEND_STATUS_CREATEAFTER, the zip + will be created at the end of the file. + (useful if the file contain a self extractor code) + if the file pathname exist and append==APPEND_STATUS_ADDINZIP, we will + add files in existing zip (be sure you don't add file that doesn't exist) + If the zipfile cannot be opened, the return value is NULL. + Else, the return value is a zipFile Handle, usable with other function + of this zip package. +*/ + +/* Note : there is no delete function into a zipfile. + If you want delete file into a zipfile, you must open a zipfile, and create another + Of couse, you can use RAW reading and writing to copy the file you did not want delte +*/ + +extern zipFile ZEXPORT zipOpen2 OF((voidpf file, + int append, + zipcharpc* globalcomment, + zlib_filefunc_def* pzlib_filefunc_def)); + +extern int ZEXPORT zipOpenNewFileInZip OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level)); +/* + Open a file in the ZIP for writing. + filename : the filename in zip (if NULL, '-' without quote will be used + *zipfi contain supplemental information + if extrafield_local!=NULL and size_extrafield_local>0, extrafield_local + contains the extrafield data the the local header + if extrafield_global!=NULL and size_extrafield_global>0, extrafield_global + contains the extrafield data the the local header + if comment != NULL, comment contain the comment string + method contain the compression method (0 for store, Z_DEFLATED for deflate) + level contain the level of compression (can be Z_DEFAULT_COMPRESSION) +*/ + + +extern int ZEXPORT zipOpenNewFileInZip2 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw)); + +/* + Same than zipOpenNewFileInZip, except if raw=1, we write raw file + */ + +extern int ZEXPORT zipOpenNewFileInZip3 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int windowBits, + int memLevel, + int strategy, + const char* password, + uLong crcForCtypting)); + +/* + Same than zipOpenNewFileInZip2, except + windowBits,memLevel,,strategy : see parameter strategy in deflateInit2 + password : crypting password (NULL for no crypting) + crcForCtypting : crc of file to compress (needed for crypting) + */ + + +extern int ZEXPORT zipWriteInFileInZip OF((zipFile file, + const void* buf, + unsigned len)); +/* + Write data in the zipfile +*/ + +extern int ZEXPORT zipCloseFileInZip OF((zipFile file)); +/* + Close the current file in the zipfile +*/ + +extern int ZEXPORT zipCloseFileInZipRaw OF((zipFile file, + uLong uncompressed_size, + uLong crc32)); +/* + Close the current file in the zipfile, for fiel opened with + parameter raw=1 in zipOpenNewFileInZip2 + uncompressed_size and crc32 are value for the uncompressed size +*/ + +extern int ZEXPORT zipClose OF((zipFile file, + const char* global_comment)); +/* + Close the zipfile +*/ + +/* + Added by Sergey A. Tachenov to tweak zipping behaviour. + */ +extern int ZEXPORT zipSetFlags(zipFile file, unsigned flags); +extern int ZEXPORT zipClearFlags(zipFile file, unsigned flags); + +#ifdef __cplusplus +} +#endif + +#endif /* _zip_H */ diff --git a/depends/settings/CMakeLists.txt b/depends/settings/CMakeLists.txt new file mode 100644 index 00000000..da853a73 --- /dev/null +++ b/depends/settings/CMakeLists.txt @@ -0,0 +1,47 @@ +project(libSettings) + +# Find Qt +find_package(Qt5Core REQUIRED) + +# Include Qt headers. +include_directories(${Qt5Base_INCLUDE_DIRS}) + + +SET(LIBSETTINGS_SOURCES +libsettings_config.h + +inifile.h +inifile.cpp + +settingsobject.h +settingsobject.cpp +inisettingsobject.h +inisettingsobject.cpp + +setting.h +setting.cpp +overridesetting.h +overridesetting.cpp +) + +# Set the include dir path. +SET(LIBSETTINGS_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" PARENT_SCOPE) + +# Static link! +ADD_DEFINITIONS(-DLIBSETTINGS_STATIC) + +add_definitions(-DLIBSETTINGS_LIBRARY) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +IF(MultiMC_CODE_COVERAGE) + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 --coverage") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 --coverage") + SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0 --coverage") + SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g -O0 --coverage") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0 --coverage") +ENDIF(MultiMC_CODE_COVERAGE) + +add_library(libSettings STATIC ${LIBSETTINGS_SOURCES}) +qt5_use_modules(libSettings Core) +target_link_libraries(libSettings) diff --git a/depends/settings/inifile.cpp b/depends/settings/inifile.cpp new file mode 100644 index 00000000..1170f0b1 --- /dev/null +++ b/depends/settings/inifile.cpp @@ -0,0 +1,106 @@ +/* Copyright 2013 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 "inifile.h" + +#include <QFile> +#include <QTextStream> +#include <QStringList> + +INIFile::INIFile() +{ +} + +QString INIFile::unescape(QString orig) +{ + orig.replace("\\n", "\n"); + orig.replace("\\t", "\t"); + orig.replace("\\\\", "\\"); + return orig; +} +QString INIFile::escape(QString orig) +{ + orig.replace("\\", "\\\\"); + orig.replace("\n", "\\n"); + orig.replace("\t", "\\t"); + return orig; +} + +bool INIFile::saveFile(QString fileName) +{ + // TODO Handle errors. + QFile file(fileName); + file.open(QIODevice::WriteOnly); + QTextStream out(&file); + out.setCodec("UTF-8"); + + for (Iterator iter = begin(); iter != end(); iter++) + { + QString value = iter.value().toString(); + value = escape(value); + out << iter.key() << "=" << value << "\n"; + } + + return true; +} + +bool INIFile::loadFile(QString fileName) +{ + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) + return false; + bool success = loadFile(file.readAll()); + file.close(); + return success; +} +bool INIFile::loadFile(QByteArray file) +{ + QTextStream in(file); + in.setCodec("UTF-8"); + + QStringList lines = in.readAll().split('\n'); + for (int i = 0; i < lines.count(); i++) + { + QString &lineRaw = lines[i]; + // Ignore comments. + QString line = lineRaw.left(lineRaw.indexOf('#')).trimmed(); + + int eqPos = line.indexOf('='); + if (eqPos == -1) + continue; + QString key = line.left(eqPos).trimmed(); + QString valueStr = line.right(line.length() - eqPos - 1).trimmed(); + + valueStr = unescape(valueStr); + + QVariant value(valueStr); + this->operator[](key) = value; + } + + return true; +} + +QVariant INIFile::get(QString key, QVariant def) const +{ + if (!this->contains(key)) + return def; + else + return this->operator[](key); +} + +void INIFile::set(QString key, QVariant val) +{ + this->operator[](key) = val; +} diff --git a/depends/settings/inifile.h b/depends/settings/inifile.h new file mode 100644 index 00000000..27da7bf0 --- /dev/null +++ b/depends/settings/inifile.h @@ -0,0 +1,38 @@ +/* Copyright 2013 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 <QVariant> +#include <QIODevice> + +#include "libsettings_config.h" + +// Sectionless INI parser (for instance config files) +class LIBSETTINGS_EXPORT INIFile : public QMap<QString, QVariant> +{ +public: + explicit INIFile(); + + bool loadFile(QByteArray file); + bool loadFile(QString fileName); + bool saveFile(QString fileName); + + QVariant get(QString key, QVariant def) const; + void set(QString key, QVariant val); + QString unescape(QString orig); + QString escape(QString orig); +}; diff --git a/depends/settings/inisettingsobject.cpp b/depends/settings/inisettingsobject.cpp new file mode 100644 index 00000000..5e52a56f --- /dev/null +++ b/depends/settings/inisettingsobject.cpp @@ -0,0 +1,76 @@ +/* Copyright 2013 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 "inisettingsobject.h" +#include "setting.h" + +INISettingsObject::INISettingsObject(const QString &path, QObject *parent) + : SettingsObject(parent) +{ + m_filePath = path; + m_ini.loadFile(path); +} + +void INISettingsObject::setFilePath(const QString &filePath) +{ + m_filePath = filePath; +} + +void INISettingsObject::changeSetting(const Setting &setting, QVariant value) +{ + if (contains(setting.id())) + { + // valid value -> set the main config, remove all the sysnonyms + if (value.isValid()) + { + auto list = setting.configKeys(); + m_ini.set(list.takeFirst(), value); + for(auto iter: list) + m_ini.remove(iter); + } + // invalid -> remove all (just like resetSetting) + else + { + for(auto iter: setting.configKeys()) + m_ini.remove(iter); + } + m_ini.saveFile(m_filePath); + } +} + +void INISettingsObject::resetSetting(const Setting &setting) +{ + // if we have the setting, remove all the synonyms. ALL OF THEM + if (contains(setting.id())) + { + for(auto iter: setting.configKeys()) + m_ini.remove(iter); + m_ini.saveFile(m_filePath); + } +} + +QVariant INISettingsObject::retrieveValue(const Setting &setting) +{ + // if we have the setting, return value of the first matching synonym + if (contains(setting.id())) + { + for(auto iter: setting.configKeys()) + { + if(m_ini.contains(iter)) + return m_ini[iter]; + } + } + return QVariant(); +} diff --git a/depends/settings/inisettingsobject.h b/depends/settings/inisettingsobject.h new file mode 100644 index 00000000..8badc0c6 --- /dev/null +++ b/depends/settings/inisettingsobject.h @@ -0,0 +1,61 @@ +/* Copyright 2013 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 <QObject> + +#include "inifile.h" + +#include "settingsobject.h" + +#include "libsettings_config.h" + +/*! + * \brief A settings object that stores its settings in an INIFile. + */ +class LIBSETTINGS_EXPORT INISettingsObject : public SettingsObject +{ + Q_OBJECT +public: + explicit INISettingsObject(const QString &path, QObject *parent = 0); + + /*! + * \brief Gets the path to the INI file. + * \return The path to the INI file. + */ + virtual QString filePath() const + { + return m_filePath; + } + + /*! + * \brief Sets the path to the INI file and reloads it. + * \param filePath The INI file's new path. + */ + virtual void setFilePath(const QString &filePath); + +protected +slots: + virtual void changeSetting(const Setting &setting, QVariant value); + virtual void resetSetting(const Setting &setting); + +protected: + virtual QVariant retrieveValue(const Setting &setting); + + INIFile m_ini; + + QString m_filePath; +}; diff --git a/depends/settings/libsettings_config.h b/depends/settings/libsettings_config.h new file mode 100644 index 00000000..e5beed28 --- /dev/null +++ b/depends/settings/libsettings_config.h @@ -0,0 +1,29 @@ +/* Copyright 2013 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 <QtCore/QtGlobal> + +#ifdef LIBSETTINGS_STATIC +#define LIBSETTINGS_EXPORT +#else +#ifdef LIBSETTINGS_LIBRARY +#define LIBSETTINGS_EXPORT Q_DECL_EXPORT +#else +#define LIBSETTINGS_EXPORT Q_DECL_IMPORT +#endif +#endif + diff --git a/depends/settings/overridesetting.cpp b/depends/settings/overridesetting.cpp new file mode 100644 index 00000000..7b5f5418 --- /dev/null +++ b/depends/settings/overridesetting.cpp @@ -0,0 +1,30 @@ +/* Copyright 2013 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 "overridesetting.h" + +OverrideSetting::OverrideSetting(std::shared_ptr<Setting> other) + : Setting(other->configKeys(), QVariant()) +{ + m_other = other; +} + +QVariant OverrideSetting::defValue() const +{ + if (m_other) + return m_other->get(); + else + return QVariant(); +} diff --git a/depends/settings/overridesetting.h b/depends/settings/overridesetting.h new file mode 100644 index 00000000..5ef901d0 --- /dev/null +++ b/depends/settings/overridesetting.h @@ -0,0 +1,41 @@ +/* Copyright 2013 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 <QObject> +#include <memory> + +#include "setting.h" + +#include "libsettings_config.h" + +/*! + * \brief A setting that 'overrides another.' + * This means that the setting's default value will be the value of another setting. + * The other setting can be (and usually is) a part of a different SettingsObject + * than this one. + */ +class LIBSETTINGS_EXPORT OverrideSetting : public Setting +{ + Q_OBJECT +public: + explicit OverrideSetting(std::shared_ptr<Setting> other); + + virtual QVariant defValue() const; + +protected: + std::shared_ptr<Setting> m_other; +}; diff --git a/depends/settings/setting.cpp b/depends/settings/setting.cpp new file mode 100644 index 00000000..0d685771 --- /dev/null +++ b/depends/settings/setting.cpp @@ -0,0 +1,53 @@ +/* Copyright 2013 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 "setting.h" +#include "settingsobject.h" + +Setting::Setting(QStringList synonyms, QVariant defVal) + : QObject(), m_synonyms(synonyms), m_defVal(defVal) +{ +} + +QVariant Setting::get() const +{ + SettingsObject *sbase = m_storage; + if (!sbase) + { + return defValue(); + } + else + { + QVariant test = sbase->retrieveValue(*this); + if (!test.isValid()) + return defValue(); + return test; + } +} + +QVariant Setting::defValue() const +{ + return m_defVal; +} + +void Setting::set(QVariant value) +{ + emit settingChanged(*this, value); +} + +void Setting::reset() +{ + emit settingReset(*this); +} diff --git a/depends/settings/setting.h b/depends/settings/setting.h new file mode 100644 index 00000000..a73474d2 --- /dev/null +++ b/depends/settings/setting.h @@ -0,0 +1,119 @@ +/* Copyright 2013 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 <QObject> +#include <QVariant> +#include <QStringList> +#include <memory> + +#include "libsettings_config.h" + +class SettingsObject; + +/*! + * + */ +class LIBSETTINGS_EXPORT Setting : public QObject +{ + Q_OBJECT +public: + /** + * Construct a Setting + * + * Synonyms are all the possible names used in the settings object, in order of preference. + * First synonym is the ID, which identifies the setting in MultiMC. + * + * defVal is the default value that will be returned when the settings object + * doesn't have any value for this setting. + */ + explicit Setting(QStringList synonyms, QVariant defVal = QVariant()); + + /*! + * \brief Gets this setting's ID. + * This is used to refer to the setting within the application. + * \warning Changing the ID while the setting is registered with a SettingsObject results in + * undefined behavior. + * \return The ID of the setting. + */ + virtual QString id() const + { + return m_synonyms.first(); + } + + /*! + * \brief Gets this setting's config file key. + * This is used to store the setting's value in the config file. It is usually + * the same as the setting's ID, but it can be different. + * \return The setting's config file key. + */ + virtual QStringList configKeys() const + { + return m_synonyms; + } + + /*! + * \brief Gets this setting's value as a QVariant. + * This is done by calling the SettingsObject's retrieveValue() function. + * If this Setting doesn't have a SettingsObject, this returns an invalid QVariant. + * \return QVariant containing this setting's value. + * \sa value() + */ + virtual QVariant get() const; + + /*! + * \brief Gets this setting's default value. + * \return The default value of this setting. + */ + virtual QVariant defValue() const; + +signals: + /*! + * \brief Signal emitted when this Setting object's value changes. + * \param setting A reference to the Setting that changed. + * \param value This Setting object's new value. + */ + void settingChanged(const Setting &setting, QVariant value); + + /*! + * \brief Signal emitted when this Setting object's value resets to default. + * \param setting A reference to the Setting that changed. + */ + void settingReset(const Setting &setting); + +public +slots: + /*! + * \brief Changes the setting's value. + * This is done by emitting the settingChanged() signal which will then be + * handled by the SettingsObject object and cause the setting to change. + * \param value The new value. + */ + virtual void set(QVariant value); + + /*! + * \brief Reset the setting to default + * This is done by emitting the settingReset() signal which will then be + * handled by the SettingsObject object and cause the setting to change. + */ + virtual void reset(); + +protected: + friend class SettingsObject; + SettingsObject * m_storage; + QStringList m_synonyms; + QVariant m_defVal; +}; diff --git a/depends/settings/settingsobject.cpp b/depends/settings/settingsobject.cpp new file mode 100644 index 00000000..43fc989a --- /dev/null +++ b/depends/settings/settingsobject.cpp @@ -0,0 +1,138 @@ +/* Copyright 2013 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 "settingsobject.h" +#include "setting.h" +#include "overridesetting.h" + +#include <QVariant> + +SettingsObject::SettingsObject(QObject *parent) : QObject(parent) +{ +} + +SettingsObject::~SettingsObject() +{ + m_settings.clear(); +} + +std::shared_ptr<Setting> SettingsObject::registerOverride(std::shared_ptr<Setting> original) +{ + if (contains(original->id())) + { + qDebug(QString("Failed to register setting %1. ID already exists.") + .arg(original->id()) + .toUtf8()); + return nullptr; // Fail + } + auto override = std::make_shared<OverrideSetting>(original); + override->m_storage = this; + connectSignals(*override); + m_settings.insert(override->id(), override); + return override; +} + +std::shared_ptr<Setting> SettingsObject::registerSetting(QStringList synonyms, QVariant defVal) +{ + if (synonyms.empty()) + return nullptr; + if (contains(synonyms.first())) + { + qDebug(QString("Failed to register setting %1. ID already exists.") + .arg(synonyms.first()) + .toUtf8()); + return nullptr; // Fail + } + auto setting = std::make_shared<Setting>(synonyms, defVal); + setting->m_storage = this; + connectSignals(*setting); + m_settings.insert(setting->id(), setting); + return setting; +} + +/* + +bool SettingsObject::registerSetting(Setting *setting) +{ + if (contains(setting->id())) + { + qDebug(QString("Failed to register setting %1. ID already exists.") + .arg(setting->id()) + .toUtf8()); + return false; // Fail + } + + m_settings.insert(setting->id(), setting); + setting->setParent(this); // Take ownership. + + // Connect signals. + connectSignals(*setting); + + // qDebug(QString("Registered setting %1.").arg(setting->id()).toUtf8()); + return true; +} +*/ +std::shared_ptr<Setting> SettingsObject::getSetting(const QString &id) const +{ + // Make sure there is a setting with the given ID. + if (!m_settings.contains(id)) + return NULL; + + return m_settings[id]; +} + +QVariant SettingsObject::get(const QString &id) const +{ + auto setting = getSetting(id); + return (setting ? setting->get() : QVariant()); +} + +bool SettingsObject::set(const QString &id, QVariant value) +{ + auto setting = getSetting(id); + if (!setting) + { + qDebug(QString("Error changing setting %1. Setting doesn't exist.").arg(id).toUtf8()); + return false; + } + else + { + setting->set(value); + return true; + } +} + +void SettingsObject::reset(const QString &id) const +{ + auto setting = getSetting(id); + if (setting) + setting->reset(); +} + +bool SettingsObject::contains(const QString &id) +{ + return m_settings.contains(id); +} + +void SettingsObject::connectSignals(const Setting &setting) +{ + connect(&setting, SIGNAL(settingChanged(const Setting &, QVariant)), + SLOT(changeSetting(const Setting &, QVariant))); + connect(&setting, SIGNAL(settingChanged(const Setting &, QVariant)), + SIGNAL(settingChanged(const Setting &, QVariant))); + + connect(&setting, SIGNAL(settingReset(Setting)), SLOT(resetSetting(const Setting &))); + connect(&setting, SIGNAL(settingReset(Setting)), SIGNAL(settingReset(const Setting &))); +} diff --git a/depends/settings/settingsobject.h b/depends/settings/settingsobject.h new file mode 100644 index 00000000..27746f2d --- /dev/null +++ b/depends/settings/settingsobject.h @@ -0,0 +1,173 @@ +/* Copyright 2013 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 <QObject> +#include <QMap> +#include <QStringList> +#include <QVariant> +#include <memory> + +#include "libsettings_config.h" + +class Setting; + +/*! + * \brief The SettingsObject handles communicating settings between the application and a + *settings file. + * The class keeps a list of Setting objects. Each Setting object represents one + * of the application's settings. These Setting objects are registered with + * a SettingsObject and can be managed similarly to the way a list works. + * + * \author Andrew Okin + * \date 2/22/2013 + * + * \sa Setting + */ +class LIBSETTINGS_EXPORT SettingsObject : public QObject +{ + Q_OBJECT +public: + explicit SettingsObject(QObject *parent = 0); + virtual ~SettingsObject(); + /*! + * Registers an override setting for the given original setting in this settings object + * + * This will fail if there is already a setting with the same ID as + * the one that is being registered. + * \return A valid Setting shared pointer if successful. + */ + std::shared_ptr<Setting> registerOverride(std::shared_ptr<Setting> original); + + /*! + * Registers the given setting with this SettingsObject and connects the necessary signals. + * + * This will fail if there is already a setting with the same ID as + * the one that is being registered. + * \return A valid Setting shared pointer if successful. + */ + std::shared_ptr<Setting> registerSetting(QStringList synonyms, + QVariant defVal = QVariant()); + + /*! + * Registers the given setting with this SettingsObject and connects the necessary signals. + * + * This will fail if there is already a setting with the same ID as + * the one that is being registered. + * \return A valid Setting shared pointer if successful. + */ + std::shared_ptr<Setting> registerSetting(QString id, QVariant defVal = QVariant()) + { + return registerSetting(QStringList(id), defVal); + } + + /*! + * \brief Gets the setting with the given ID. + * \param id The ID of the setting to get. + * \return A pointer to the setting with the given ID. + * Returns null if there is no setting with the given ID. + * \sa operator []() + */ + std::shared_ptr<Setting> getSetting(const QString &id) const; + + /*! + * \brief Gets the value of the setting with the given ID. + * \param id The ID of the setting to get. + * \return The setting's value as a QVariant. + * If no setting with the given ID exists, returns an invalid QVariant. + */ + QVariant get(const QString &id) const; + + /*! + * \brief Sets the value of the setting with the given ID. + * If no setting with the given ID exists, returns false and logs to qDebug + * \param id The ID of the setting to change. + * \param value The new value of the setting. + * \return True if successful, false if it failed. + */ + bool set(const QString &id, QVariant value); + + /*! + * \brief Reverts the setting with the given ID to default. + * \param id The ID of the setting to reset. + */ + void reset(const QString &id) const; + + /*! + * \brief Checks if this SettingsObject contains a setting with the given ID. + * \param id The ID to check for. + * \return True if the SettingsObject has a setting with the given ID. + */ + bool contains(const QString &id); + +signals: + /*! + * \brief Signal emitted when one of this SettingsObject object's settings changes. + * This is usually just connected directly to each Setting object's + * settingChanged() signals. + * \param setting A reference to the Setting object that changed. + * \param value The Setting object's new value. + */ + void settingChanged(const Setting &setting, QVariant value); + + /*! + * \brief Signal emitted when one of this SettingsObject object's settings resets. + * This is usually just connected directly to each Setting object's + * settingReset() signals. + * \param setting A reference to the Setting object that changed. + */ + void settingReset(const Setting &setting); + +protected +slots: + /*! + * \brief Changes a setting. + * This slot is usually connected to each Setting object's + * settingChanged() signal. The signal is emitted, causing this slot + * to update the setting's value in the config file. + * \param setting A reference to the Setting object that changed. + * \param value The setting's new value. + */ + virtual void changeSetting(const Setting &setting, QVariant value) = 0; + + /*! + * \brief Resets a setting. + * This slot is usually connected to each Setting object's + * settingReset() signal. The signal is emitted, causing this slot + * to update the setting's value in the config file. + * \param setting A reference to the Setting object that changed. + */ + virtual void resetSetting(const Setting &setting) = 0; + +protected: + /*! + * \brief Connects the necessary signals to the given Setting. + * \param setting The setting to connect. + */ + void connectSignals(const Setting &setting); + + /*! + * \brief Function used by Setting objects to get their values from the SettingsObject. + * \param setting The + * \return + */ + virtual QVariant retrieveValue(const Setting &setting) = 0; + + friend class Setting; + +private: + QMap<QString, std::shared_ptr<Setting>> m_settings; +}; diff --git a/depends/util/CMakeLists.txt b/depends/util/CMakeLists.txt new file mode 100644 index 00000000..db7d70e6 --- /dev/null +++ b/depends/util/CMakeLists.txt @@ -0,0 +1,61 @@ +project(libUtil) + +######## Set compiler flags ######## +IF(APPLE) + # assume clang 4.1.0+, add C++0x/C++11 stuff + message(STATUS "Using APPLE CMAKE_CXX_FLAGS") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") +ELSEIF(UNIX) + # assume GCC, add C++0x/C++11 stuff + MESSAGE(STATUS "Using UNIX CMAKE_CXX_FLAGS") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") +ELSEIF(MINGW) + MESSAGE(STATUS "Using MINGW CMAKE_CXX_FLAGS") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x") +ENDIF() + + +# Find Qt +find_package(Qt5Core REQUIRED) + +# Include Qt headers. +include_directories(${Qt5Base_INCLUDE_DIRS}) +# include_directories(${Qt5Network_INCLUDE_DIRS}) + +SET(LIBUTIL_SOURCES +include/libutil_config.h + +include/pathutils.h +src/pathutils.cpp + +include/osutils.h + +include/userutils.h +src/userutils.cpp + +include/cmdutils.h +src/cmdutils.cpp +) + +# Set the include dir path. +SET(LIBUTIL_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE) + +# Static link! +ADD_DEFINITIONS(-DLIBUTIL_STATIC) + +add_definitions(-DLIBUTIL_LIBRARY) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +IF(MultiMC_CODE_COVERAGE) + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 --coverage") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 --coverage") + SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0 --coverage") + SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g -O0 --coverage") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0 --coverage") +ENDIF(MultiMC_CODE_COVERAGE) + +add_library(libUtil STATIC ${LIBUTIL_SOURCES}) +# qt5_use_modules(libUtil Core Network) +qt5_use_modules(libUtil Core) +target_link_libraries(libUtil) diff --git a/depends/util/include/cmdutils.h b/depends/util/include/cmdutils.h new file mode 100644 index 00000000..4705f3ca --- /dev/null +++ b/depends/util/include/cmdutils.h @@ -0,0 +1,255 @@ +/* Copyright 2013 MultiMC Contributors + * + * Authors: Orochimarufan <orochimarufan.x3@gmail.com> + * + * 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 <exception> +#include <stdexcept> + +#include <QString> +#include <QVariant> +#include <QHash> +#include <QStringList> + +#include "libutil_config.h" + +/** + * @file libutil/include/cmdutils.h + * @brief commandline parsing and processing utilities + */ + +namespace Util +{ +namespace Commandline +{ + +/** + * @brief split a string into argv items like a shell would do + * @param args the argument string + * @return a QStringList containing all arguments + */ +LIBUTIL_EXPORT QStringList splitArgs(QString args); + +/** + * @brief The FlagStyle enum + * Specifies how flags are decorated + */ + +namespace FlagStyle +{ +enum Enum +{ + GNU, /**< --option and -o (GNU Style) */ + Unix, /**< -option and -o (Unix Style) */ + Windows, /**< /option and /o (Windows Style) */ +#ifdef Q_OS_WIN32 + Default = Windows +#else + Default = GNU +#endif +}; +} + +/** + * @brief The ArgumentStyle enum + */ +namespace ArgumentStyle +{ +enum Enum +{ + Space, /**< --option=value */ + Equals, /**< --option value */ + SpaceAndEquals, /**< --option[= ]value */ +#ifdef Q_OS_WIN32 + Default = Equals +#else + Default = SpaceAndEquals +#endif +}; +} + +/** + * @brief The ParsingError class + */ +class LIBUTIL_EXPORT ParsingError : public std::runtime_error +{ +public: + ParsingError(const QString &what); +}; + +/** + * @brief The Parser class + */ +class LIBUTIL_EXPORT Parser +{ +public: + /** + * @brief Parser constructor + * @param flagStyle the FlagStyle to use in this Parser + * @param argStyle the ArgumentStyle to use in this Parser + */ + Parser(FlagStyle::Enum flagStyle = FlagStyle::Default, + ArgumentStyle::Enum argStyle = ArgumentStyle::Default); + + /** + * @brief set the flag style + * @param style + */ + void setFlagStyle(FlagStyle::Enum style); + + /** + * @brief get the flag style + * @return + */ + FlagStyle::Enum flagStyle(); + + /** + * @brief set the argument style + * @param style + */ + void setArgumentStyle(ArgumentStyle::Enum style); + + /** + * @brief get the argument style + * @return + */ + ArgumentStyle::Enum argumentStyle(); + + /** + * @brief define a boolean switch + * @param name the parameter name + * @param def the default value + */ + void addSwitch(QString name, bool def = false); + + /** + * @brief define an option that takes an additional argument + * @param name the parameter name + * @param def the default value + */ + void addOption(QString name, QVariant def = QVariant()); + + /** + * @brief define a positional argument + * @param name the parameter name + * @param required wether this argument is required + * @param def the default value + */ + void addArgument(QString name, bool required = true, QVariant def = QVariant()); + + /** + * @brief adds a flag to an existing parameter + * @param name the (existing) parameter name + * @param flag the flag character + * @see addSwitch addArgument addOption + * Note: any one parameter can only have one flag + */ + void addShortOpt(QString name, QChar flag); + + /** + * @brief adds documentation to a Parameter + * @param name the parameter name + * @param metavar a string to be displayed as placeholder for the value + * @param doc a QString containing the documentation + * Note: on positional arguments, metavar replaces the name as displayed. + * on options , metavar replaces the value placeholder + */ + void addDocumentation(QString name, QString doc, QString metavar = QString()); + + /** + * @brief generate a help message + * @param progName the program name to use in the help message + * @param helpIndent how much the parameter documentation should be indented + * @param flagsInUsage whether we should use flags instead of options in the usage + * @return a help message + */ + QString compileHelp(QString progName, int helpIndent = 22, bool flagsInUsage = true); + + /** + * @brief generate a short usage message + * @param progName the program name to use in the usage message + * @param useFlags whether we should use flags instead of options + * @return a usage message + */ + QString compileUsage(QString progName, bool useFlags = true); + + /** + * @brief parse + * @param argv a QStringList containing the program ARGV + * @return a QHash mapping argument names to their values + */ + QHash<QString, QVariant> parse(QStringList argv); + + /** + * @brief clear all definitions + */ + void clear(); + + ~Parser(); + +private: + FlagStyle::Enum m_flagStyle; + ArgumentStyle::Enum m_argStyle; + + enum OptionType + { + otSwitch, + otOption + }; + + // Important: the common part MUST BE COMMON ON ALL THREE structs + struct CommonDef + { + QString name; + QString doc; + QString metavar; + QVariant def; + }; + + struct OptionDef + { + // common + QString name; + QString doc; + QString metavar; + QVariant def; + // option + OptionType type; + QChar flag; + }; + + struct PositionalDef + { + // common + QString name; + QString doc; + QString metavar; + QVariant def; + // positional + bool required; + }; + + QHash<QString, OptionDef *> m_options; + QHash<QChar, OptionDef *> m_flags; + QHash<QString, CommonDef *> m_params; + QList<PositionalDef *> m_positionals; + QList<OptionDef *> m_optionList; + + void getPrefix(QString &opt, QString &flag); +}; +} +} diff --git a/depends/util/include/libutil_config.h b/depends/util/include/libutil_config.h new file mode 100644 index 00000000..56b33b74 --- /dev/null +++ b/depends/util/include/libutil_config.h @@ -0,0 +1,28 @@ +/* Copyright 2013 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 <QtCore/QtGlobal> + +#ifdef LIBUTIL_STATIC +#define LIBUTIL_EXPORT +#else +#ifdef LIBUTIL_LIBRARY +#define LIBUTIL_EXPORT Q_DECL_EXPORT +#else +#define LIBUTIL_EXPORT Q_DECL_IMPORT +#endif +#endif diff --git a/depends/util/include/osutils.h b/depends/util/include/osutils.h new file mode 100644 index 00000000..d615d31f --- /dev/null +++ b/depends/util/include/osutils.h @@ -0,0 +1,26 @@ +/* Copyright 2013 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> + +#if defined _WIN32 | defined _WIN64 +#define WINDOWS 1 +#elif __APPLE__ &__MACH__ +#define OSX 1 +#elif __linux__ +#define LINUX 1 +#endif diff --git a/depends/util/include/pathutils.h b/depends/util/include/pathutils.h new file mode 100644 index 00000000..45c2a6de --- /dev/null +++ b/depends/util/include/pathutils.h @@ -0,0 +1,59 @@ +/* Copyright 2013 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 "libutil_config.h" + +LIBUTIL_EXPORT QString PathCombine(QString path1, QString path2); +LIBUTIL_EXPORT QString PathCombine(QString path1, QString path2, QString path3); + +LIBUTIL_EXPORT QString AbsolutePath(QString path); + +/** + * Normalize path + * + * Any paths inside the current directory will be normalized to relative paths (to current) + * Other paths will be made absolute + * + * Returns false if the path logic somehow filed (and normalizedPath in invalid) + */ +QString NormalizePath(QString path); + +LIBUTIL_EXPORT QString RemoveInvalidFilenameChars(QString string, QChar replaceWith = '-'); + +LIBUTIL_EXPORT QString DirNameFromString(QString string, QString inDir = "."); + +/** + * Creates all the folders in a path for the specified path + * last segment of the path is treated as a file name and is ignored! + */ +LIBUTIL_EXPORT bool ensureFilePathExists(QString filenamepath); + +/** + * Creates all the folders in a path for the specified path + * last segment of the path is treated as a folder name and is created! + */ +LIBUTIL_EXPORT bool ensureFolderPathExists(QString filenamepath); + +LIBUTIL_EXPORT bool copyPath(QString src, QString dst); + +/// Opens the given file in the default application. +LIBUTIL_EXPORT void openFileInDefaultProgram(QString filename); + +/// Opens the given directory in the default application. +LIBUTIL_EXPORT void openDirInDefaultProgram(QString dirpath, bool ensureExists = false); diff --git a/depends/util/include/userutils.h b/depends/util/include/userutils.h new file mode 100644 index 00000000..6ce08bce --- /dev/null +++ b/depends/util/include/userutils.h @@ -0,0 +1,17 @@ +#pragma once + +#include <QString> + +#include "libutil_config.h" + +namespace Util +{ +// Get the Directory representing the User's Desktop +LIBUTIL_EXPORT QString getDesktopDir(); + +// Create a shortcut at *location*, pointing to *dest* called with the arguments *args* +// call it *name* and assign it the icon *icon* +// return true if operation succeeded +LIBUTIL_EXPORT bool createShortCut(QString location, QString dest, QStringList args, + QString name, QString iconLocation); +} diff --git a/depends/util/src/cmdutils.cpp b/depends/util/src/cmdutils.cpp new file mode 100644 index 00000000..b12098dc --- /dev/null +++ b/depends/util/src/cmdutils.cpp @@ -0,0 +1,486 @@ +/* Copyright 2013 MultiMC Contributors + * + * Authors: Orochimarufan <orochimarufan.x3@gmail.com> + * + * 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 "include/cmdutils.h" + +/** + * @file libutil/src/cmdutils.cpp + */ + +namespace Util +{ +namespace Commandline +{ + +// commandline splitter +QStringList splitArgs(QString args) +{ + QStringList argv; + QString current; + bool escape = false; + QChar inquotes; + for (int i = 0; i < args.length(); i++) + { + QChar cchar = args.at(i); + + // \ escaped + if (escape) + { + current += cchar; + escape = false; + // in "quotes" + } + else if (!inquotes.isNull()) + { + if (cchar == 0x5C) + escape = true; + else if (cchar == inquotes) + inquotes = 0; + else + current += cchar; + // otherwise + } + else + { + if (cchar == 0x20) + { + if (!current.isEmpty()) + { + argv << current; + current.clear(); + } + } + else if (cchar == 0x22 || cchar == 0x27) + inquotes = cchar; + else + current += cchar; + } + } + if (!current.isEmpty()) + argv << current; + return argv; +} + +Parser::Parser(FlagStyle::Enum flagStyle, ArgumentStyle::Enum argStyle) +{ + m_flagStyle = flagStyle; + m_argStyle = argStyle; +} + +// styles setter/getter +void Parser::setArgumentStyle(ArgumentStyle::Enum style) +{ + m_argStyle = style; +} +ArgumentStyle::Enum Parser::argumentStyle() +{ + return m_argStyle; +} + +void Parser::setFlagStyle(FlagStyle::Enum style) +{ + m_flagStyle = style; +} +FlagStyle::Enum Parser::flagStyle() +{ + return m_flagStyle; +} + +// setup methods +void Parser::addSwitch(QString name, bool def) +{ + if (m_params.contains(name)) + throw "Name not unique"; + + OptionDef *param = new OptionDef; + param->type = otSwitch; + param->name = name; + param->metavar = QString("<%1>").arg(name); + param->def = def; + + m_options[name] = param; + m_params[name] = (CommonDef *)param; + m_optionList.append(param); +} + +void Parser::addOption(QString name, QVariant def) +{ + if (m_params.contains(name)) + throw "Name not unique"; + + OptionDef *param = new OptionDef; + param->type = otOption; + param->name = name; + param->metavar = QString("<%1>").arg(name); + param->def = def; + + m_options[name] = param; + m_params[name] = (CommonDef *)param; + m_optionList.append(param); +} + +void Parser::addArgument(QString name, bool required, QVariant def) +{ + if (m_params.contains(name)) + throw "Name not unique"; + + PositionalDef *param = new PositionalDef; + param->name = name; + param->def = def; + param->required = required; + param->metavar = name; + + m_positionals.append(param); + m_params[name] = (CommonDef *)param; +} + +void Parser::addDocumentation(QString name, QString doc, QString metavar) +{ + if (!m_params.contains(name)) + throw "Name does not exist"; + + CommonDef *param = m_params[name]; + param->doc = doc; + if (!metavar.isNull()) + param->metavar = metavar; +} + +void Parser::addShortOpt(QString name, QChar flag) +{ + if (!m_params.contains(name)) + throw "Name does not exist"; + if (!m_options.contains(name)) + throw "Name is not an Option or Swtich"; + + OptionDef *param = m_options[name]; + m_flags[flag] = param; + param->flag = flag; +} + +// help methods +QString Parser::compileHelp(QString progName, int helpIndent, bool useFlags) +{ + QStringList help; + help << compileUsage(progName, useFlags) << "\r\n"; + + // positionals + if (!m_positionals.isEmpty()) + { + help << "\r\n"; + help << "Positional arguments:\r\n"; + QListIterator<PositionalDef *> it2(m_positionals); + while (it2.hasNext()) + { + PositionalDef *param = it2.next(); + help << " " << param->metavar; + help << " " << QString(helpIndent - param->metavar.length() - 1, ' '); + help << param->doc << "\r\n"; + } + } + + // Options + if (!m_optionList.isEmpty()) + { + help << "\r\n"; + QString optPrefix, flagPrefix; + getPrefix(optPrefix, flagPrefix); + + help << "Options & Switches:\r\n"; + QListIterator<OptionDef *> it(m_optionList); + while (it.hasNext()) + { + OptionDef *option = it.next(); + help << " "; + int nameLength = optPrefix.length() + option->name.length(); + if (!option->flag.isNull()) + { + nameLength += 3 + flagPrefix.length(); + help << flagPrefix << option->flag << ", "; + } + help << optPrefix << option->name; + if (option->type == otOption) + { + QString arg = QString("%1%2").arg( + ((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar); + nameLength += arg.length(); + help << arg; + } + help << " " << QString(helpIndent - nameLength - 1, ' '); + help << option->doc << "\r\n"; + } + } + + return help.join(""); +} + +QString Parser::compileUsage(QString progName, bool useFlags) +{ + QStringList usage; + usage << "Usage: " << progName; + + QString optPrefix, flagPrefix; + getPrefix(optPrefix, flagPrefix); + + // options + QListIterator<OptionDef *> it(m_optionList); + while (it.hasNext()) + { + OptionDef *option = it.next(); + usage << " ["; + if (!option->flag.isNull() && useFlags) + usage << flagPrefix << option->flag; + else + usage << optPrefix << option->name; + if (option->type == otOption) + usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar; + usage << "]"; + } + + // arguments + QListIterator<PositionalDef *> it2(m_positionals); + while (it2.hasNext()) + { + PositionalDef *param = it2.next(); + usage << " " << (param->required ? "<" : "["); + usage << param->metavar; + usage << (param->required ? ">" : "]"); + } + + return usage.join(""); +} + +// parsing +QHash<QString, QVariant> Parser::parse(QStringList argv) +{ + QHash<QString, QVariant> map; + + QStringListIterator it(argv); + QString programName = it.next(); + + QString optionPrefix; + QString flagPrefix; + QListIterator<PositionalDef *> positionals(m_positionals); + QStringList expecting; + + getPrefix(optionPrefix, flagPrefix); + + while (it.hasNext()) + { + QString arg = it.next(); + + if (!expecting.isEmpty()) + // we were expecting an argument + { + QString name = expecting.first(); +/* + if (map.contains(name)) + throw ParsingError( + QString("Option %2%1 was given multiple times").arg(name, optionPrefix)); +*/ + map[name] = QVariant(arg); + + expecting.removeFirst(); + continue; + } + + if (arg.startsWith(optionPrefix)) + // we have an option + { + // qDebug("Found option %s", qPrintable(arg)); + + QString name = arg.mid(optionPrefix.length()); + QString equals; + + if ((m_argStyle == ArgumentStyle::Equals || + m_argStyle == ArgumentStyle::SpaceAndEquals) && + name.contains("=")) + { + int i = name.indexOf("="); + equals = name.mid(i + 1); + name = name.left(i); + } + + if (m_options.contains(name)) + { + /* + if (map.contains(name)) + throw ParsingError(QString("Option %2%1 was given multiple times") + .arg(name, optionPrefix)); +*/ + OptionDef *option = m_options[name]; + if (option->type == otSwitch) + map[name] = true; + else // if (option->type == otOption) + { + if (m_argStyle == ArgumentStyle::Space) + expecting.append(name); + else if (!equals.isNull()) + map[name] = equals; + else if (m_argStyle == ArgumentStyle::SpaceAndEquals) + expecting.append(name); + else + throw ParsingError(QString("Option %2%1 reqires an argument.") + .arg(name, optionPrefix)); + } + + continue; + } + + throw ParsingError(QString("Unknown Option %2%1").arg(name, optionPrefix)); + } + + if (arg.startsWith(flagPrefix)) + // we have (a) flag(s) + { + // qDebug("Found flags %s", qPrintable(arg)); + + QString flags = arg.mid(flagPrefix.length()); + QString equals; + + if ((m_argStyle == ArgumentStyle::Equals || + m_argStyle == ArgumentStyle::SpaceAndEquals) && + flags.contains("=")) + { + int i = flags.indexOf("="); + equals = flags.mid(i + 1); + flags = flags.left(i); + } + + for (int i = 0; i < flags.length(); i++) + { + QChar flag = flags.at(i); + + if (!m_flags.contains(flag)) + throw ParsingError(QString("Unknown flag %2%1").arg(flag, flagPrefix)); + + OptionDef *option = m_flags[flag]; +/* + if (map.contains(option->name)) + throw ParsingError(QString("Option %2%1 was given multiple times") + .arg(option->name, optionPrefix)); +*/ + if (option->type == otSwitch) + map[option->name] = true; + else // if (option->type == otOption) + { + if (m_argStyle == ArgumentStyle::Space) + expecting.append(option->name); + else if (!equals.isNull()) + if (i == flags.length() - 1) + map[option->name] = equals; + else + throw ParsingError(QString("Flag %4%2 of Argument-requiring Option " + "%1 not last flag in %4%3") + .arg(option->name, flag, flags, flagPrefix)); + else if (m_argStyle == ArgumentStyle::SpaceAndEquals) + expecting.append(option->name); + else + throw ParsingError(QString("Option %1 reqires an argument. (flag %3%2)") + .arg(option->name, flag, flagPrefix)); + } + } + + continue; + } + + // must be a positional argument + if (!positionals.hasNext()) + throw ParsingError(QString("Don't know what to do with '%1'").arg(arg)); + + PositionalDef *param = positionals.next(); + + map[param->name] = arg; + } + + // check if we're missing something + if (!expecting.isEmpty()) + throw ParsingError(QString("Was still expecting arguments for %2%1").arg( + expecting.join(QString(", ") + optionPrefix), optionPrefix)); + + while (positionals.hasNext()) + { + PositionalDef *param = positionals.next(); + if (param->required) + throw ParsingError( + QString("Missing required positional argument '%1'").arg(param->name)); + else + map[param->name] = param->def; + } + + // fill out gaps + QListIterator<OptionDef *> iter(m_optionList); + while (iter.hasNext()) + { + OptionDef *option = iter.next(); + if (!map.contains(option->name)) + map[option->name] = option->def; + } + + return map; +} + +// clear defs +void Parser::clear() +{ + m_flags.clear(); + m_params.clear(); + m_options.clear(); + + QMutableListIterator<OptionDef *> it(m_optionList); + while (it.hasNext()) + { + OptionDef *option = it.next(); + it.remove(); + delete option; + } + + QMutableListIterator<PositionalDef *> it2(m_positionals); + while (it2.hasNext()) + { + PositionalDef *arg = it2.next(); + it2.remove(); + delete arg; + } +} + +// Destructor +Parser::~Parser() +{ + clear(); +} + +// getPrefix +void Parser::getPrefix(QString &opt, QString &flag) +{ + if (m_flagStyle == FlagStyle::Windows) + opt = flag = "/"; + else if (m_flagStyle == FlagStyle::Unix) + opt = flag = "-"; + // else if (m_flagStyle == FlagStyle::GNU) + else + { + opt = "--"; + flag = "-"; + } +} + +// ParsingError +ParsingError::ParsingError(const QString &what) : std::runtime_error(what.toStdString()) +{ +} +} +} diff --git a/depends/util/src/pathutils.cpp b/depends/util/src/pathutils.cpp new file mode 100644 index 00000000..20888754 --- /dev/null +++ b/depends/util/src/pathutils.cpp @@ -0,0 +1,147 @@ +/* Copyright 2013 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 "include/pathutils.h" + +#include <QFileInfo> +#include <QDir> +#include <QDesktopServices> +#include <QUrl> +#include <QDebug> + +QString PathCombine(QString path1, QString path2) +{ + return QDir::cleanPath(path1 + QDir::separator() + path2); +} + +QString PathCombine(QString path1, QString path2, QString path3) +{ + return PathCombine(PathCombine(path1, path2), path3); +} + +QString AbsolutePath(QString path) +{ + return QFileInfo(path).absolutePath(); +} + +/** + * Normalize path + * + * Any paths inside the current directory will be normalized to relative paths (to current) + * Other paths will be made absolute + */ +QString NormalizePath(QString path) +{ + QDir a = QDir::currentPath(); + QString currentAbsolute = a.absolutePath(); + + QDir b(path); + QString newAbsolute = b.absolutePath(); + + if (newAbsolute.startsWith(currentAbsolute)) + { + return a.relativeFilePath(newAbsolute); + } + else + { + return newAbsolute; + } +} + +QString badFilenameChars = "\"\\/?<>:*|!"; + +QString RemoveInvalidFilenameChars(QString string, QChar replaceWith) +{ + for (int i = 0; i < string.length(); i++) + { + if (badFilenameChars.contains(string[i])) + { + string[i] = replaceWith; + } + } + return string; +} + +QString DirNameFromString(QString string, QString inDir) +{ + int num = 0; + QString dirName = RemoveInvalidFilenameChars(string, '-'); + while (QFileInfo(PathCombine(inDir, dirName)).exists()) + { + num++; + dirName = RemoveInvalidFilenameChars(dirName, '-') + QString::number(num); + + // If it's over 9000 + if (num > 9000) + return ""; + } + return dirName; +} + +bool ensureFilePathExists(QString filenamepath) +{ + QFileInfo a(filenamepath); + QDir dir; + QString ensuredPath = a.path(); + bool success = dir.mkpath(ensuredPath); + return success; +} + +bool ensureFolderPathExists(QString foldernamepath) +{ + QFileInfo a(foldernamepath); + QDir dir; + QString ensuredPath = a.filePath(); + bool success = dir.mkpath(ensuredPath); + return success; +} + +bool copyPath(QString src, QString dst) +{ + QDir dir(src); + if (!dir.exists()) + return false; + if (!ensureFolderPathExists(dst)) + return false; + + foreach(QString d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) + { + QString inner_src = src + QDir::separator() + d; + QString inner_dst = dst + QDir::separator() + d; + copyPath(inner_src, inner_dst); + } + + foreach(QString f, dir.entryList(QDir::Files)) + { + QFile::copy(src + QDir::separator() + f, dst + QDir::separator() + f); + } + return true; +} + +void openDirInDefaultProgram(QString path, bool ensureExists) +{ + QDir parentPath; + QDir dir(path); + if (!dir.exists()) + { + parentPath.mkpath(dir.absolutePath()); + } + QDesktopServices::openUrl("file:///" + dir.absolutePath()); +} + +void openFileInDefaultProgram(QString filename) +{ + QDesktopServices::openUrl("file:///" + QFileInfo(filename).absolutePath()); +} diff --git a/depends/util/src/userutils.cpp b/depends/util/src/userutils.cpp new file mode 100644 index 00000000..060a58e9 --- /dev/null +++ b/depends/util/src/userutils.cpp @@ -0,0 +1,127 @@ +#include "include/userutils.h" + +#include <QStandardPaths> +#include <QFile> +#include <QTextStream> + +#include "include/osutils.h" +#include "include/pathutils.h" + +// Win32 crap +#if WINDOWS + +#include <windows.h> +#include <winnls.h> +#include <shobjidl.h> +#include <objbase.h> +#include <objidl.h> +#include <shlguid.h> +#include <shlobj.h> + +bool called_coinit = false; + +HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args) +{ + HRESULT hres; + + if (!called_coinit) + { + hres = CoInitialize(NULL); + called_coinit = true; + + if (!SUCCEEDED(hres)) + { + qWarning("Failed to initialize COM. Error 0x%08X", hres); + return hres; + } + } + + IShellLink *link; + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, + (LPVOID *)&link); + + if (SUCCEEDED(hres)) + { + IPersistFile *persistFile; + + link->SetPath(targetPath); + link->SetArguments(args); + + hres = link->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile); + if (SUCCEEDED(hres)) + { + WCHAR wstr[MAX_PATH]; + + MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH); + + hres = persistFile->Save(wstr, TRUE); + persistFile->Release(); + } + link->Release(); + } + return hres; +} + +#endif + +QString Util::getDesktopDir() +{ + return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); +} + +// Cross-platform Shortcut creation +bool Util::createShortCut(QString location, QString dest, QStringList args, QString name, + QString icon) +{ +#if LINUX + location = PathCombine(location, name + ".desktop"); + qDebug("location: %s", qPrintable(location)); + + QFile f(location); + f.open(QIODevice::WriteOnly | QIODevice::Text); + QTextStream stream(&f); + + QString argstring; + if (!args.empty()) + argstring = " '" + args.join("' '") + "'"; + + stream << "[Desktop Entry]" + << "\n"; + stream << "Type=Application" + << "\n"; + stream << "TryExec=" << dest.toLocal8Bit() << "\n"; + stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n"; + stream << "Name=" << name.toLocal8Bit() << "\n"; + stream << "Icon=" << icon.toLocal8Bit() << "\n"; + + stream.flush(); + f.close(); + + f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | + QFileDevice::ExeOther); + + return true; +#elif WINDOWS + // TODO: Fix + // QFile file(PathCombine(location, name + ".lnk")); + // WCHAR *file_w; + // WCHAR *dest_w; + // WCHAR *args_w; + // file.fileName().toWCharArray(file_w); + // dest.toWCharArray(dest_w); + + // QString argStr; + // for (int i = 0; i < args.count(); i++) + // { + // argStr.append(args[i]); + // argStr.append(" "); + // } + // argStr.toWCharArray(args_w); + + // return SUCCEEDED(CreateLink(file_w, dest_w, args_w)); + return false; +#else + qWarning("Desktop Shortcuts not supported on your platform!"); + return false; +#endif +} diff --git a/depends/xz-embedded/CMakeLists.txt b/depends/xz-embedded/CMakeLists.txt new file mode 100644 index 00000000..d4987f76 --- /dev/null +++ b/depends/xz-embedded/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 2.6) +project(xz-embedded) + +option(XZ_BUILD_BCJ "Build xz-embedded with BCJ support (native binary optimization)" OFF) +option(XZ_BUILD_CRC64 "Build xz-embedded with CRC64 checksum support" ON) +option(XZ_BUILD_MINIDEC "Build a tiny utility that decompresses xz streams" OFF) + +set(CMAKE_C_FLAGS "-std=c99") + +include_directories(include) +SET(XZ_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE) + +# See include/xz.h for manual feature configuration +# tweak this list and xz.h to fit your needs + +set(XZ_SOURCES +include/xz.h +src/xz_config.h +src/xz_crc32.c +src/xz_crc64.c +src/xz_dec_lzma2.c +src/xz_dec_stream.c +src/xz_lzma2.h +src/xz_private.h +src/xz_stream.h +# src/xz_dec_bcj.c +) +# TODO: look into what would be needed for plain old lzma + +add_library(xz-embedded STATIC ${XZ_SOURCES}) +add_executable(xzminidec xzminidec.c) +target_link_libraries(xzminidec xz-embedded) diff --git a/depends/xz-embedded/include/xz.h b/depends/xz-embedded/include/xz.h new file mode 100644 index 00000000..eef8ef69 --- /dev/null +++ b/depends/xz-embedded/include/xz.h @@ -0,0 +1,321 @@ +/* + * XZ decompressor + * + * Authors: Lasse Collin <lasse.collin@tukaani.org> + * Igor Pavlov <http://7-zip.org/> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#ifndef XZ_H +#define XZ_H + +#ifdef __KERNEL__ +#include <linux/stddef.h> +#include <linux/types.h> +#else +#include <stddef.h> +#include <stdint.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Definitions that determine available features */ +#define XZ_DEC_ANY_CHECK 1 +#define XZ_USE_CRC64 1 + +// native machine code compression stuff +/* +#define XZ_DEC_X86 +#define XZ_DEC_POWERPC +#define XZ_DEC_IA64 +#define XZ_DEC_ARM +#define XZ_DEC_ARMTHUMB +#define XZ_DEC_SPARC +*/ + +/* In Linux, this is used to make extern functions static when needed. */ +#ifndef XZ_EXTERN +#define XZ_EXTERN extern +#endif + +/** + * enum xz_mode - Operation mode + * + * @XZ_SINGLE: Single-call mode. This uses less RAM than + * than multi-call modes, because the LZMA2 + * dictionary doesn't need to be allocated as + * part of the decoder state. All required data + * structures are allocated at initialization, + * so xz_dec_run() cannot return XZ_MEM_ERROR. + * @XZ_PREALLOC: Multi-call mode with preallocated LZMA2 + * dictionary buffer. All data structures are + * allocated at initialization, so xz_dec_run() + * cannot return XZ_MEM_ERROR. + * @XZ_DYNALLOC: Multi-call mode. The LZMA2 dictionary is + * allocated once the required size has been + * parsed from the stream headers. If the + * allocation fails, xz_dec_run() will return + * XZ_MEM_ERROR. + * + * It is possible to enable support only for a subset of the above + * modes at compile time by defining XZ_DEC_SINGLE, XZ_DEC_PREALLOC, + * or XZ_DEC_DYNALLOC. The xz_dec kernel module is always compiled + * with support for all operation modes, but the preboot code may + * be built with fewer features to minimize code size. + */ +enum xz_mode +{ + XZ_SINGLE, + XZ_PREALLOC, + XZ_DYNALLOC +}; + +/** + * enum xz_ret - Return codes + * @XZ_OK: Everything is OK so far. More input or more + * output space is required to continue. This + * return code is possible only in multi-call mode + * (XZ_PREALLOC or XZ_DYNALLOC). + * @XZ_STREAM_END: Operation finished successfully. + * @XZ_UNSUPPORTED_CHECK: Integrity check type is not supported. Decoding + * is still possible in multi-call mode by simply + * calling xz_dec_run() again. + * Note that this return value is used only if + * XZ_DEC_ANY_CHECK was defined at build time, + * which is not used in the kernel. Unsupported + * check types return XZ_OPTIONS_ERROR if + * XZ_DEC_ANY_CHECK was not defined at build time. + * @XZ_MEM_ERROR: Allocating memory failed. This return code is + * possible only if the decoder was initialized + * with XZ_DYNALLOC. The amount of memory that was + * tried to be allocated was no more than the + * dict_max argument given to xz_dec_init(). + * @XZ_MEMLIMIT_ERROR: A bigger LZMA2 dictionary would be needed than + * allowed by the dict_max argument given to + * xz_dec_init(). This return value is possible + * only in multi-call mode (XZ_PREALLOC or + * XZ_DYNALLOC); the single-call mode (XZ_SINGLE) + * ignores the dict_max argument. + * @XZ_FORMAT_ERROR: File format was not recognized (wrong magic + * bytes). + * @XZ_OPTIONS_ERROR: This implementation doesn't support the requested + * compression options. In the decoder this means + * that the header CRC32 matches, but the header + * itself specifies something that we don't support. + * @XZ_DATA_ERROR: Compressed data is corrupt. + * @XZ_BUF_ERROR: Cannot make any progress. Details are slightly + * different between multi-call and single-call + * mode; more information below. + * + * In multi-call mode, XZ_BUF_ERROR is returned when two consecutive calls + * to XZ code cannot consume any input and cannot produce any new output. + * This happens when there is no new input available, or the output buffer + * is full while at least one output byte is still pending. Assuming your + * code is not buggy, you can get this error only when decoding a compressed + * stream that is truncated or otherwise corrupt. + * + * In single-call mode, XZ_BUF_ERROR is returned only when the output buffer + * is too small or the compressed input is corrupt in a way that makes the + * decoder produce more output than the caller expected. When it is + * (relatively) clear that the compressed input is truncated, XZ_DATA_ERROR + * is used instead of XZ_BUF_ERROR. + */ +enum xz_ret +{ + XZ_OK, + XZ_STREAM_END, + XZ_UNSUPPORTED_CHECK, + XZ_MEM_ERROR, + XZ_MEMLIMIT_ERROR, + XZ_FORMAT_ERROR, + XZ_OPTIONS_ERROR, + XZ_DATA_ERROR, + XZ_BUF_ERROR +}; + +/** + * struct xz_buf - Passing input and output buffers to XZ code + * @in: Beginning of the input buffer. This may be NULL if and only + * if in_pos is equal to in_size. + * @in_pos: Current position in the input buffer. This must not exceed + * in_size. + * @in_size: Size of the input buffer + * @out: Beginning of the output buffer. This may be NULL if and only + * if out_pos is equal to out_size. + * @out_pos: Current position in the output buffer. This must not exceed + * out_size. + * @out_size: Size of the output buffer + * + * Only the contents of the output buffer from out[out_pos] onward, and + * the variables in_pos and out_pos are modified by the XZ code. + */ +struct xz_buf +{ + const uint8_t *in; + size_t in_pos; + size_t in_size; + + uint8_t *out; + size_t out_pos; + size_t out_size; +}; + +/** + * struct xz_dec - Opaque type to hold the XZ decoder state + */ +struct xz_dec; + +/** + * xz_dec_init() - Allocate and initialize a XZ decoder state + * @mode: Operation mode + * @dict_max: Maximum size of the LZMA2 dictionary (history buffer) for + * multi-call decoding. This is ignored in single-call mode + * (mode == XZ_SINGLE). LZMA2 dictionary is always 2^n bytes + * or 2^n + 2^(n-1) bytes (the latter sizes are less common + * in practice), so other values for dict_max don't make sense. + * In the kernel, dictionary sizes of 64 KiB, 128 KiB, 256 KiB, + * 512 KiB, and 1 MiB are probably the only reasonable values, + * except for kernel and initramfs images where a bigger + * dictionary can be fine and useful. + * + * Single-call mode (XZ_SINGLE): xz_dec_run() decodes the whole stream at + * once. The caller must provide enough output space or the decoding will + * fail. The output space is used as the dictionary buffer, which is why + * there is no need to allocate the dictionary as part of the decoder's + * internal state. + * + * Because the output buffer is used as the workspace, streams encoded using + * a big dictionary are not a problem in single-call mode. It is enough that + * the output buffer is big enough to hold the actual uncompressed data; it + * can be smaller than the dictionary size stored in the stream headers. + * + * Multi-call mode with preallocated dictionary (XZ_PREALLOC): dict_max bytes + * of memory is preallocated for the LZMA2 dictionary. This way there is no + * risk that xz_dec_run() could run out of memory, since xz_dec_run() will + * never allocate any memory. Instead, if the preallocated dictionary is too + * small for decoding the given input stream, xz_dec_run() will return + * XZ_MEMLIMIT_ERROR. Thus, it is important to know what kind of data will be + * decoded to avoid allocating excessive amount of memory for the dictionary. + * + * Multi-call mode with dynamically allocated dictionary (XZ_DYNALLOC): + * dict_max specifies the maximum allowed dictionary size that xz_dec_run() + * may allocate once it has parsed the dictionary size from the stream + * headers. This way excessive allocations can be avoided while still + * limiting the maximum memory usage to a sane value to prevent running the + * system out of memory when decompressing streams from untrusted sources. + * + * On success, xz_dec_init() returns a pointer to struct xz_dec, which is + * ready to be used with xz_dec_run(). If memory allocation fails, + * xz_dec_init() returns NULL. + */ +XZ_EXTERN struct xz_dec *xz_dec_init(enum xz_mode mode, uint32_t dict_max); + +/** + * xz_dec_run() - Run the XZ decoder + * @s: Decoder state allocated using xz_dec_init() + * @b: Input and output buffers + * + * The possible return values depend on build options and operation mode. + * See enum xz_ret for details. + * + * Note that if an error occurs in single-call mode (return value is not + * XZ_STREAM_END), b->in_pos and b->out_pos are not modified and the + * contents of the output buffer from b->out[b->out_pos] onward are + * undefined. This is true even after XZ_BUF_ERROR, because with some filter + * chains, there may be a second pass over the output buffer, and this pass + * cannot be properly done if the output buffer is truncated. Thus, you + * cannot give the single-call decoder a too small buffer and then expect to + * get that amount valid data from the beginning of the stream. You must use + * the multi-call decoder if you don't want to uncompress the whole stream. + */ +XZ_EXTERN enum xz_ret xz_dec_run(struct xz_dec *s, struct xz_buf *b); + +/** + * xz_dec_reset() - Reset an already allocated decoder state + * @s: Decoder state allocated using xz_dec_init() + * + * This function can be used to reset the multi-call decoder state without + * freeing and reallocating memory with xz_dec_end() and xz_dec_init(). + * + * In single-call mode, xz_dec_reset() is always called in the beginning of + * xz_dec_run(). Thus, explicit call to xz_dec_reset() is useful only in + * multi-call mode. + */ +XZ_EXTERN void xz_dec_reset(struct xz_dec *s); + +/** + * xz_dec_end() - Free the memory allocated for the decoder state + * @s: Decoder state allocated using xz_dec_init(). If s is NULL, + * this function does nothing. + */ +XZ_EXTERN void xz_dec_end(struct xz_dec *s); + +/* + * Standalone build (userspace build or in-kernel build for boot time use) + * needs a CRC32 implementation. For normal in-kernel use, kernel's own + * CRC32 module is used instead, and users of this module don't need to + * care about the functions below. + */ +#ifndef XZ_INTERNAL_CRC32 +#ifdef __KERNEL__ +#define XZ_INTERNAL_CRC32 0 +#else +#define XZ_INTERNAL_CRC32 1 +#endif +#endif + +/* + * If CRC64 support has been enabled with XZ_USE_CRC64, a CRC64 + * implementation is needed too. + */ +#ifndef XZ_USE_CRC64 +#undef XZ_INTERNAL_CRC64 +#define XZ_INTERNAL_CRC64 0 +#endif +#ifndef XZ_INTERNAL_CRC64 +#ifdef __KERNEL__ +#error Using CRC64 in the kernel has not been implemented. +#else +#define XZ_INTERNAL_CRC64 1 +#endif +#endif + +#if XZ_INTERNAL_CRC32 +/* + * This must be called before any other xz_* function to initialize + * the CRC32 lookup table. + */ +XZ_EXTERN void xz_crc32_init(void); + +/* + * Update CRC32 value using the polynomial from IEEE-802.3. To start a new + * calculation, the third argument must be zero. To continue the calculation, + * the previously returned value is passed as the third argument. + */ +XZ_EXTERN uint32_t xz_crc32(const uint8_t *buf, size_t size, uint32_t crc); +#endif + +#if XZ_INTERNAL_CRC64 +/* + * This must be called before any other xz_* function (except xz_crc32_init()) + * to initialize the CRC64 lookup table. + */ +XZ_EXTERN void xz_crc64_init(void); + +/* + * Update CRC64 value using the polynomial from ECMA-182. To start a new + * calculation, the third argument must be zero. To continue the calculation, + * the previously returned value is passed as the third argument. + */ +XZ_EXTERN uint64_t xz_crc64(const uint8_t *buf, size_t size, uint64_t crc); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/depends/xz-embedded/src/xz_config.h b/depends/xz-embedded/src/xz_config.h new file mode 100644 index 00000000..40805b75 --- /dev/null +++ b/depends/xz-embedded/src/xz_config.h @@ -0,0 +1,119 @@ +/* + * Private includes and definitions for userspace use of XZ Embedded + * + * Author: Lasse Collin <lasse.collin@tukaani.org> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#ifndef XZ_CONFIG_H +#define XZ_CONFIG_H + +/* Uncomment to enable CRC64 support. */ +/* #define XZ_USE_CRC64 */ + +/* Uncomment as needed to enable BCJ filter decoders. */ +/* #define XZ_DEC_X86 */ +/* #define XZ_DEC_POWERPC */ +/* #define XZ_DEC_IA64 */ +/* #define XZ_DEC_ARM */ +/* #define XZ_DEC_ARMTHUMB */ +/* #define XZ_DEC_SPARC */ + +/* + * MSVC doesn't support modern C but XZ Embedded is mostly C89 + * so these are enough. + */ +#ifdef _MSC_VER +typedef unsigned char bool; +#define true 1 +#define false 0 +#define inline __inline +#else +#include <stdbool.h> +#endif + +#include <stdlib.h> +#include <string.h> + +#include "xz.h" + +#define kmalloc(size, flags) malloc(size) +#define kfree(ptr) free(ptr) +#define vmalloc(size) malloc(size) +#define vfree(ptr) free(ptr) + +#define memeq(a, b, size) (memcmp(a, b, size) == 0) +#define memzero(buf, size) memset(buf, 0, size) + +#ifndef min +#define min(x, y) ((x) < (y) ? (x) : (y)) +#endif +#define min_t(type, x, y) min(x, y) + +/* + * Some functions have been marked with __always_inline to keep the + * performance reasonable even when the compiler is optimizing for + * small code size. You may be able to save a few bytes by #defining + * __always_inline to plain inline, but don't complain if the code + * becomes slow. + * + * NOTE: System headers on GNU/Linux may #define this macro already, + * so if you want to change it, you need to #undef it first. + */ +#ifndef __always_inline +#ifdef __GNUC__ +#define __always_inline inline __attribute__((__always_inline__)) +#else +#define __always_inline inline +#endif +#endif + +/* Inline functions to access unaligned unsigned 32-bit integers */ +#ifndef get_unaligned_le32 +static inline uint32_t get_unaligned_le32(const uint8_t *buf) +{ + return (uint32_t)buf[0] | ((uint32_t)buf[1] << 8) | ((uint32_t)buf[2] << 16) | + ((uint32_t)buf[3] << 24); +} +#endif + +#ifndef get_unaligned_be32 +static inline uint32_t get_unaligned_be32(const uint8_t *buf) +{ + return (uint32_t)(buf[0] << 24) | ((uint32_t)buf[1] << 16) | ((uint32_t)buf[2] << 8) | + (uint32_t)buf[3]; +} +#endif + +#ifndef put_unaligned_le32 +static inline void put_unaligned_le32(uint32_t val, uint8_t *buf) +{ + buf[0] = (uint8_t)val; + buf[1] = (uint8_t)(val >> 8); + buf[2] = (uint8_t)(val >> 16); + buf[3] = (uint8_t)(val >> 24); +} +#endif + +#ifndef put_unaligned_be32 +static inline void put_unaligned_be32(uint32_t val, uint8_t *buf) +{ + buf[0] = (uint8_t)(val >> 24); + buf[1] = (uint8_t)(val >> 16); + buf[2] = (uint8_t)(val >> 8); + buf[3] = (uint8_t)val; +} +#endif + +/* + * Use get_unaligned_le32() also for aligned access for simplicity. On + * little endian systems, #define get_le32(ptr) (*(const uint32_t *)(ptr)) + * could save a few bytes in code size. + */ +#ifndef get_le32 +#define get_le32 get_unaligned_le32 +#endif + +#endif diff --git a/depends/xz-embedded/src/xz_crc32.c b/depends/xz-embedded/src/xz_crc32.c new file mode 100644 index 00000000..c412662b --- /dev/null +++ b/depends/xz-embedded/src/xz_crc32.c @@ -0,0 +1,61 @@ +/* + * CRC32 using the polynomial from IEEE-802.3 + * + * Authors: Lasse Collin <lasse.collin@tukaani.org> + * Igor Pavlov <http://7-zip.org/> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +/* + * This is not the fastest implementation, but it is pretty compact. + * The fastest versions of xz_crc32() on modern CPUs without hardware + * accelerated CRC instruction are 3-5 times as fast as this version, + * but they are bigger and use more memory for the lookup table. + */ + +#include "xz_private.h" + +/* + * STATIC_RW_DATA is used in the pre-boot environment on some architectures. + * See <linux/decompress/mm.h> for details. + */ +#ifndef STATIC_RW_DATA +#define STATIC_RW_DATA static +#endif + +STATIC_RW_DATA uint32_t xz_crc32_table[256]; + +XZ_EXTERN void xz_crc32_init(void) +{ + const uint32_t poly = 0xEDB88320; + + uint32_t i; + uint32_t j; + uint32_t r; + + for (i = 0; i < 256; ++i) + { + r = i; + for (j = 0; j < 8; ++j) + r = (r >> 1) ^ (poly & ~((r & 1) - 1)); + + xz_crc32_table[i] = r; + } + + return; +} + +XZ_EXTERN uint32_t xz_crc32(const uint8_t *buf, size_t size, uint32_t crc) +{ + crc = ~crc; + + while (size != 0) + { + crc = xz_crc32_table[*buf++ ^ (crc & 0xFF)] ^ (crc >> 8); + --size; + } + + return ~crc; +} diff --git a/depends/xz-embedded/src/xz_crc64.c b/depends/xz-embedded/src/xz_crc64.c new file mode 100644 index 00000000..4794b9d3 --- /dev/null +++ b/depends/xz-embedded/src/xz_crc64.c @@ -0,0 +1,52 @@ +/* + * CRC64 using the polynomial from ECMA-182 + * + * This file is similar to xz_crc32.c. See the comments there. + * + * Authors: Lasse Collin <lasse.collin@tukaani.org> + * Igor Pavlov <http://7-zip.org/> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#include "xz_private.h" + +#ifndef STATIC_RW_DATA +#define STATIC_RW_DATA static +#endif + +STATIC_RW_DATA uint64_t xz_crc64_table[256]; + +XZ_EXTERN void xz_crc64_init(void) +{ + const uint64_t poly = 0xC96C5795D7870F42; + + uint32_t i; + uint32_t j; + uint64_t r; + + for (i = 0; i < 256; ++i) + { + r = i; + for (j = 0; j < 8; ++j) + r = (r >> 1) ^ (poly & ~((r & 1) - 1)); + + xz_crc64_table[i] = r; + } + + return; +} + +XZ_EXTERN uint64_t xz_crc64(const uint8_t *buf, size_t size, uint64_t crc) +{ + crc = ~crc; + + while (size != 0) + { + crc = xz_crc64_table[*buf++ ^ (crc & 0xFF)] ^ (crc >> 8); + --size; + } + + return ~crc; +} diff --git a/depends/xz-embedded/src/xz_dec_bcj.c b/depends/xz-embedded/src/xz_dec_bcj.c new file mode 100644 index 00000000..9ffda3bd --- /dev/null +++ b/depends/xz-embedded/src/xz_dec_bcj.c @@ -0,0 +1,588 @@ +/* + * Branch/Call/Jump (BCJ) filter decoders + * + * Authors: Lasse Collin <lasse.collin@tukaani.org> + * Igor Pavlov <http://7-zip.org/> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#include "xz_private.h" + +/* + * The rest of the file is inside this ifdef. It makes things a little more + * convenient when building without support for any BCJ filters. + */ +#ifdef XZ_DEC_BCJ + +struct xz_dec_bcj +{ + /* Type of the BCJ filter being used */ + enum + { + BCJ_X86 = 4, /* x86 or x86-64 */ + BCJ_POWERPC = 5, /* Big endian only */ + BCJ_IA64 = 6, /* Big or little endian */ + BCJ_ARM = 7, /* Little endian only */ + BCJ_ARMTHUMB = 8, /* Little endian only */ + BCJ_SPARC = 9 /* Big or little endian */ + } type; + + /* + * Return value of the next filter in the chain. We need to preserve + * this information across calls, because we must not call the next + * filter anymore once it has returned XZ_STREAM_END. + */ + enum xz_ret ret; + + /* True if we are operating in single-call mode. */ + bool single_call; + + /* + * Absolute position relative to the beginning of the uncompressed + * data (in a single .xz Block). We care only about the lowest 32 + * bits so this doesn't need to be uint64_t even with big files. + */ + uint32_t pos; + + /* x86 filter state */ + uint32_t x86_prev_mask; + + /* Temporary space to hold the variables from struct xz_buf */ + uint8_t *out; + size_t out_pos; + size_t out_size; + + struct + { + /* Amount of already filtered data in the beginning of buf */ + size_t filtered; + + /* Total amount of data currently stored in buf */ + size_t size; + + /* + * Buffer to hold a mix of filtered and unfiltered data. This + * needs to be big enough to hold Alignment + 2 * Look-ahead: + * + * Type Alignment Look-ahead + * x86 1 4 + * PowerPC 4 0 + * IA-64 16 0 + * ARM 4 0 + * ARM-Thumb 2 2 + * SPARC 4 0 + */ + uint8_t buf[16]; + } temp; +}; + +#ifdef XZ_DEC_X86 +/* + * This is used to test the most significant byte of a memory address + * in an x86 instruction. + */ +static inline int bcj_x86_test_msbyte(uint8_t b) +{ + return b == 0x00 || b == 0xFF; +} + +static size_t bcj_x86(struct xz_dec_bcj *s, uint8_t *buf, size_t size) +{ + static const bool mask_to_allowed_status[8] = {true, true, true, false, + true, false, false, false}; + + static const uint8_t mask_to_bit_num[8] = {0, 1, 2, 2, 3, 3, 3, 3}; + + size_t i; + size_t prev_pos = (size_t) - 1; + uint32_t prev_mask = s->x86_prev_mask; + uint32_t src; + uint32_t dest; + uint32_t j; + uint8_t b; + + if (size <= 4) + return 0; + + size -= 4; + for (i = 0; i < size; ++i) + { + if ((buf[i] & 0xFE) != 0xE8) + continue; + + prev_pos = i - prev_pos; + if (prev_pos > 3) + { + prev_mask = 0; + } + else + { + prev_mask = (prev_mask << (prev_pos - 1)) & 7; + if (prev_mask != 0) + { + b = buf[i + 4 - mask_to_bit_num[prev_mask]]; + if (!mask_to_allowed_status[prev_mask] || bcj_x86_test_msbyte(b)) + { + prev_pos = i; + prev_mask = (prev_mask << 1) | 1; + continue; + } + } + } + + prev_pos = i; + + if (bcj_x86_test_msbyte(buf[i + 4])) + { + src = get_unaligned_le32(buf + i + 1); + while (true) + { + dest = src - (s->pos + (uint32_t)i + 5); + if (prev_mask == 0) + break; + + j = mask_to_bit_num[prev_mask] * 8; + b = (uint8_t)(dest >> (24 - j)); + if (!bcj_x86_test_msbyte(b)) + break; + + src = dest ^ (((uint32_t)1 << (32 - j)) - 1); + } + + dest &= 0x01FFFFFF; + dest |= (uint32_t)0 - (dest & 0x01000000); + put_unaligned_le32(dest, buf + i + 1); + i += 4; + } + else + { + prev_mask = (prev_mask << 1) | 1; + } + } + + prev_pos = i - prev_pos; + s->x86_prev_mask = prev_pos > 3 ? 0 : prev_mask << (prev_pos - 1); + return i; +} +#endif + +#ifdef XZ_DEC_POWERPC +static size_t bcj_powerpc(struct xz_dec_bcj *s, uint8_t *buf, size_t size) +{ + size_t i; + uint32_t instr; + + for (i = 0; i + 4 <= size; i += 4) + { + instr = get_unaligned_be32(buf + i); + if ((instr & 0xFC000003) == 0x48000001) + { + instr &= 0x03FFFFFC; + instr -= s->pos + (uint32_t)i; + instr &= 0x03FFFFFC; + instr |= 0x48000001; + put_unaligned_be32(instr, buf + i); + } + } + + return i; +} +#endif + +#ifdef XZ_DEC_IA64 +static size_t bcj_ia64(struct xz_dec_bcj *s, uint8_t *buf, size_t size) +{ + static const uint8_t branch_table[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 4, 6, 6, 0, 0, 7, 7, 4, 4, 0, 0, 4, 4, 0, 0}; + + /* + * The local variables take a little bit stack space, but it's less + * than what LZMA2 decoder takes, so it doesn't make sense to reduce + * stack usage here without doing that for the LZMA2 decoder too. + */ + + /* Loop counters */ + size_t i; + size_t j; + + /* Instruction slot (0, 1, or 2) in the 128-bit instruction word */ + uint32_t slot; + + /* Bitwise offset of the instruction indicated by slot */ + uint32_t bit_pos; + + /* bit_pos split into byte and bit parts */ + uint32_t byte_pos; + uint32_t bit_res; + + /* Address part of an instruction */ + uint32_t addr; + + /* Mask used to detect which instructions to convert */ + uint32_t mask; + + /* 41-bit instruction stored somewhere in the lowest 48 bits */ + uint64_t instr; + + /* Instruction normalized with bit_res for easier manipulation */ + uint64_t norm; + + for (i = 0; i + 16 <= size; i += 16) + { + mask = branch_table[buf[i] & 0x1F]; + for (slot = 0, bit_pos = 5; slot < 3; ++slot, bit_pos += 41) + { + if (((mask >> slot) & 1) == 0) + continue; + + byte_pos = bit_pos >> 3; + bit_res = bit_pos & 7; + instr = 0; + for (j = 0; j < 6; ++j) + instr |= (uint64_t)(buf[i + j + byte_pos]) << (8 * j); + + norm = instr >> bit_res; + + if (((norm >> 37) & 0x0F) == 0x05 && ((norm >> 9) & 0x07) == 0) + { + addr = (norm >> 13) & 0x0FFFFF; + addr |= ((uint32_t)(norm >> 36) & 1) << 20; + addr <<= 4; + addr -= s->pos + (uint32_t)i; + addr >>= 4; + + norm &= ~((uint64_t)0x8FFFFF << 13); + norm |= (uint64_t)(addr & 0x0FFFFF) << 13; + norm |= (uint64_t)(addr & 0x100000) << (36 - 20); + + instr &= (1 << bit_res) - 1; + instr |= norm << bit_res; + + for (j = 0; j < 6; j++) + buf[i + j + byte_pos] = (uint8_t)(instr >> (8 * j)); + } + } + } + + return i; +} +#endif + +#ifdef XZ_DEC_ARM +static size_t bcj_arm(struct xz_dec_bcj *s, uint8_t *buf, size_t size) +{ + size_t i; + uint32_t addr; + + for (i = 0; i + 4 <= size; i += 4) + { + if (buf[i + 3] == 0xEB) + { + addr = + (uint32_t)buf[i] | ((uint32_t)buf[i + 1] << 8) | ((uint32_t)buf[i + 2] << 16); + addr <<= 2; + addr -= s->pos + (uint32_t)i + 8; + addr >>= 2; + buf[i] = (uint8_t)addr; + buf[i + 1] = (uint8_t)(addr >> 8); + buf[i + 2] = (uint8_t)(addr >> 16); + } + } + + return i; +} +#endif + +#ifdef XZ_DEC_ARMTHUMB +static size_t bcj_armthumb(struct xz_dec_bcj *s, uint8_t *buf, size_t size) +{ + size_t i; + uint32_t addr; + + for (i = 0; i + 4 <= size; i += 2) + { + if ((buf[i + 1] & 0xF8) == 0xF0 && (buf[i + 3] & 0xF8) == 0xF8) + { + addr = (((uint32_t)buf[i + 1] & 0x07) << 19) | ((uint32_t)buf[i] << 11) | + (((uint32_t)buf[i + 3] & 0x07) << 8) | (uint32_t)buf[i + 2]; + addr <<= 1; + addr -= s->pos + (uint32_t)i + 4; + addr >>= 1; + buf[i + 1] = (uint8_t)(0xF0 | ((addr >> 19) & 0x07)); + buf[i] = (uint8_t)(addr >> 11); + buf[i + 3] = (uint8_t)(0xF8 | ((addr >> 8) & 0x07)); + buf[i + 2] = (uint8_t)addr; + i += 2; + } + } + + return i; +} +#endif + +#ifdef XZ_DEC_SPARC +static size_t bcj_sparc(struct xz_dec_bcj *s, uint8_t *buf, size_t size) +{ + size_t i; + uint32_t instr; + + for (i = 0; i + 4 <= size; i += 4) + { + instr = get_unaligned_be32(buf + i); + if ((instr >> 22) == 0x100 || (instr >> 22) == 0x1FF) + { + instr <<= 2; + instr -= s->pos + (uint32_t)i; + instr >>= 2; + instr = + ((uint32_t)0x40000000 - (instr & 0x400000)) | 0x40000000 | (instr & 0x3FFFFF); + put_unaligned_be32(instr, buf + i); + } + } + + return i; +} +#endif + +/* + * Apply the selected BCJ filter. Update *pos and s->pos to match the amount + * of data that got filtered. + * + * NOTE: This is implemented as a switch statement to avoid using function + * pointers, which could be problematic in the kernel boot code, which must + * avoid pointers to static data (at least on x86). + */ +static void bcj_apply(struct xz_dec_bcj *s, uint8_t *buf, size_t *pos, size_t size) +{ + size_t filtered; + + buf += *pos; + size -= *pos; + + switch (s->type) + { +#ifdef XZ_DEC_X86 + case BCJ_X86: + filtered = bcj_x86(s, buf, size); + break; +#endif +#ifdef XZ_DEC_POWERPC + case BCJ_POWERPC: + filtered = bcj_powerpc(s, buf, size); + break; +#endif +#ifdef XZ_DEC_IA64 + case BCJ_IA64: + filtered = bcj_ia64(s, buf, size); + break; +#endif +#ifdef XZ_DEC_ARM + case BCJ_ARM: + filtered = bcj_arm(s, buf, size); + break; +#endif +#ifdef XZ_DEC_ARMTHUMB + case BCJ_ARMTHUMB: + filtered = bcj_armthumb(s, buf, size); + break; +#endif +#ifdef XZ_DEC_SPARC + case BCJ_SPARC: + filtered = bcj_sparc(s, buf, size); + break; +#endif + default: + /* Never reached but silence compiler warnings. */ + filtered = 0; + break; + } + + *pos += filtered; + s->pos += filtered; +} + +/* + * Flush pending filtered data from temp to the output buffer. + * Move the remaining mixture of possibly filtered and unfiltered + * data to the beginning of temp. + */ +static void bcj_flush(struct xz_dec_bcj *s, struct xz_buf *b) +{ + size_t copy_size; + + copy_size = min_t(size_t, s->temp.filtered, b->out_size - b->out_pos); + memcpy(b->out + b->out_pos, s->temp.buf, copy_size); + b->out_pos += copy_size; + + s->temp.filtered -= copy_size; + s->temp.size -= copy_size; + memmove(s->temp.buf, s->temp.buf + copy_size, s->temp.size); +} + +/* + * The BCJ filter functions are primitive in sense that they process the + * data in chunks of 1-16 bytes. To hide this issue, this function does + * some buffering. + */ +XZ_EXTERN enum xz_ret xz_dec_bcj_run(struct xz_dec_bcj *s, struct xz_dec_lzma2 *lzma2, + struct xz_buf *b) +{ + size_t out_start; + + /* + * Flush pending already filtered data to the output buffer. Return + * immediatelly if we couldn't flush everything, or if the next + * filter in the chain had already returned XZ_STREAM_END. + */ + if (s->temp.filtered > 0) + { + bcj_flush(s, b); + if (s->temp.filtered > 0) + return XZ_OK; + + if (s->ret == XZ_STREAM_END) + return XZ_STREAM_END; + } + + /* + * If we have more output space than what is currently pending in + * temp, copy the unfiltered data from temp to the output buffer + * and try to fill the output buffer by decoding more data from the + * next filter in the chain. Apply the BCJ filter on the new data + * in the output buffer. If everything cannot be filtered, copy it + * to temp and rewind the output buffer position accordingly. + * + * This needs to be always run when temp.size == 0 to handle a special + * case where the output buffer is full and the next filter has no + * more output coming but hasn't returned XZ_STREAM_END yet. + */ + if (s->temp.size < b->out_size - b->out_pos || s->temp.size == 0) + { + out_start = b->out_pos; + memcpy(b->out + b->out_pos, s->temp.buf, s->temp.size); + b->out_pos += s->temp.size; + + s->ret = xz_dec_lzma2_run(lzma2, b); + if (s->ret != XZ_STREAM_END && (s->ret != XZ_OK || s->single_call)) + return s->ret; + + bcj_apply(s, b->out, &out_start, b->out_pos); + + /* + * As an exception, if the next filter returned XZ_STREAM_END, + * we can do that too, since the last few bytes that remain + * unfiltered are meant to remain unfiltered. + */ + if (s->ret == XZ_STREAM_END) + return XZ_STREAM_END; + + s->temp.size = b->out_pos - out_start; + b->out_pos -= s->temp.size; + memcpy(s->temp.buf, b->out + b->out_pos, s->temp.size); + + /* + * If there wasn't enough input to the next filter to fill + * the output buffer with unfiltered data, there's no point + * to try decoding more data to temp. + */ + if (b->out_pos + s->temp.size < b->out_size) + return XZ_OK; + } + + /* + * We have unfiltered data in temp. If the output buffer isn't full + * yet, try to fill the temp buffer by decoding more data from the + * next filter. Apply the BCJ filter on temp. Then we hopefully can + * fill the actual output buffer by copying filtered data from temp. + * A mix of filtered and unfiltered data may be left in temp; it will + * be taken care on the next call to this function. + */ + if (b->out_pos < b->out_size) + { + /* Make b->out{,_pos,_size} temporarily point to s->temp. */ + s->out = b->out; + s->out_pos = b->out_pos; + s->out_size = b->out_size; + b->out = s->temp.buf; + b->out_pos = s->temp.size; + b->out_size = sizeof(s->temp.buf); + + s->ret = xz_dec_lzma2_run(lzma2, b); + + s->temp.size = b->out_pos; + b->out = s->out; + b->out_pos = s->out_pos; + b->out_size = s->out_size; + + if (s->ret != XZ_OK && s->ret != XZ_STREAM_END) + return s->ret; + + bcj_apply(s, s->temp.buf, &s->temp.filtered, s->temp.size); + + /* + * If the next filter returned XZ_STREAM_END, we mark that + * everything is filtered, since the last unfiltered bytes + * of the stream are meant to be left as is. + */ + if (s->ret == XZ_STREAM_END) + s->temp.filtered = s->temp.size; + + bcj_flush(s, b); + if (s->temp.filtered > 0) + return XZ_OK; + } + + return s->ret; +} + +XZ_EXTERN struct xz_dec_bcj *xz_dec_bcj_create(bool single_call) +{ + struct xz_dec_bcj *s = kmalloc(sizeof(*s), GFP_KERNEL); + if (s != NULL) + s->single_call = single_call; + + return s; +} + +XZ_EXTERN enum xz_ret xz_dec_bcj_reset(struct xz_dec_bcj *s, uint8_t id) +{ + switch (id) + { +#ifdef XZ_DEC_X86 + case BCJ_X86: +#endif +#ifdef XZ_DEC_POWERPC + case BCJ_POWERPC: +#endif +#ifdef XZ_DEC_IA64 + case BCJ_IA64: +#endif +#ifdef XZ_DEC_ARM + case BCJ_ARM: +#endif +#ifdef XZ_DEC_ARMTHUMB + case BCJ_ARMTHUMB: +#endif +#ifdef XZ_DEC_SPARC + case BCJ_SPARC: +#endif + break; + + default: + /* Unsupported Filter ID */ + return XZ_OPTIONS_ERROR; + } + + s->type = id; + s->ret = XZ_OK; + s->pos = 0; + s->x86_prev_mask = 0; + s->temp.filtered = 0; + s->temp.size = 0; + + return XZ_OK; +} + +#endif diff --git a/depends/xz-embedded/src/xz_dec_lzma2.c b/depends/xz-embedded/src/xz_dec_lzma2.c new file mode 100644 index 00000000..3d7b9a2e --- /dev/null +++ b/depends/xz-embedded/src/xz_dec_lzma2.c @@ -0,0 +1,1231 @@ +/* + * LZMA2 decoder + * + * Authors: Lasse Collin <lasse.collin@tukaani.org> + * Igor Pavlov <http://7-zip.org/> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#include "xz_private.h" +#include "xz_lzma2.h" + +/* + * Range decoder initialization eats the first five bytes of each LZMA chunk. + */ +#define RC_INIT_BYTES 5 + +/* + * Minimum number of usable input buffer to safely decode one LZMA symbol. + * The worst case is that we decode 22 bits using probabilities and 26 + * direct bits. This may decode at maximum of 20 bytes of input. However, + * lzma_main() does an extra normalization before returning, thus we + * need to put 21 here. + */ +#define LZMA_IN_REQUIRED 21 + +/* + * Dictionary (history buffer) + * + * These are always true: + * start <= pos <= full <= end + * pos <= limit <= end + * + * In multi-call mode, also these are true: + * end == size + * size <= size_max + * allocated <= size + * + * Most of these variables are size_t to support single-call mode, + * in which the dictionary variables address the actual output + * buffer directly. + */ +struct dictionary +{ + /* Beginning of the history buffer */ + uint8_t *buf; + + /* Old position in buf (before decoding more data) */ + size_t start; + + /* Position in buf */ + size_t pos; + + /* + * How full dictionary is. This is used to detect corrupt input that + * would read beyond the beginning of the uncompressed stream. + */ + size_t full; + + /* Write limit; we don't write to buf[limit] or later bytes. */ + size_t limit; + + /* + * End of the dictionary buffer. In multi-call mode, this is + * the same as the dictionary size. In single-call mode, this + * indicates the size of the output buffer. + */ + size_t end; + + /* + * Size of the dictionary as specified in Block Header. This is used + * together with "full" to detect corrupt input that would make us + * read beyond the beginning of the uncompressed stream. + */ + uint32_t size; + + /* + * Maximum allowed dictionary size in multi-call mode. + * This is ignored in single-call mode. + */ + uint32_t size_max; + + /* + * Amount of memory currently allocated for the dictionary. + * This is used only with XZ_DYNALLOC. (With XZ_PREALLOC, + * size_max is always the same as the allocated size.) + */ + uint32_t allocated; + + /* Operation mode */ + enum xz_mode mode; +}; + +/* Range decoder */ +struct rc_dec +{ + uint32_t range; + uint32_t code; + + /* + * Number of initializing bytes remaining to be read + * by rc_read_init(). + */ + uint32_t init_bytes_left; + + /* + * Buffer from which we read our input. It can be either + * temp.buf or the caller-provided input buffer. + */ + const uint8_t *in; + size_t in_pos; + size_t in_limit; +}; + +/* Probabilities for a length decoder. */ +struct lzma_len_dec +{ + /* Probability of match length being at least 10 */ + uint16_t choice; + + /* Probability of match length being at least 18 */ + uint16_t choice2; + + /* Probabilities for match lengths 2-9 */ + uint16_t low[POS_STATES_MAX][LEN_LOW_SYMBOLS]; + + /* Probabilities for match lengths 10-17 */ + uint16_t mid[POS_STATES_MAX][LEN_MID_SYMBOLS]; + + /* Probabilities for match lengths 18-273 */ + uint16_t high[LEN_HIGH_SYMBOLS]; +}; + +struct lzma_dec +{ + /* Distances of latest four matches */ + uint32_t rep0; + uint32_t rep1; + uint32_t rep2; + uint32_t rep3; + + /* Types of the most recently seen LZMA symbols */ + enum lzma_state state; + + /* + * Length of a match. This is updated so that dict_repeat can + * be called again to finish repeating the whole match. + */ + uint32_t len; + + /* + * LZMA properties or related bit masks (number of literal + * context bits, a mask dervied from the number of literal + * position bits, and a mask dervied from the number + * position bits) + */ + uint32_t lc; + uint32_t literal_pos_mask; /* (1 << lp) - 1 */ + uint32_t pos_mask; /* (1 << pb) - 1 */ + + /* If 1, it's a match. Otherwise it's a single 8-bit literal. */ + uint16_t is_match[STATES][POS_STATES_MAX]; + + /* If 1, it's a repeated match. The distance is one of rep0 .. rep3. */ + uint16_t is_rep[STATES]; + + /* + * If 0, distance of a repeated match is rep0. + * Otherwise check is_rep1. + */ + uint16_t is_rep0[STATES]; + + /* + * If 0, distance of a repeated match is rep1. + * Otherwise check is_rep2. + */ + uint16_t is_rep1[STATES]; + + /* If 0, distance of a repeated match is rep2. Otherwise it is rep3. */ + uint16_t is_rep2[STATES]; + + /* + * If 1, the repeated match has length of one byte. Otherwise + * the length is decoded from rep_len_decoder. + */ + uint16_t is_rep0_long[STATES][POS_STATES_MAX]; + + /* + * Probability tree for the highest two bits of the match + * distance. There is a separate probability tree for match + * lengths of 2 (i.e. MATCH_LEN_MIN), 3, 4, and [5, 273]. + */ + uint16_t dist_slot[DIST_STATES][DIST_SLOTS]; + + /* + * Probility trees for additional bits for match distance + * when the distance is in the range [4, 127]. + */ + uint16_t dist_special[FULL_DISTANCES - DIST_MODEL_END]; + + /* + * Probability tree for the lowest four bits of a match + * distance that is equal to or greater than 128. + */ + uint16_t dist_align[ALIGN_SIZE]; + + /* Length of a normal match */ + struct lzma_len_dec match_len_dec; + + /* Length of a repeated match */ + struct lzma_len_dec rep_len_dec; + + /* Probabilities of literals */ + uint16_t literal[LITERAL_CODERS_MAX][LITERAL_CODER_SIZE]; +}; + +struct lzma2_dec +{ + /* Position in xz_dec_lzma2_run(). */ + enum lzma2_seq + { + SEQ_CONTROL, + SEQ_UNCOMPRESSED_1, + SEQ_UNCOMPRESSED_2, + SEQ_COMPRESSED_0, + SEQ_COMPRESSED_1, + SEQ_PROPERTIES, + SEQ_LZMA_PREPARE, + SEQ_LZMA_RUN, + SEQ_COPY + } sequence; + + /* Next position after decoding the compressed size of the chunk. */ + enum lzma2_seq next_sequence; + + /* Uncompressed size of LZMA chunk (2 MiB at maximum) */ + uint32_t uncompressed; + + /* + * Compressed size of LZMA chunk or compressed/uncompressed + * size of uncompressed chunk (64 KiB at maximum) + */ + uint32_t compressed; + + /* + * True if dictionary reset is needed. This is false before + * the first chunk (LZMA or uncompressed). + */ + bool need_dict_reset; + + /* + * True if new LZMA properties are needed. This is false + * before the first LZMA chunk. + */ + bool need_props; +}; + +struct xz_dec_lzma2 +{ + /* + * The order below is important on x86 to reduce code size and + * it shouldn't hurt on other platforms. Everything up to and + * including lzma.pos_mask are in the first 128 bytes on x86-32, + * which allows using smaller instructions to access those + * variables. On x86-64, fewer variables fit into the first 128 + * bytes, but this is still the best order without sacrificing + * the readability by splitting the structures. + */ + struct rc_dec rc; + struct dictionary dict; + struct lzma2_dec lzma2; + struct lzma_dec lzma; + + /* + * Temporary buffer which holds small number of input bytes between + * decoder calls. See lzma2_lzma() for details. + */ + struct + { + uint32_t size; + uint8_t buf[3 * LZMA_IN_REQUIRED]; + } temp; +}; + +/************** + * Dictionary * + **************/ + +/* + * Reset the dictionary state. When in single-call mode, set up the beginning + * of the dictionary to point to the actual output buffer. + */ +static void dict_reset(struct dictionary *dict, struct xz_buf *b) +{ + if (DEC_IS_SINGLE(dict->mode)) + { + dict->buf = b->out + b->out_pos; + dict->end = b->out_size - b->out_pos; + } + + dict->start = 0; + dict->pos = 0; + dict->limit = 0; + dict->full = 0; +} + +/* Set dictionary write limit */ +static void dict_limit(struct dictionary *dict, size_t out_max) +{ + if (dict->end - dict->pos <= out_max) + dict->limit = dict->end; + else + dict->limit = dict->pos + out_max; +} + +/* Return true if at least one byte can be written into the dictionary. */ +static inline bool dict_has_space(const struct dictionary *dict) +{ + return dict->pos < dict->limit; +} + +/* + * Get a byte from the dictionary at the given distance. The distance is + * assumed to valid, or as a special case, zero when the dictionary is + * still empty. This special case is needed for single-call decoding to + * avoid writing a '\0' to the end of the destination buffer. + */ +static inline uint32_t dict_get(const struct dictionary *dict, uint32_t dist) +{ + size_t offset = dict->pos - dist - 1; + + if (dist >= dict->pos) + offset += dict->end; + + return dict->full > 0 ? dict->buf[offset] : 0; +} + +/* + * Put one byte into the dictionary. It is assumed that there is space for it. + */ +static inline void dict_put(struct dictionary *dict, uint8_t byte) +{ + dict->buf[dict->pos++] = byte; + + if (dict->full < dict->pos) + dict->full = dict->pos; +} + +/* + * Repeat given number of bytes from the given distance. If the distance is + * invalid, false is returned. On success, true is returned and *len is + * updated to indicate how many bytes were left to be repeated. + */ +static bool dict_repeat(struct dictionary *dict, uint32_t *len, uint32_t dist) +{ + size_t back; + uint32_t left; + + if (dist >= dict->full || dist >= dict->size) + return false; + + left = min_t(size_t, dict->limit - dict->pos, *len); + *len -= left; + + back = dict->pos - dist - 1; + if (dist >= dict->pos) + back += dict->end; + + do + { + dict->buf[dict->pos++] = dict->buf[back++]; + if (back == dict->end) + back = 0; + } while (--left > 0); + + if (dict->full < dict->pos) + dict->full = dict->pos; + + return true; +} + +/* Copy uncompressed data as is from input to dictionary and output buffers. */ +static void dict_uncompressed(struct dictionary *dict, struct xz_buf *b, uint32_t *left) +{ + size_t copy_size; + + while (*left > 0 && b->in_pos < b->in_size && b->out_pos < b->out_size) + { + copy_size = min(b->in_size - b->in_pos, b->out_size - b->out_pos); + if (copy_size > dict->end - dict->pos) + copy_size = dict->end - dict->pos; + if (copy_size > *left) + copy_size = *left; + + *left -= copy_size; + + memcpy(dict->buf + dict->pos, b->in + b->in_pos, copy_size); + dict->pos += copy_size; + + if (dict->full < dict->pos) + dict->full = dict->pos; + + if (DEC_IS_MULTI(dict->mode)) + { + if (dict->pos == dict->end) + dict->pos = 0; + + memcpy(b->out + b->out_pos, b->in + b->in_pos, copy_size); + } + + dict->start = dict->pos; + + b->out_pos += copy_size; + b->in_pos += copy_size; + } +} + +/* + * Flush pending data from dictionary to b->out. It is assumed that there is + * enough space in b->out. This is guaranteed because caller uses dict_limit() + * before decoding data into the dictionary. + */ +static uint32_t dict_flush(struct dictionary *dict, struct xz_buf *b) +{ + size_t copy_size = dict->pos - dict->start; + + if (DEC_IS_MULTI(dict->mode)) + { + if (dict->pos == dict->end) + dict->pos = 0; + + memcpy(b->out + b->out_pos, dict->buf + dict->start, copy_size); + } + + dict->start = dict->pos; + b->out_pos += copy_size; + return copy_size; +} + +/***************** + * Range decoder * + *****************/ + +/* Reset the range decoder. */ +static void rc_reset(struct rc_dec *rc) +{ + rc->range = (uint32_t) - 1; + rc->code = 0; + rc->init_bytes_left = RC_INIT_BYTES; +} + +/* + * Read the first five initial bytes into rc->code if they haven't been + * read already. (Yes, the first byte gets completely ignored.) + */ +static bool rc_read_init(struct rc_dec *rc, struct xz_buf *b) +{ + while (rc->init_bytes_left > 0) + { + if (b->in_pos == b->in_size) + return false; + + rc->code = (rc->code << 8) + b->in[b->in_pos++]; + --rc->init_bytes_left; + } + + return true; +} + +/* Return true if there may not be enough input for the next decoding loop. */ +static inline bool rc_limit_exceeded(const struct rc_dec *rc) +{ + return rc->in_pos > rc->in_limit; +} + +/* + * Return true if it is possible (from point of view of range decoder) that + * we have reached the end of the LZMA chunk. + */ +static inline bool rc_is_finished(const struct rc_dec *rc) +{ + return rc->code == 0; +} + +/* Read the next input byte if needed. */ +static __always_inline void rc_normalize(struct rc_dec *rc) +{ + if (rc->range < RC_TOP_VALUE) + { + rc->range <<= RC_SHIFT_BITS; + rc->code = (rc->code << RC_SHIFT_BITS) + rc->in[rc->in_pos++]; + } +} + +/* + * Decode one bit. In some versions, this function has been splitted in three + * functions so that the compiler is supposed to be able to more easily avoid + * an extra branch. In this particular version of the LZMA decoder, this + * doesn't seem to be a good idea (tested with GCC 3.3.6, 3.4.6, and 4.3.3 + * on x86). Using a non-splitted version results in nicer looking code too. + * + * NOTE: This must return an int. Do not make it return a bool or the speed + * of the code generated by GCC 3.x decreases 10-15 %. (GCC 4.3 doesn't care, + * and it generates 10-20 % faster code than GCC 3.x from this file anyway.) + */ +static __always_inline int rc_bit(struct rc_dec *rc, uint16_t *prob) +{ + uint32_t bound; + int bit; + + rc_normalize(rc); + bound = (rc->range >> RC_BIT_MODEL_TOTAL_BITS) * *prob; + if (rc->code < bound) + { + rc->range = bound; + *prob += (RC_BIT_MODEL_TOTAL - *prob) >> RC_MOVE_BITS; + bit = 0; + } + else + { + rc->range -= bound; + rc->code -= bound; + *prob -= *prob >> RC_MOVE_BITS; + bit = 1; + } + + return bit; +} + +/* Decode a bittree starting from the most significant bit. */ +static __always_inline uint32_t rc_bittree(struct rc_dec *rc, uint16_t *probs, uint32_t limit) +{ + uint32_t symbol = 1; + + do + { + if (rc_bit(rc, &probs[symbol])) + symbol = (symbol << 1) + 1; + else + symbol <<= 1; + } while (symbol < limit); + + return symbol; +} + +/* Decode a bittree starting from the least significant bit. */ +static __always_inline void rc_bittree_reverse(struct rc_dec *rc, uint16_t *probs, + uint32_t *dest, uint32_t limit) +{ + uint32_t symbol = 1; + uint32_t i = 0; + + do + { + if (rc_bit(rc, &probs[symbol])) + { + symbol = (symbol << 1) + 1; + *dest += 1 << i; + } + else + { + symbol <<= 1; + } + } while (++i < limit); +} + +/* Decode direct bits (fixed fifty-fifty probability) */ +static inline void rc_direct(struct rc_dec *rc, uint32_t *dest, uint32_t limit) +{ + uint32_t mask; + + do + { + rc_normalize(rc); + rc->range >>= 1; + rc->code -= rc->range; + mask = (uint32_t)0 - (rc->code >> 31); + rc->code += rc->range & mask; + *dest = (*dest << 1) + (mask + 1); + } while (--limit > 0); +} + +/******** + * LZMA * + ********/ + +/* Get pointer to literal coder probability array. */ +static uint16_t *lzma_literal_probs(struct xz_dec_lzma2 *s) +{ + uint32_t prev_byte = dict_get(&s->dict, 0); + uint32_t low = prev_byte >> (8 - s->lzma.lc); + uint32_t high = (s->dict.pos & s->lzma.literal_pos_mask) << s->lzma.lc; + return s->lzma.literal[low + high]; +} + +/* Decode a literal (one 8-bit byte) */ +static void lzma_literal(struct xz_dec_lzma2 *s) +{ + uint16_t *probs; + uint32_t symbol; + uint32_t match_byte; + uint32_t match_bit; + uint32_t offset; + uint32_t i; + + probs = lzma_literal_probs(s); + + if (lzma_state_is_literal(s->lzma.state)) + { + symbol = rc_bittree(&s->rc, probs, 0x100); + } + else + { + symbol = 1; + match_byte = dict_get(&s->dict, s->lzma.rep0) << 1; + offset = 0x100; + + do + { + match_bit = match_byte & offset; + match_byte <<= 1; + i = offset + match_bit + symbol; + + if (rc_bit(&s->rc, &probs[i])) + { + symbol = (symbol << 1) + 1; + offset &= match_bit; + } + else + { + symbol <<= 1; + offset &= ~match_bit; + } + } while (symbol < 0x100); + } + + dict_put(&s->dict, (uint8_t)symbol); + lzma_state_literal(&s->lzma.state); +} + +/* Decode the length of the match into s->lzma.len. */ +static void lzma_len(struct xz_dec_lzma2 *s, struct lzma_len_dec *l, uint32_t pos_state) +{ + uint16_t *probs; + uint32_t limit; + + if (!rc_bit(&s->rc, &l->choice)) + { + probs = l->low[pos_state]; + limit = LEN_LOW_SYMBOLS; + s->lzma.len = MATCH_LEN_MIN; + } + else + { + if (!rc_bit(&s->rc, &l->choice2)) + { + probs = l->mid[pos_state]; + limit = LEN_MID_SYMBOLS; + s->lzma.len = MATCH_LEN_MIN + LEN_LOW_SYMBOLS; + } + else + { + probs = l->high; + limit = LEN_HIGH_SYMBOLS; + s->lzma.len = MATCH_LEN_MIN + LEN_LOW_SYMBOLS + LEN_MID_SYMBOLS; + } + } + + s->lzma.len += rc_bittree(&s->rc, probs, limit) - limit; +} + +/* Decode a match. The distance will be stored in s->lzma.rep0. */ +static void lzma_match(struct xz_dec_lzma2 *s, uint32_t pos_state) +{ + uint16_t *probs; + uint32_t dist_slot; + uint32_t limit; + + lzma_state_match(&s->lzma.state); + + s->lzma.rep3 = s->lzma.rep2; + s->lzma.rep2 = s->lzma.rep1; + s->lzma.rep1 = s->lzma.rep0; + + lzma_len(s, &s->lzma.match_len_dec, pos_state); + + probs = s->lzma.dist_slot[lzma_get_dist_state(s->lzma.len)]; + dist_slot = rc_bittree(&s->rc, probs, DIST_SLOTS) - DIST_SLOTS; + + if (dist_slot < DIST_MODEL_START) + { + s->lzma.rep0 = dist_slot; + } + else + { + limit = (dist_slot >> 1) - 1; + s->lzma.rep0 = 2 + (dist_slot & 1); + + if (dist_slot < DIST_MODEL_END) + { + s->lzma.rep0 <<= limit; + probs = s->lzma.dist_special + s->lzma.rep0 - dist_slot - 1; + rc_bittree_reverse(&s->rc, probs, &s->lzma.rep0, limit); + } + else + { + rc_direct(&s->rc, &s->lzma.rep0, limit - ALIGN_BITS); + s->lzma.rep0 <<= ALIGN_BITS; + rc_bittree_reverse(&s->rc, s->lzma.dist_align, &s->lzma.rep0, ALIGN_BITS); + } + } +} + +/* + * Decode a repeated match. The distance is one of the four most recently + * seen matches. The distance will be stored in s->lzma.rep0. + */ +static void lzma_rep_match(struct xz_dec_lzma2 *s, uint32_t pos_state) +{ + uint32_t tmp; + + if (!rc_bit(&s->rc, &s->lzma.is_rep0[s->lzma.state])) + { + if (!rc_bit(&s->rc, &s->lzma.is_rep0_long[s->lzma.state][pos_state])) + { + lzma_state_short_rep(&s->lzma.state); + s->lzma.len = 1; + return; + } + } + else + { + if (!rc_bit(&s->rc, &s->lzma.is_rep1[s->lzma.state])) + { + tmp = s->lzma.rep1; + } + else + { + if (!rc_bit(&s->rc, &s->lzma.is_rep2[s->lzma.state])) + { + tmp = s->lzma.rep2; + } + else + { + tmp = s->lzma.rep3; + s->lzma.rep3 = s->lzma.rep2; + } + + s->lzma.rep2 = s->lzma.rep1; + } + + s->lzma.rep1 = s->lzma.rep0; + s->lzma.rep0 = tmp; + } + + lzma_state_long_rep(&s->lzma.state); + lzma_len(s, &s->lzma.rep_len_dec, pos_state); +} + +/* LZMA decoder core */ +static bool lzma_main(struct xz_dec_lzma2 *s) +{ + uint32_t pos_state; + + /* + * If the dictionary was reached during the previous call, try to + * finish the possibly pending repeat in the dictionary. + */ + if (dict_has_space(&s->dict) && s->lzma.len > 0) + dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0); + + /* + * Decode more LZMA symbols. One iteration may consume up to + * LZMA_IN_REQUIRED - 1 bytes. + */ + while (dict_has_space(&s->dict) && !rc_limit_exceeded(&s->rc)) + { + pos_state = s->dict.pos & s->lzma.pos_mask; + + if (!rc_bit(&s->rc, &s->lzma.is_match[s->lzma.state][pos_state])) + { + lzma_literal(s); + } + else + { + if (rc_bit(&s->rc, &s->lzma.is_rep[s->lzma.state])) + lzma_rep_match(s, pos_state); + else + lzma_match(s, pos_state); + + if (!dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0)) + return false; + } + } + + /* + * Having the range decoder always normalized when we are outside + * this function makes it easier to correctly handle end of the chunk. + */ + rc_normalize(&s->rc); + + return true; +} + +/* + * Reset the LZMA decoder and range decoder state. Dictionary is nore reset + * here, because LZMA state may be reset without resetting the dictionary. + */ +static void lzma_reset(struct xz_dec_lzma2 *s) +{ + uint16_t *probs; + size_t i; + + s->lzma.state = STATE_LIT_LIT; + s->lzma.rep0 = 0; + s->lzma.rep1 = 0; + s->lzma.rep2 = 0; + s->lzma.rep3 = 0; + + /* + * All probabilities are initialized to the same value. This hack + * makes the code smaller by avoiding a separate loop for each + * probability array. + * + * This could be optimized so that only that part of literal + * probabilities that are actually required. In the common case + * we would write 12 KiB less. + */ + probs = s->lzma.is_match[0]; + for (i = 0; i < PROBS_TOTAL; ++i) + probs[i] = RC_BIT_MODEL_TOTAL / 2; + + rc_reset(&s->rc); +} + +/* + * Decode and validate LZMA properties (lc/lp/pb) and calculate the bit masks + * from the decoded lp and pb values. On success, the LZMA decoder state is + * reset and true is returned. + */ +static bool lzma_props(struct xz_dec_lzma2 *s, uint8_t props) +{ + if (props > (4 * 5 + 4) * 9 + 8) + return false; + + s->lzma.pos_mask = 0; + while (props >= 9 * 5) + { + props -= 9 * 5; + ++s->lzma.pos_mask; + } + + s->lzma.pos_mask = (1 << s->lzma.pos_mask) - 1; + + s->lzma.literal_pos_mask = 0; + while (props >= 9) + { + props -= 9; + ++s->lzma.literal_pos_mask; + } + + s->lzma.lc = props; + + if (s->lzma.lc + s->lzma.literal_pos_mask > 4) + return false; + + s->lzma.literal_pos_mask = (1 << s->lzma.literal_pos_mask) - 1; + + lzma_reset(s); + + return true; +} + +/********* + * LZMA2 * + *********/ + +/* + * The LZMA decoder assumes that if the input limit (s->rc.in_limit) hasn't + * been exceeded, it is safe to read up to LZMA_IN_REQUIRED bytes. This + * wrapper function takes care of making the LZMA decoder's assumption safe. + * + * As long as there is plenty of input left to be decoded in the current LZMA + * chunk, we decode directly from the caller-supplied input buffer until + * there's LZMA_IN_REQUIRED bytes left. Those remaining bytes are copied into + * s->temp.buf, which (hopefully) gets filled on the next call to this + * function. We decode a few bytes from the temporary buffer so that we can + * continue decoding from the caller-supplied input buffer again. + */ +static bool lzma2_lzma(struct xz_dec_lzma2 *s, struct xz_buf *b) +{ + size_t in_avail; + uint32_t tmp; + + in_avail = b->in_size - b->in_pos; + if (s->temp.size > 0 || s->lzma2.compressed == 0) + { + tmp = 2 * LZMA_IN_REQUIRED - s->temp.size; + if (tmp > s->lzma2.compressed - s->temp.size) + tmp = s->lzma2.compressed - s->temp.size; + if (tmp > in_avail) + tmp = in_avail; + + memcpy(s->temp.buf + s->temp.size, b->in + b->in_pos, tmp); + + if (s->temp.size + tmp == s->lzma2.compressed) + { + memzero(s->temp.buf + s->temp.size + tmp, sizeof(s->temp.buf) - s->temp.size - tmp); + s->rc.in_limit = s->temp.size + tmp; + } + else if (s->temp.size + tmp < LZMA_IN_REQUIRED) + { + s->temp.size += tmp; + b->in_pos += tmp; + return true; + } + else + { + s->rc.in_limit = s->temp.size + tmp - LZMA_IN_REQUIRED; + } + + s->rc.in = s->temp.buf; + s->rc.in_pos = 0; + + if (!lzma_main(s) || s->rc.in_pos > s->temp.size + tmp) + return false; + + s->lzma2.compressed -= s->rc.in_pos; + + if (s->rc.in_pos < s->temp.size) + { + s->temp.size -= s->rc.in_pos; + memmove(s->temp.buf, s->temp.buf + s->rc.in_pos, s->temp.size); + return true; + } + + b->in_pos += s->rc.in_pos - s->temp.size; + s->temp.size = 0; + } + + in_avail = b->in_size - b->in_pos; + if (in_avail >= LZMA_IN_REQUIRED) + { + s->rc.in = b->in; + s->rc.in_pos = b->in_pos; + + if (in_avail >= s->lzma2.compressed + LZMA_IN_REQUIRED) + s->rc.in_limit = b->in_pos + s->lzma2.compressed; + else + s->rc.in_limit = b->in_size - LZMA_IN_REQUIRED; + + if (!lzma_main(s)) + return false; + + in_avail = s->rc.in_pos - b->in_pos; + if (in_avail > s->lzma2.compressed) + return false; + + s->lzma2.compressed -= in_avail; + b->in_pos = s->rc.in_pos; + } + + in_avail = b->in_size - b->in_pos; + if (in_avail < LZMA_IN_REQUIRED) + { + if (in_avail > s->lzma2.compressed) + in_avail = s->lzma2.compressed; + + memcpy(s->temp.buf, b->in + b->in_pos, in_avail); + s->temp.size = in_avail; + b->in_pos += in_avail; + } + + return true; +} + +/* + * Take care of the LZMA2 control layer, and forward the job of actual LZMA + * decoding or copying of uncompressed chunks to other functions. + */ +XZ_EXTERN enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, struct xz_buf *b) +{ + uint32_t tmp; + + while (b->in_pos < b->in_size || s->lzma2.sequence == SEQ_LZMA_RUN) + { + switch (s->lzma2.sequence) + { + case SEQ_CONTROL: + /* + * LZMA2 control byte + * + * Exact values: + * 0x00 End marker + * 0x01 Dictionary reset followed by + * an uncompressed chunk + * 0x02 Uncompressed chunk (no dictionary reset) + * + * Highest three bits (s->control & 0xE0): + * 0xE0 Dictionary reset, new properties and state + * reset, followed by LZMA compressed chunk + * 0xC0 New properties and state reset, followed + * by LZMA compressed chunk (no dictionary + * reset) + * 0xA0 State reset using old properties, + * followed by LZMA compressed chunk (no + * dictionary reset) + * 0x80 LZMA chunk (no dictionary or state reset) + * + * For LZMA compressed chunks, the lowest five bits + * (s->control & 1F) are the highest bits of the + * uncompressed size (bits 16-20). + * + * A new LZMA2 stream must begin with a dictionary + * reset. The first LZMA chunk must set new + * properties and reset the LZMA state. + * + * Values that don't match anything described above + * are invalid and we return XZ_DATA_ERROR. + */ + tmp = b->in[b->in_pos++]; + + if (tmp == 0x00) + return XZ_STREAM_END; + + if (tmp >= 0xE0 || tmp == 0x01) + { + s->lzma2.need_props = true; + s->lzma2.need_dict_reset = false; + dict_reset(&s->dict, b); + } + else if (s->lzma2.need_dict_reset) + { + return XZ_DATA_ERROR; + } + + if (tmp >= 0x80) + { + s->lzma2.uncompressed = (tmp & 0x1F) << 16; + s->lzma2.sequence = SEQ_UNCOMPRESSED_1; + + if (tmp >= 0xC0) + { + /* + * When there are new properties, + * state reset is done at + * SEQ_PROPERTIES. + */ + s->lzma2.need_props = false; + s->lzma2.next_sequence = SEQ_PROPERTIES; + } + else if (s->lzma2.need_props) + { + return XZ_DATA_ERROR; + } + else + { + s->lzma2.next_sequence = SEQ_LZMA_PREPARE; + if (tmp >= 0xA0) + lzma_reset(s); + } + } + else + { + if (tmp > 0x02) + return XZ_DATA_ERROR; + + s->lzma2.sequence = SEQ_COMPRESSED_0; + s->lzma2.next_sequence = SEQ_COPY; + } + + break; + + case SEQ_UNCOMPRESSED_1: + s->lzma2.uncompressed += (uint32_t)b->in[b->in_pos++] << 8; + s->lzma2.sequence = SEQ_UNCOMPRESSED_2; + break; + + case SEQ_UNCOMPRESSED_2: + s->lzma2.uncompressed += (uint32_t)b->in[b->in_pos++] + 1; + s->lzma2.sequence = SEQ_COMPRESSED_0; + break; + + case SEQ_COMPRESSED_0: + s->lzma2.compressed = (uint32_t)b->in[b->in_pos++] << 8; + s->lzma2.sequence = SEQ_COMPRESSED_1; + break; + + case SEQ_COMPRESSED_1: + s->lzma2.compressed += (uint32_t)b->in[b->in_pos++] + 1; + s->lzma2.sequence = s->lzma2.next_sequence; + break; + + case SEQ_PROPERTIES: + if (!lzma_props(s, b->in[b->in_pos++])) + return XZ_DATA_ERROR; + + s->lzma2.sequence = SEQ_LZMA_PREPARE; + + case SEQ_LZMA_PREPARE: + if (s->lzma2.compressed < RC_INIT_BYTES) + return XZ_DATA_ERROR; + + if (!rc_read_init(&s->rc, b)) + return XZ_OK; + + s->lzma2.compressed -= RC_INIT_BYTES; + s->lzma2.sequence = SEQ_LZMA_RUN; + + case SEQ_LZMA_RUN: + /* + * Set dictionary limit to indicate how much we want + * to be encoded at maximum. Decode new data into the + * dictionary. Flush the new data from dictionary to + * b->out. Check if we finished decoding this chunk. + * In case the dictionary got full but we didn't fill + * the output buffer yet, we may run this loop + * multiple times without changing s->lzma2.sequence. + */ + dict_limit(&s->dict, + min_t(size_t, b->out_size - b->out_pos, s->lzma2.uncompressed)); + if (!lzma2_lzma(s, b)) + return XZ_DATA_ERROR; + + s->lzma2.uncompressed -= dict_flush(&s->dict, b); + + if (s->lzma2.uncompressed == 0) + { + if (s->lzma2.compressed > 0 || s->lzma.len > 0 || !rc_is_finished(&s->rc)) + return XZ_DATA_ERROR; + + rc_reset(&s->rc); + s->lzma2.sequence = SEQ_CONTROL; + } + else if (b->out_pos == b->out_size || + (b->in_pos == b->in_size && s->temp.size < s->lzma2.compressed)) + { + return XZ_OK; + } + + break; + + case SEQ_COPY: + dict_uncompressed(&s->dict, b, &s->lzma2.compressed); + if (s->lzma2.compressed > 0) + return XZ_OK; + + s->lzma2.sequence = SEQ_CONTROL; + break; + } + } + + return XZ_OK; +} + +XZ_EXTERN struct xz_dec_lzma2 *xz_dec_lzma2_create(enum xz_mode mode, uint32_t dict_max) +{ + struct xz_dec_lzma2 *s = kmalloc(sizeof(*s), GFP_KERNEL); + if (s == NULL) + return NULL; + + s->dict.mode = mode; + s->dict.size_max = dict_max; + + if (DEC_IS_PREALLOC(mode)) + { + s->dict.buf = vmalloc(dict_max); + if (s->dict.buf == NULL) + { + kfree(s); + return NULL; + } + } + else if (DEC_IS_DYNALLOC(mode)) + { + s->dict.buf = NULL; + s->dict.allocated = 0; + } + + return s; +} + +XZ_EXTERN enum xz_ret xz_dec_lzma2_reset(struct xz_dec_lzma2 *s, uint8_t props) +{ + /* This limits dictionary size to 3 GiB to keep parsing simpler. */ + if (props > 39) + return XZ_OPTIONS_ERROR; + + s->dict.size = 2 + (props & 1); + s->dict.size <<= (props >> 1) + 11; + + if (DEC_IS_MULTI(s->dict.mode)) + { + if (s->dict.size > s->dict.size_max) + return XZ_MEMLIMIT_ERROR; + + s->dict.end = s->dict.size; + + if (DEC_IS_DYNALLOC(s->dict.mode)) + { + if (s->dict.allocated < s->dict.size) + { + vfree(s->dict.buf); + s->dict.buf = vmalloc(s->dict.size); + if (s->dict.buf == NULL) + { + s->dict.allocated = 0; + return XZ_MEM_ERROR; + } + } + } + } + + s->lzma.len = 0; + + s->lzma2.sequence = SEQ_CONTROL; + s->lzma2.need_dict_reset = true; + + s->temp.size = 0; + + return XZ_OK; +} + +XZ_EXTERN void xz_dec_lzma2_end(struct xz_dec_lzma2 *s) +{ + if (DEC_IS_MULTI(s->dict.mode)) + vfree(s->dict.buf); + + kfree(s); +} diff --git a/depends/xz-embedded/src/xz_dec_stream.c b/depends/xz-embedded/src/xz_dec_stream.c new file mode 100644 index 00000000..6e935ded --- /dev/null +++ b/depends/xz-embedded/src/xz_dec_stream.c @@ -0,0 +1,860 @@ +/* + * .xz Stream decoder + * + * Author: Lasse Collin <lasse.collin@tukaani.org> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#include "xz_private.h" +#include "xz_stream.h" + +#ifdef XZ_USE_CRC64 +#define IS_CRC64(check_type) ((check_type) == XZ_CHECK_CRC64) +#else +#define IS_CRC64(check_type) false +#endif + +/* Hash used to validate the Index field */ +struct xz_dec_hash +{ + vli_type unpadded; + vli_type uncompressed; + uint32_t crc32; +}; + +struct xz_dec +{ + /* Position in dec_main() */ + enum + { + SEQ_STREAM_HEADER, + SEQ_BLOCK_START, + SEQ_BLOCK_HEADER, + SEQ_BLOCK_UNCOMPRESS, + SEQ_BLOCK_PADDING, + SEQ_BLOCK_CHECK, + SEQ_INDEX, + SEQ_INDEX_PADDING, + SEQ_INDEX_CRC32, + SEQ_STREAM_FOOTER + } sequence; + + /* Position in variable-length integers and Check fields */ + uint32_t pos; + + /* Variable-length integer decoded by dec_vli() */ + vli_type vli; + + /* Saved in_pos and out_pos */ + size_t in_start; + size_t out_start; + +#ifdef XZ_USE_CRC64 + /* CRC32 or CRC64 value in Block or CRC32 value in Index */ + uint64_t crc; +#else + /* CRC32 value in Block or Index */ + uint32_t crc; +#endif + + /* Type of the integrity check calculated from uncompressed data */ + enum xz_check check_type; + + /* Operation mode */ + enum xz_mode mode; + + /* + * True if the next call to xz_dec_run() is allowed to return + * XZ_BUF_ERROR. + */ + bool allow_buf_error; + + /* Information stored in Block Header */ + struct + { + /* + * Value stored in the Compressed Size field, or + * VLI_UNKNOWN if Compressed Size is not present. + */ + vli_type compressed; + + /* + * Value stored in the Uncompressed Size field, or + * VLI_UNKNOWN if Uncompressed Size is not present. + */ + vli_type uncompressed; + + /* Size of the Block Header field */ + uint32_t size; + } block_header; + + /* Information collected when decoding Blocks */ + struct + { + /* Observed compressed size of the current Block */ + vli_type compressed; + + /* Observed uncompressed size of the current Block */ + vli_type uncompressed; + + /* Number of Blocks decoded so far */ + vli_type count; + + /* + * Hash calculated from the Block sizes. This is used to + * validate the Index field. + */ + struct xz_dec_hash hash; + } block; + + /* Variables needed when verifying the Index field */ + struct + { + /* Position in dec_index() */ + enum + { + SEQ_INDEX_COUNT, + SEQ_INDEX_UNPADDED, + SEQ_INDEX_UNCOMPRESSED + } sequence; + + /* Size of the Index in bytes */ + vli_type size; + + /* Number of Records (matches block.count in valid files) */ + vli_type count; + + /* + * Hash calculated from the Records (matches block.hash in + * valid files). + */ + struct xz_dec_hash hash; + } index; + + /* + * Temporary buffer needed to hold Stream Header, Block Header, + * and Stream Footer. The Block Header is the biggest (1 KiB) + * so we reserve space according to that. buf[] has to be aligned + * to a multiple of four bytes; the size_t variables before it + * should guarantee this. + */ + struct + { + size_t pos; + size_t size; + uint8_t buf[1024]; + } temp; + + struct xz_dec_lzma2 *lzma2; + +#ifdef XZ_DEC_BCJ + struct xz_dec_bcj *bcj; + bool bcj_active; +#endif +}; + +#ifdef XZ_DEC_ANY_CHECK +/* Sizes of the Check field with different Check IDs */ +static const uint8_t check_sizes[16] = {0, 4, 4, 4, 8, 8, 8, 16, + 16, 16, 32, 32, 32, 64, 64, 64}; +#endif + +/* + * Fill s->temp by copying data starting from b->in[b->in_pos]. Caller + * must have set s->temp.pos to indicate how much data we are supposed + * to copy into s->temp.buf. Return true once s->temp.pos has reached + * s->temp.size. + */ +static bool fill_temp(struct xz_dec *s, struct xz_buf *b) +{ + size_t copy_size = min_t(size_t, b->in_size - b->in_pos, s->temp.size - s->temp.pos); + + memcpy(s->temp.buf + s->temp.pos, b->in + b->in_pos, copy_size); + b->in_pos += copy_size; + s->temp.pos += copy_size; + + if (s->temp.pos == s->temp.size) + { + s->temp.pos = 0; + return true; + } + + return false; +} + +/* Decode a variable-length integer (little-endian base-128 encoding) */ +static enum xz_ret dec_vli(struct xz_dec *s, const uint8_t *in, size_t *in_pos, size_t in_size) +{ + uint8_t byte; + + if (s->pos == 0) + s->vli = 0; + + while (*in_pos < in_size) + { + byte = in[*in_pos]; + ++*in_pos; + + s->vli |= (vli_type)(byte & 0x7F) << s->pos; + + if ((byte & 0x80) == 0) + { + /* Don't allow non-minimal encodings. */ + if (byte == 0 && s->pos != 0) + return XZ_DATA_ERROR; + + s->pos = 0; + return XZ_STREAM_END; + } + + s->pos += 7; + if (s->pos == 7 * VLI_BYTES_MAX) + return XZ_DATA_ERROR; + } + + return XZ_OK; +} + +/* + * Decode the Compressed Data field from a Block. Update and validate + * the observed compressed and uncompressed sizes of the Block so that + * they don't exceed the values possibly stored in the Block Header + * (validation assumes that no integer overflow occurs, since vli_type + * is normally uint64_t). Update the CRC32 or CRC64 value if presence of + * the CRC32 or CRC64 field was indicated in Stream Header. + * + * Once the decoding is finished, validate that the observed sizes match + * the sizes possibly stored in the Block Header. Update the hash and + * Block count, which are later used to validate the Index field. + */ +static enum xz_ret dec_block(struct xz_dec *s, struct xz_buf *b) +{ + enum xz_ret ret; + + s->in_start = b->in_pos; + s->out_start = b->out_pos; + +#ifdef XZ_DEC_BCJ + if (s->bcj_active) + ret = xz_dec_bcj_run(s->bcj, s->lzma2, b); + else +#endif + ret = xz_dec_lzma2_run(s->lzma2, b); + + s->block.compressed += b->in_pos - s->in_start; + s->block.uncompressed += b->out_pos - s->out_start; + + /* + * There is no need to separately check for VLI_UNKNOWN, since + * the observed sizes are always smaller than VLI_UNKNOWN. + */ + if (s->block.compressed > s->block_header.compressed || + s->block.uncompressed > s->block_header.uncompressed) + return XZ_DATA_ERROR; + + if (s->check_type == XZ_CHECK_CRC32) + s->crc = xz_crc32(b->out + s->out_start, b->out_pos - s->out_start, s->crc); +#ifdef XZ_USE_CRC64 + else if (s->check_type == XZ_CHECK_CRC64) + s->crc = xz_crc64(b->out + s->out_start, b->out_pos - s->out_start, s->crc); +#endif + + if (ret == XZ_STREAM_END) + { + if (s->block_header.compressed != VLI_UNKNOWN && + s->block_header.compressed != s->block.compressed) + return XZ_DATA_ERROR; + + if (s->block_header.uncompressed != VLI_UNKNOWN && + s->block_header.uncompressed != s->block.uncompressed) + return XZ_DATA_ERROR; + + s->block.hash.unpadded += s->block_header.size + s->block.compressed; + +#ifdef XZ_DEC_ANY_CHECK + s->block.hash.unpadded += check_sizes[s->check_type]; +#else + if (s->check_type == XZ_CHECK_CRC32) + s->block.hash.unpadded += 4; + else if (IS_CRC64(s->check_type)) + s->block.hash.unpadded += 8; +#endif + + s->block.hash.uncompressed += s->block.uncompressed; + s->block.hash.crc32 = xz_crc32((const uint8_t *)&s->block.hash, sizeof(s->block.hash), + s->block.hash.crc32); + + ++s->block.count; + } + + return ret; +} + +/* Update the Index size and the CRC32 value. */ +static void index_update(struct xz_dec *s, const struct xz_buf *b) +{ + size_t in_used = b->in_pos - s->in_start; + s->index.size += in_used; + s->crc = xz_crc32(b->in + s->in_start, in_used, s->crc); +} + +/* + * Decode the Number of Records, Unpadded Size, and Uncompressed Size + * fields from the Index field. That is, Index Padding and CRC32 are not + * decoded by this function. + * + * This can return XZ_OK (more input needed), XZ_STREAM_END (everything + * successfully decoded), or XZ_DATA_ERROR (input is corrupt). + */ +static enum xz_ret dec_index(struct xz_dec *s, struct xz_buf *b) +{ + enum xz_ret ret; + + do + { + ret = dec_vli(s, b->in, &b->in_pos, b->in_size); + if (ret != XZ_STREAM_END) + { + index_update(s, b); + return ret; + } + + switch (s->index.sequence) + { + case SEQ_INDEX_COUNT: + s->index.count = s->vli; + + /* + * Validate that the Number of Records field + * indicates the same number of Records as + * there were Blocks in the Stream. + */ + if (s->index.count != s->block.count) + return XZ_DATA_ERROR; + + s->index.sequence = SEQ_INDEX_UNPADDED; + break; + + case SEQ_INDEX_UNPADDED: + s->index.hash.unpadded += s->vli; + s->index.sequence = SEQ_INDEX_UNCOMPRESSED; + break; + + case SEQ_INDEX_UNCOMPRESSED: + s->index.hash.uncompressed += s->vli; + s->index.hash.crc32 = xz_crc32((const uint8_t *)&s->index.hash, + sizeof(s->index.hash), s->index.hash.crc32); + --s->index.count; + s->index.sequence = SEQ_INDEX_UNPADDED; + break; + } + } while (s->index.count > 0); + + return XZ_STREAM_END; +} + +/* + * Validate that the next four or eight input bytes match the value + * of s->crc. s->pos must be zero when starting to validate the first byte. + * The "bits" argument allows using the same code for both CRC32 and CRC64. + */ +static enum xz_ret crc_validate(struct xz_dec *s, struct xz_buf *b, uint32_t bits) +{ + do + { + if (b->in_pos == b->in_size) + return XZ_OK; + + if (((s->crc >> s->pos) & 0xFF) != b->in[b->in_pos++]) + return XZ_DATA_ERROR; + + s->pos += 8; + + } while (s->pos < bits); + + s->crc = 0; + s->pos = 0; + + return XZ_STREAM_END; +} + +#ifdef XZ_DEC_ANY_CHECK +/* + * Skip over the Check field when the Check ID is not supported. + * Returns true once the whole Check field has been skipped over. + */ +static bool check_skip(struct xz_dec *s, struct xz_buf *b) +{ + while (s->pos < check_sizes[s->check_type]) + { + if (b->in_pos == b->in_size) + return false; + + ++b->in_pos; + ++s->pos; + } + + s->pos = 0; + + return true; +} +#endif + +/* Decode the Stream Header field (the first 12 bytes of the .xz Stream). */ +static enum xz_ret dec_stream_header(struct xz_dec *s) +{ + if (!memeq(s->temp.buf, HEADER_MAGIC, HEADER_MAGIC_SIZE)) + return XZ_FORMAT_ERROR; + + if (xz_crc32(s->temp.buf + HEADER_MAGIC_SIZE, 2, 0) != + get_le32(s->temp.buf + HEADER_MAGIC_SIZE + 2)) + return XZ_DATA_ERROR; + + if (s->temp.buf[HEADER_MAGIC_SIZE] != 0) + return XZ_OPTIONS_ERROR; + + /* + * Of integrity checks, we support none (Check ID = 0), + * CRC32 (Check ID = 1), and optionally CRC64 (Check ID = 4). + * However, if XZ_DEC_ANY_CHECK is defined, we will accept other + * check types too, but then the check won't be verified and + * a warning (XZ_UNSUPPORTED_CHECK) will be given. + */ + s->check_type = s->temp.buf[HEADER_MAGIC_SIZE + 1]; + +#ifdef XZ_DEC_ANY_CHECK + if (s->check_type > XZ_CHECK_MAX) + return XZ_OPTIONS_ERROR; + + if (s->check_type > XZ_CHECK_CRC32 && !IS_CRC64(s->check_type)) + return XZ_UNSUPPORTED_CHECK; +#else + if (s->check_type > XZ_CHECK_CRC32 && !IS_CRC64(s->check_type)) + return XZ_OPTIONS_ERROR; +#endif + + return XZ_OK; +} + +/* Decode the Stream Footer field (the last 12 bytes of the .xz Stream) */ +static enum xz_ret dec_stream_footer(struct xz_dec *s) +{ + if (!memeq(s->temp.buf + 10, FOOTER_MAGIC, FOOTER_MAGIC_SIZE)) + return XZ_DATA_ERROR; + + if (xz_crc32(s->temp.buf + 4, 6, 0) != get_le32(s->temp.buf)) + return XZ_DATA_ERROR; + + /* + * Validate Backward Size. Note that we never added the size of the + * Index CRC32 field to s->index.size, thus we use s->index.size / 4 + * instead of s->index.size / 4 - 1. + */ + if ((s->index.size >> 2) != get_le32(s->temp.buf + 4)) + return XZ_DATA_ERROR; + + if (s->temp.buf[8] != 0 || s->temp.buf[9] != s->check_type) + return XZ_DATA_ERROR; + + /* + * Use XZ_STREAM_END instead of XZ_OK to be more convenient + * for the caller. + */ + return XZ_STREAM_END; +} + +/* Decode the Block Header and initialize the filter chain. */ +static enum xz_ret dec_block_header(struct xz_dec *s) +{ + enum xz_ret ret; + + /* + * Validate the CRC32. We know that the temp buffer is at least + * eight bytes so this is safe. + */ + s->temp.size -= 4; + if (xz_crc32(s->temp.buf, s->temp.size, 0) != get_le32(s->temp.buf + s->temp.size)) + return XZ_DATA_ERROR; + + s->temp.pos = 2; + +/* + * Catch unsupported Block Flags. We support only one or two filters + * in the chain, so we catch that with the same test. + */ +#ifdef XZ_DEC_BCJ + if (s->temp.buf[1] & 0x3E) +#else + if (s->temp.buf[1] & 0x3F) +#endif + return XZ_OPTIONS_ERROR; + + /* Compressed Size */ + if (s->temp.buf[1] & 0x40) + { + if (dec_vli(s, s->temp.buf, &s->temp.pos, s->temp.size) != XZ_STREAM_END) + return XZ_DATA_ERROR; + + s->block_header.compressed = s->vli; + } + else + { + s->block_header.compressed = VLI_UNKNOWN; + } + + /* Uncompressed Size */ + if (s->temp.buf[1] & 0x80) + { + if (dec_vli(s, s->temp.buf, &s->temp.pos, s->temp.size) != XZ_STREAM_END) + return XZ_DATA_ERROR; + + s->block_header.uncompressed = s->vli; + } + else + { + s->block_header.uncompressed = VLI_UNKNOWN; + } + +#ifdef XZ_DEC_BCJ + /* If there are two filters, the first one must be a BCJ filter. */ + s->bcj_active = s->temp.buf[1] & 0x01; + if (s->bcj_active) + { + if (s->temp.size - s->temp.pos < 2) + return XZ_OPTIONS_ERROR; + + ret = xz_dec_bcj_reset(s->bcj, s->temp.buf[s->temp.pos++]); + if (ret != XZ_OK) + return ret; + + /* + * We don't support custom start offset, + * so Size of Properties must be zero. + */ + if (s->temp.buf[s->temp.pos++] != 0x00) + return XZ_OPTIONS_ERROR; + } +#endif + + /* Valid Filter Flags always take at least two bytes. */ + if (s->temp.size - s->temp.pos < 2) + return XZ_DATA_ERROR; + + /* Filter ID = LZMA2 */ + if (s->temp.buf[s->temp.pos++] != 0x21) + return XZ_OPTIONS_ERROR; + + /* Size of Properties = 1-byte Filter Properties */ + if (s->temp.buf[s->temp.pos++] != 0x01) + return XZ_OPTIONS_ERROR; + + /* Filter Properties contains LZMA2 dictionary size. */ + if (s->temp.size - s->temp.pos < 1) + return XZ_DATA_ERROR; + + ret = xz_dec_lzma2_reset(s->lzma2, s->temp.buf[s->temp.pos++]); + if (ret != XZ_OK) + return ret; + + /* The rest must be Header Padding. */ + while (s->temp.pos < s->temp.size) + if (s->temp.buf[s->temp.pos++] != 0x00) + return XZ_OPTIONS_ERROR; + + s->temp.pos = 0; + s->block.compressed = 0; + s->block.uncompressed = 0; + + return XZ_OK; +} + +static enum xz_ret dec_main(struct xz_dec *s, struct xz_buf *b) +{ + enum xz_ret ret; + + /* + * Store the start position for the case when we are in the middle + * of the Index field. + */ + s->in_start = b->in_pos; + + while (true) + { + switch (s->sequence) + { + case SEQ_STREAM_HEADER: + /* + * Stream Header is copied to s->temp, and then + * decoded from there. This way if the caller + * gives us only little input at a time, we can + * still keep the Stream Header decoding code + * simple. Similar approach is used in many places + * in this file. + */ + if (!fill_temp(s, b)) + return XZ_OK; + + /* + * If dec_stream_header() returns + * XZ_UNSUPPORTED_CHECK, it is still possible + * to continue decoding if working in multi-call + * mode. Thus, update s->sequence before calling + * dec_stream_header(). + */ + s->sequence = SEQ_BLOCK_START; + + ret = dec_stream_header(s); + if (ret != XZ_OK) + return ret; + + case SEQ_BLOCK_START: + /* We need one byte of input to continue. */ + if (b->in_pos == b->in_size) + return XZ_OK; + + /* See if this is the beginning of the Index field. */ + if (b->in[b->in_pos] == 0) + { + s->in_start = b->in_pos++; + s->sequence = SEQ_INDEX; + break; + } + + /* + * Calculate the size of the Block Header and + * prepare to decode it. + */ + s->block_header.size = ((uint32_t)b->in[b->in_pos] + 1) * 4; + + s->temp.size = s->block_header.size; + s->temp.pos = 0; + s->sequence = SEQ_BLOCK_HEADER; + + case SEQ_BLOCK_HEADER: + if (!fill_temp(s, b)) + return XZ_OK; + + ret = dec_block_header(s); + if (ret != XZ_OK) + return ret; + + s->sequence = SEQ_BLOCK_UNCOMPRESS; + + case SEQ_BLOCK_UNCOMPRESS: + ret = dec_block(s, b); + if (ret != XZ_STREAM_END) + return ret; + + s->sequence = SEQ_BLOCK_PADDING; + + case SEQ_BLOCK_PADDING: + /* + * Size of Compressed Data + Block Padding + * must be a multiple of four. We don't need + * s->block.compressed for anything else + * anymore, so we use it here to test the size + * of the Block Padding field. + */ + while (s->block.compressed & 3) + { + if (b->in_pos == b->in_size) + return XZ_OK; + + if (b->in[b->in_pos++] != 0) + return XZ_DATA_ERROR; + + ++s->block.compressed; + } + + s->sequence = SEQ_BLOCK_CHECK; + + case SEQ_BLOCK_CHECK: + if (s->check_type == XZ_CHECK_CRC32) + { + ret = crc_validate(s, b, 32); + if (ret != XZ_STREAM_END) + return ret; + } + else if (IS_CRC64(s->check_type)) + { + ret = crc_validate(s, b, 64); + if (ret != XZ_STREAM_END) + return ret; + } +#ifdef XZ_DEC_ANY_CHECK + else if (!check_skip(s, b)) + { + return XZ_OK; + } +#endif + + s->sequence = SEQ_BLOCK_START; + break; + + case SEQ_INDEX: + ret = dec_index(s, b); + if (ret != XZ_STREAM_END) + return ret; + + s->sequence = SEQ_INDEX_PADDING; + + case SEQ_INDEX_PADDING: + while ((s->index.size + (b->in_pos - s->in_start)) & 3) + { + if (b->in_pos == b->in_size) + { + index_update(s, b); + return XZ_OK; + } + + if (b->in[b->in_pos++] != 0) + return XZ_DATA_ERROR; + } + + /* Finish the CRC32 value and Index size. */ + index_update(s, b); + + /* Compare the hashes to validate the Index field. */ + if (!memeq(&s->block.hash, &s->index.hash, sizeof(s->block.hash))) + return XZ_DATA_ERROR; + + s->sequence = SEQ_INDEX_CRC32; + + case SEQ_INDEX_CRC32: + ret = crc_validate(s, b, 32); + if (ret != XZ_STREAM_END) + return ret; + + s->temp.size = STREAM_HEADER_SIZE; + s->sequence = SEQ_STREAM_FOOTER; + + case SEQ_STREAM_FOOTER: + if (!fill_temp(s, b)) + return XZ_OK; + + return dec_stream_footer(s); + } + } + + /* Never reached */ +} + +/* + * xz_dec_run() is a wrapper for dec_main() to handle some special cases in + * multi-call and single-call decoding. + * + * In multi-call mode, we must return XZ_BUF_ERROR when it seems clear that we + * are not going to make any progress anymore. This is to prevent the caller + * from calling us infinitely when the input file is truncated or otherwise + * corrupt. Since zlib-style API allows that the caller fills the input buffer + * only when the decoder doesn't produce any new output, we have to be careful + * to avoid returning XZ_BUF_ERROR too easily: XZ_BUF_ERROR is returned only + * after the second consecutive call to xz_dec_run() that makes no progress. + * + * In single-call mode, if we couldn't decode everything and no error + * occurred, either the input is truncated or the output buffer is too small. + * Since we know that the last input byte never produces any output, we know + * that if all the input was consumed and decoding wasn't finished, the file + * must be corrupt. Otherwise the output buffer has to be too small or the + * file is corrupt in a way that decoding it produces too big output. + * + * If single-call decoding fails, we reset b->in_pos and b->out_pos back to + * their original values. This is because with some filter chains there won't + * be any valid uncompressed data in the output buffer unless the decoding + * actually succeeds (that's the price to pay of using the output buffer as + * the workspace). + */ +XZ_EXTERN enum xz_ret xz_dec_run(struct xz_dec *s, struct xz_buf *b) +{ + size_t in_start; + size_t out_start; + enum xz_ret ret; + + if (DEC_IS_SINGLE(s->mode)) + xz_dec_reset(s); + + in_start = b->in_pos; + out_start = b->out_pos; + ret = dec_main(s, b); + + if (DEC_IS_SINGLE(s->mode)) + { + if (ret == XZ_OK) + ret = b->in_pos == b->in_size ? XZ_DATA_ERROR : XZ_BUF_ERROR; + + if (ret != XZ_STREAM_END) + { + b->in_pos = in_start; + b->out_pos = out_start; + } + } + else if (ret == XZ_OK && in_start == b->in_pos && out_start == b->out_pos) + { + if (s->allow_buf_error) + ret = XZ_BUF_ERROR; + + s->allow_buf_error = true; + } + else + { + s->allow_buf_error = false; + } + + return ret; +} + +XZ_EXTERN struct xz_dec *xz_dec_init(enum xz_mode mode, uint32_t dict_max) +{ + struct xz_dec *s = kmalloc(sizeof(*s), GFP_KERNEL); + if (s == NULL) + return NULL; + + s->mode = mode; + +#ifdef XZ_DEC_BCJ + s->bcj = xz_dec_bcj_create(DEC_IS_SINGLE(mode)); + if (s->bcj == NULL) + goto error_bcj; +#endif + + s->lzma2 = xz_dec_lzma2_create(mode, dict_max); + if (s->lzma2 == NULL) + goto error_lzma2; + + xz_dec_reset(s); + return s; + +error_lzma2: +#ifdef XZ_DEC_BCJ + xz_dec_bcj_end(s->bcj); +error_bcj: +#endif + kfree(s); + return NULL; +} + +XZ_EXTERN void xz_dec_reset(struct xz_dec *s) +{ + s->sequence = SEQ_STREAM_HEADER; + s->allow_buf_error = false; + s->pos = 0; + s->crc = 0; + memzero(&s->block, sizeof(s->block)); + memzero(&s->index, sizeof(s->index)); + s->temp.pos = 0; + s->temp.size = STREAM_HEADER_SIZE; +} + +XZ_EXTERN void xz_dec_end(struct xz_dec *s) +{ + if (s != NULL) + { + xz_dec_lzma2_end(s->lzma2); +#ifdef XZ_DEC_BCJ + xz_dec_bcj_end(s->bcj); +#endif + kfree(s); + } +} diff --git a/depends/xz-embedded/src/xz_lzma2.h b/depends/xz-embedded/src/xz_lzma2.h new file mode 100644 index 00000000..3976033a --- /dev/null +++ b/depends/xz-embedded/src/xz_lzma2.h @@ -0,0 +1,204 @@ +/* + * LZMA2 definitions + * + * Authors: Lasse Collin <lasse.collin@tukaani.org> + * Igor Pavlov <http://7-zip.org/> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#ifndef XZ_LZMA2_H +#define XZ_LZMA2_H + +/* Range coder constants */ +#define RC_SHIFT_BITS 8 +#define RC_TOP_BITS 24 +#define RC_TOP_VALUE (1 << RC_TOP_BITS) +#define RC_BIT_MODEL_TOTAL_BITS 11 +#define RC_BIT_MODEL_TOTAL (1 << RC_BIT_MODEL_TOTAL_BITS) +#define RC_MOVE_BITS 5 + +/* + * Maximum number of position states. A position state is the lowest pb + * number of bits of the current uncompressed offset. In some places there + * are different sets of probabilities for different position states. + */ +#define POS_STATES_MAX (1 << 4) + +/* + * This enum is used to track which LZMA symbols have occurred most recently + * and in which order. This information is used to predict the next symbol. + * + * Symbols: + * - Literal: One 8-bit byte + * - Match: Repeat a chunk of data at some distance + * - Long repeat: Multi-byte match at a recently seen distance + * - Short repeat: One-byte repeat at a recently seen distance + * + * The symbol names are in from STATE_oldest_older_previous. REP means + * either short or long repeated match, and NONLIT means any non-literal. + */ +enum lzma_state +{ + STATE_LIT_LIT, + STATE_MATCH_LIT_LIT, + STATE_REP_LIT_LIT, + STATE_SHORTREP_LIT_LIT, + STATE_MATCH_LIT, + STATE_REP_LIT, + STATE_SHORTREP_LIT, + STATE_LIT_MATCH, + STATE_LIT_LONGREP, + STATE_LIT_SHORTREP, + STATE_NONLIT_MATCH, + STATE_NONLIT_REP +}; + +/* Total number of states */ +#define STATES 12 + +/* The lowest 7 states indicate that the previous state was a literal. */ +#define LIT_STATES 7 + +/* Indicate that the latest symbol was a literal. */ +static inline void lzma_state_literal(enum lzma_state *state) +{ + if (*state <= STATE_SHORTREP_LIT_LIT) + *state = STATE_LIT_LIT; + else if (*state <= STATE_LIT_SHORTREP) + *state -= 3; + else + *state -= 6; +} + +/* Indicate that the latest symbol was a match. */ +static inline void lzma_state_match(enum lzma_state *state) +{ + *state = *state < LIT_STATES ? STATE_LIT_MATCH : STATE_NONLIT_MATCH; +} + +/* Indicate that the latest state was a long repeated match. */ +static inline void lzma_state_long_rep(enum lzma_state *state) +{ + *state = *state < LIT_STATES ? STATE_LIT_LONGREP : STATE_NONLIT_REP; +} + +/* Indicate that the latest symbol was a short match. */ +static inline void lzma_state_short_rep(enum lzma_state *state) +{ + *state = *state < LIT_STATES ? STATE_LIT_SHORTREP : STATE_NONLIT_REP; +} + +/* Test if the previous symbol was a literal. */ +static inline bool lzma_state_is_literal(enum lzma_state state) +{ + return state < LIT_STATES; +} + +/* Each literal coder is divided in three sections: + * - 0x001-0x0FF: Without match byte + * - 0x101-0x1FF: With match byte; match bit is 0 + * - 0x201-0x2FF: With match byte; match bit is 1 + * + * Match byte is used when the previous LZMA symbol was something else than + * a literal (that is, it was some kind of match). + */ +#define LITERAL_CODER_SIZE 0x300 + +/* Maximum number of literal coders */ +#define LITERAL_CODERS_MAX (1 << 4) + +/* Minimum length of a match is two bytes. */ +#define MATCH_LEN_MIN 2 + +/* Match length is encoded with 4, 5, or 10 bits. + * + * Length Bits + * 2-9 4 = Choice=0 + 3 bits + * 10-17 5 = Choice=1 + Choice2=0 + 3 bits + * 18-273 10 = Choice=1 + Choice2=1 + 8 bits + */ +#define LEN_LOW_BITS 3 +#define LEN_LOW_SYMBOLS (1 << LEN_LOW_BITS) +#define LEN_MID_BITS 3 +#define LEN_MID_SYMBOLS (1 << LEN_MID_BITS) +#define LEN_HIGH_BITS 8 +#define LEN_HIGH_SYMBOLS (1 << LEN_HIGH_BITS) +#define LEN_SYMBOLS (LEN_LOW_SYMBOLS + LEN_MID_SYMBOLS + LEN_HIGH_SYMBOLS) + +/* + * Maximum length of a match is 273 which is a result of the encoding + * described above. + */ +#define MATCH_LEN_MAX (MATCH_LEN_MIN + LEN_SYMBOLS - 1) + +/* + * Different sets of probabilities are used for match distances that have + * very short match length: Lengths of 2, 3, and 4 bytes have a separate + * set of probabilities for each length. The matches with longer length + * use a shared set of probabilities. + */ +#define DIST_STATES 4 + +/* + * Get the index of the appropriate probability array for decoding + * the distance slot. + */ +static inline uint32_t lzma_get_dist_state(uint32_t len) +{ + return len < DIST_STATES + MATCH_LEN_MIN ? len - MATCH_LEN_MIN : DIST_STATES - 1; +} + +/* + * The highest two bits of a 32-bit match distance are encoded using six bits. + * This six-bit value is called a distance slot. This way encoding a 32-bit + * value takes 6-36 bits, larger values taking more bits. + */ +#define DIST_SLOT_BITS 6 +#define DIST_SLOTS (1 << DIST_SLOT_BITS) + +/* Match distances up to 127 are fully encoded using probabilities. Since + * the highest two bits (distance slot) are always encoded using six bits, + * the distances 0-3 don't need any additional bits to encode, since the + * distance slot itself is the same as the actual distance. DIST_MODEL_START + * indicates the first distance slot where at least one additional bit is + * needed. + */ +#define DIST_MODEL_START 4 + +/* + * Match distances greater than 127 are encoded in three pieces: + * - distance slot: the highest two bits + * - direct bits: 2-26 bits below the highest two bits + * - alignment bits: four lowest bits + * + * Direct bits don't use any probabilities. + * + * The distance slot value of 14 is for distances 128-191. + */ +#define DIST_MODEL_END 14 + +/* Distance slots that indicate a distance <= 127. */ +#define FULL_DISTANCES_BITS (DIST_MODEL_END / 2) +#define FULL_DISTANCES (1 << FULL_DISTANCES_BITS) + +/* + * For match distances greater than 127, only the highest two bits and the + * lowest four bits (alignment) is encoded using probabilities. + */ +#define ALIGN_BITS 4 +#define ALIGN_SIZE (1 << ALIGN_BITS) +#define ALIGN_MASK (ALIGN_SIZE - 1) + +/* Total number of all probability variables */ +#define PROBS_TOTAL (1846 + LITERAL_CODERS_MAX *LITERAL_CODER_SIZE) + +/* + * LZMA remembers the four most recent match distances. Reusing these + * distances tends to take less space than re-encoding the actual + * distance value. + */ +#define REPS 4 + +#endif diff --git a/depends/xz-embedded/src/xz_private.h b/depends/xz-embedded/src/xz_private.h new file mode 100644 index 00000000..55a3af1c --- /dev/null +++ b/depends/xz-embedded/src/xz_private.h @@ -0,0 +1,150 @@ +/* + * Private includes and definitions + * + * Author: Lasse Collin <lasse.collin@tukaani.org> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#ifndef XZ_PRIVATE_H +#define XZ_PRIVATE_H + +#ifdef __KERNEL__ +#include <linux/xz.h> +#include <linux/kernel.h> +#include <asm/unaligned.h> +/* XZ_PREBOOT may be defined only via decompress_unxz.c. */ +#ifndef XZ_PREBOOT +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/string.h> +#ifdef CONFIG_XZ_DEC_X86 +#define XZ_DEC_X86 +#endif +#ifdef CONFIG_XZ_DEC_POWERPC +#define XZ_DEC_POWERPC +#endif +#ifdef CONFIG_XZ_DEC_IA64 +#define XZ_DEC_IA64 +#endif +#ifdef CONFIG_XZ_DEC_ARM +#define XZ_DEC_ARM +#endif +#ifdef CONFIG_XZ_DEC_ARMTHUMB +#define XZ_DEC_ARMTHUMB +#endif +#ifdef CONFIG_XZ_DEC_SPARC +#define XZ_DEC_SPARC +#endif +#define memeq(a, b, size) (memcmp(a, b, size) == 0) +#define memzero(buf, size) memset(buf, 0, size) +#endif +#define get_le32(p) le32_to_cpup((const uint32_t *)(p)) +#else +/* + * For userspace builds, use a separate header to define the required + * macros and functions. This makes it easier to adapt the code into + * different environments and avoids clutter in the Linux kernel tree. + */ +#include "xz_config.h" +#endif + +/* If no specific decoding mode is requested, enable support for all modes. */ +#if !defined(XZ_DEC_SINGLE) && !defined(XZ_DEC_PREALLOC) && !defined(XZ_DEC_DYNALLOC) +#define XZ_DEC_SINGLE +#define XZ_DEC_PREALLOC +#define XZ_DEC_DYNALLOC +#endif + +/* + * The DEC_IS_foo(mode) macros are used in "if" statements. If only some + * of the supported modes are enabled, these macros will evaluate to true or + * false at compile time and thus allow the compiler to omit unneeded code. + */ +#ifdef XZ_DEC_SINGLE +#define DEC_IS_SINGLE(mode) ((mode) == XZ_SINGLE) +#else +#define DEC_IS_SINGLE(mode) (false) +#endif + +#ifdef XZ_DEC_PREALLOC +#define DEC_IS_PREALLOC(mode) ((mode) == XZ_PREALLOC) +#else +#define DEC_IS_PREALLOC(mode) (false) +#endif + +#ifdef XZ_DEC_DYNALLOC +#define DEC_IS_DYNALLOC(mode) ((mode) == XZ_DYNALLOC) +#else +#define DEC_IS_DYNALLOC(mode) (false) +#endif + +#if !defined(XZ_DEC_SINGLE) +#define DEC_IS_MULTI(mode) (true) +#elif defined(XZ_DEC_PREALLOC) || defined(XZ_DEC_DYNALLOC) +#define DEC_IS_MULTI(mode) ((mode) != XZ_SINGLE) +#else +#define DEC_IS_MULTI(mode) (false) +#endif + +/* + * If any of the BCJ filter decoders are wanted, define XZ_DEC_BCJ. + * XZ_DEC_BCJ is used to enable generic support for BCJ decoders. + */ +#ifndef XZ_DEC_BCJ +#if defined(XZ_DEC_X86) || defined(XZ_DEC_POWERPC) || defined(XZ_DEC_IA64) || \ + defined(XZ_DEC_ARM) || defined(XZ_DEC_ARM) || defined(XZ_DEC_ARMTHUMB) || \ + defined(XZ_DEC_SPARC) +#define XZ_DEC_BCJ +#endif +#endif + +/* + * Allocate memory for LZMA2 decoder. xz_dec_lzma2_reset() must be used + * before calling xz_dec_lzma2_run(). + */ +XZ_EXTERN struct xz_dec_lzma2 *xz_dec_lzma2_create(enum xz_mode mode, uint32_t dict_max); + +/* + * Decode the LZMA2 properties (one byte) and reset the decoder. Return + * XZ_OK on success, XZ_MEMLIMIT_ERROR if the preallocated dictionary is not + * big enough, and XZ_OPTIONS_ERROR if props indicates something that this + * decoder doesn't support. + */ +XZ_EXTERN enum xz_ret xz_dec_lzma2_reset(struct xz_dec_lzma2 *s, uint8_t props); + +/* Decode raw LZMA2 stream from b->in to b->out. */ +XZ_EXTERN enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, struct xz_buf *b); + +/* Free the memory allocated for the LZMA2 decoder. */ +XZ_EXTERN void xz_dec_lzma2_end(struct xz_dec_lzma2 *s); + +#ifdef XZ_DEC_BCJ +/* + * Allocate memory for BCJ decoders. xz_dec_bcj_reset() must be used before + * calling xz_dec_bcj_run(). + */ +XZ_EXTERN struct xz_dec_bcj *xz_dec_bcj_create(bool single_call); + +/* + * Decode the Filter ID of a BCJ filter. This implementation doesn't + * support custom start offsets, so no decoding of Filter Properties + * is needed. Returns XZ_OK if the given Filter ID is supported. + * Otherwise XZ_OPTIONS_ERROR is returned. + */ +XZ_EXTERN enum xz_ret xz_dec_bcj_reset(struct xz_dec_bcj *s, uint8_t id); + +/* + * Decode raw BCJ + LZMA2 stream. This must be used only if there actually is + * a BCJ filter in the chain. If the chain has only LZMA2, xz_dec_lzma2_run() + * must be called directly. + */ +XZ_EXTERN enum xz_ret xz_dec_bcj_run(struct xz_dec_bcj *s, struct xz_dec_lzma2 *lzma2, + struct xz_buf *b); + +/* Free the memory allocated for the BCJ filters. */ +#define xz_dec_bcj_end(s) kfree(s) +#endif + +#endif diff --git a/depends/xz-embedded/src/xz_stream.h b/depends/xz-embedded/src/xz_stream.h new file mode 100644 index 00000000..c0e191e6 --- /dev/null +++ b/depends/xz-embedded/src/xz_stream.h @@ -0,0 +1,62 @@ +/* + * Definitions for handling the .xz file format + * + * Author: Lasse Collin <lasse.collin@tukaani.org> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#ifndef XZ_STREAM_H +#define XZ_STREAM_H + +#if defined(__KERNEL__) && !XZ_INTERNAL_CRC32 +#include <linux/crc32.h> +#undef crc32 +#define xz_crc32(buf, size, crc) (~crc32_le(~(uint32_t)(crc), buf, size)) +#endif + +/* + * See the .xz file format specification at + * http://tukaani.org/xz/xz-file-format.txt + * to understand the container format. + */ + +#define STREAM_HEADER_SIZE 12 + +#define HEADER_MAGIC "\3757zXZ" +#define HEADER_MAGIC_SIZE 6 + +#define FOOTER_MAGIC "YZ" +#define FOOTER_MAGIC_SIZE 2 + +/* + * Variable-length integer can hold a 63-bit unsigned integer or a special + * value indicating that the value is unknown. + * + * Experimental: vli_type can be defined to uint32_t to save a few bytes + * in code size (no effect on speed). Doing so limits the uncompressed and + * compressed size of the file to less than 256 MiB and may also weaken + * error detection slightly. + */ +typedef uint64_t vli_type; + +#define VLI_MAX ((vli_type) - 1 / 2) +#define VLI_UNKNOWN ((vli_type) - 1) + +/* Maximum encoded size of a VLI */ +#define VLI_BYTES_MAX (sizeof(vli_type) * 8 / 7) + +/* Integrity Check types */ +enum xz_check +{ + XZ_CHECK_NONE = 0, + XZ_CHECK_CRC32 = 1, + XZ_CHECK_CRC64 = 4, + XZ_CHECK_SHA256 = 10 +}; + +/* Maximum possible Check ID */ +#define XZ_CHECK_MAX 15 + +#endif diff --git a/depends/xz-embedded/xzminidec.c b/depends/xz-embedded/xzminidec.c new file mode 100644 index 00000000..bb62c3ac --- /dev/null +++ b/depends/xz-embedded/xzminidec.c @@ -0,0 +1,144 @@ +/* + * Simple XZ decoder command line tool + * + * Author: Lasse Collin <lasse.collin@tukaani.org> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +/* + * This is really limited: Not all filters from .xz format are supported, + * only CRC32 is supported as the integrity check, and decoding of + * concatenated .xz streams is not supported. Thus, you may want to look + * at xzdec from XZ Utils if a few KiB bigger tool is not a problem. + */ + +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include "xz.h" + +static uint8_t in[BUFSIZ]; +static uint8_t out[BUFSIZ]; + +int main(int argc, char **argv) +{ + struct xz_buf b; + struct xz_dec *s; + enum xz_ret ret; + const char *msg; + + if (argc >= 2 && strcmp(argv[1], "--help") == 0) + { + fputs("Uncompress a .xz file from stdin to stdout.\n" + "Arguments other than `--help' are ignored.\n", + stdout); + return 0; + } + + xz_crc32_init(); +#ifdef XZ_USE_CRC64 + xz_crc64_init(); +#endif + + /* + * Support up to 64 MiB dictionary. The actually needed memory + * is allocated once the headers have been parsed. + */ + s = xz_dec_init(XZ_DYNALLOC, 1 << 26); + if (s == NULL) + { + msg = "Memory allocation failed\n"; + goto error; + } + + b.in = in; + b.in_pos = 0; + b.in_size = 0; + b.out = out; + b.out_pos = 0; + b.out_size = BUFSIZ; + + while (true) + { + if (b.in_pos == b.in_size) + { + b.in_size = fread(in, 1, sizeof(in), stdin); + b.in_pos = 0; + } + + ret = xz_dec_run(s, &b); + + if (b.out_pos == sizeof(out)) + { + if (fwrite(out, 1, b.out_pos, stdout) != b.out_pos) + { + msg = "Write error\n"; + goto error; + } + + b.out_pos = 0; + } + + if (ret == XZ_OK) + continue; + +#ifdef XZ_DEC_ANY_CHECK + if (ret == XZ_UNSUPPORTED_CHECK) + { + fputs(argv[0], stderr); + fputs(": ", stderr); + fputs("Unsupported check; not verifying " + "file integrity\n", + stderr); + continue; + } +#endif + + if (fwrite(out, 1, b.out_pos, stdout) != b.out_pos || fclose(stdout)) + { + msg = "Write error\n"; + goto error; + } + + switch (ret) + { + case XZ_STREAM_END: + xz_dec_end(s); + return 0; + + case XZ_MEM_ERROR: + msg = "Memory allocation failed\n"; + goto error; + + case XZ_MEMLIMIT_ERROR: + msg = "Memory usage limit reached\n"; + goto error; + + case XZ_FORMAT_ERROR: + msg = "Not a .xz file\n"; + goto error; + + case XZ_OPTIONS_ERROR: + msg = "Unsupported options in the .xz headers\n"; + goto error; + + case XZ_DATA_ERROR: + case XZ_BUF_ERROR: + msg = "File is corrupt\n"; + goto error; + + default: + msg = "Bug!\n"; + goto error; + } + } + +error: + xz_dec_end(s); + fputs(argv[0], stderr); + fputs(": ", stderr); + fputs(msg, stderr); + return 1; +} |